From 48df376f845d06990d0887cb36a6cff14a806662 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Jan 2025 03:25:27 +0000
Subject: [PATCH 001/227] initial commit

---
 .devcontainer/Dockerfile                      |    9 +
 .devcontainer/devcontainer.json               |   40 +
 .github/workflows/ci.yml                      |   53 +
 .gitignore                                    |   16 +
 .python-version                               |    1 +
 .stats.yml                                    |    2 +
 Brewfile                                      |    2 +
 CONTRIBUTING.md                               |  129 ++
 LICENSE                                       |  201 ++
 README.md                                     |  357 ++-
 SECURITY.md                                   |   27 +
 api.md                                        |  125 +
 bin/publish-pypi                              |    9 +
 examples/.keep                                |    4 +
 mypy.ini                                      |   50 +
 noxfile.py                                    |    9 +
 pyproject.toml                                |  207 ++
 requirements-dev.lock                         |  103 +
 requirements.lock                             |   44 +
 scripts/bootstrap                             |   19 +
 scripts/format                                |    8 +
 scripts/lint                                  |   12 +
 scripts/mock                                  |   41 +
 scripts/test                                  |   59 +
 scripts/utils/ruffen-docs.py                  |  167 ++
 src/codex/__init__.py                         |   84 +
 src/codex/_base_client.py                     | 2051 +++++++++++++++++
 src/codex/_client.py                          |  518 +++++
 src/codex/_compat.py                          |  219 ++
 src/codex/_constants.py                       |   14 +
 src/codex/_exceptions.py                      |  108 +
 src/codex/_files.py                           |  123 +
 src/codex/_models.py                          |  795 +++++++
 src/codex/_qs.py                              |  150 ++
 src/codex/_resource.py                        |   43 +
 src/codex/_response.py                        |  824 +++++++
 src/codex/_streaming.py                       |  333 +++
 src/codex/_types.py                           |  217 ++
 src/codex/_utils/__init__.py                  |   57 +
 src/codex/_utils/_logs.py                     |   25 +
 src/codex/_utils/_proxy.py                    |   62 +
 src/codex/_utils/_reflection.py               |   42 +
 src/codex/_utils/_streams.py                  |   12 +
 src/codex/_utils/_sync.py                     |   71 +
 src/codex/_utils/_transform.py                |  392 ++++
 src/codex/_utils/_typing.py                   |  149 ++
 src/codex/_utils/_utils.py                    |  414 ++++
 src/codex/_version.py                         |    4 +
 src/codex/lib/.keep                           |    4 +
 src/codex/py.typed                            |    0
 src/codex/resources/__init__.py               |   61 +
 src/codex/resources/health.py                 |  235 ++
 src/codex/resources/organizations/__init__.py |   33 +
 src/codex/resources/organizations/billing.py  |  242 ++
 .../resources/organizations/organizations.py  |  195 ++
 src/codex/resources/projects/__init__.py      |   47 +
 src/codex/resources/projects/access_keys.py   |  594 +++++
 src/codex/resources/projects/knowledge.py     |  740 ++++++
 src/codex/resources/projects/projects.py      |  659 ++++++
 src/codex/resources/users/__init__.py         |   33 +
 src/codex/resources/users/myself/__init__.py  |   47 +
 src/codex/resources/users/myself/api_key.py   |  135 ++
 src/codex/resources/users/myself/myself.py    |  199 ++
 .../resources/users/myself/organizations.py   |  135 ++
 src/codex/resources/users/users.py            |  102 +
 src/codex/types/__init__.py                   |   11 +
 src/codex/types/health_check_response.py      |   10 +
 src/codex/types/organization_schema_public.py |   17 +
 src/codex/types/organizations/__init__.py     |    6 +
 .../organization_billing_invoices_schema.py   |   10 +
 .../organization_billing_usage_schema.py      |   10 +
 src/codex/types/project_create_params.py      |   22 +
 src/codex/types/project_list_params.py        |   11 +
 src/codex/types/project_list_response.py      |   10 +
 src/codex/types/project_return_schema.py      |   30 +
 src/codex/types/project_update_params.py      |   20 +
 src/codex/types/projects/__init__.py          |   15 +
 .../projects/access_key_create_params.py      |   19 +
 .../projects/access_key_list_response.py      |   10 +
 src/codex/types/projects/access_key_schema.py |   31 +
 .../projects/access_key_update_params.py      |   21 +
 src/codex/types/projects/entry.py             |   20 +
 .../projects/knowledge_add_question_params.py |   11 +
 .../types/projects/knowledge_create_params.py |   14 +
 .../types/projects/knowledge_list_params.py   |   21 +
 .../types/projects/knowledge_query_params.py  |   11 +
 .../types/projects/knowledge_update_params.py |   16 +
 .../types/projects/list_knowledge_response.py |   14 +
 src/codex/types/users/__init__.py             |    6 +
 src/codex/types/users/myself/__init__.py      |    5 +
 .../users/myself/user_organizations_schema.py |   22 +
 src/codex/types/users/user_schema.py          |   24 +
 src/codex/types/users/user_schema_public.py   |   17 +
 tests/__init__.py                             |    1 +
 tests/api_resources/__init__.py               |    1 +
 tests/api_resources/organizations/__init__.py |    1 +
 .../organizations/test_billing.py             |  174 ++
 tests/api_resources/projects/__init__.py      |    1 +
 .../projects/test_access_keys.py              |  476 ++++
 .../api_resources/projects/test_knowledge.py  |  607 +++++
 tests/api_resources/test_health.py            |  172 ++
 tests/api_resources/test_organizations.py     |   98 +
 tests/api_resources/test_projects.py          |  461 ++++
 tests/api_resources/users/__init__.py         |    1 +
 tests/api_resources/users/myself/__init__.py  |    1 +
 .../users/myself/test_api_key.py              |   72 +
 .../users/myself/test_organizations.py        |   72 +
 tests/api_resources/users/test_myself.py      |   72 +
 tests/conftest.py                             |   65 +
 tests/sample_file.txt                         |    1 +
 tests/test_client.py                          | 1873 +++++++++++++++
 tests/test_deepcopy.py                        |   58 +
 tests/test_extract_files.py                   |   64 +
 tests/test_files.py                           |   51 +
 tests/test_models.py                          |  856 +++++++
 tests/test_qs.py                              |   78 +
 tests/test_required_args.py                   |  111 +
 tests/test_response.py                        |  277 +++
 tests/test_streaming.py                       |  248 ++
 tests/test_transform.py                       |  425 ++++
 tests/test_utils/test_proxy.py                |   23 +
 tests/test_utils/test_typing.py               |   73 +
 tests/utils.py                                |  159 ++
 123 files changed, 18867 insertions(+), 1 deletion(-)
 create mode 100644 .devcontainer/Dockerfile
 create mode 100644 .devcontainer/devcontainer.json
 create mode 100644 .github/workflows/ci.yml
 create mode 100644 .gitignore
 create mode 100644 .python-version
 create mode 100644 .stats.yml
 create mode 100644 Brewfile
 create mode 100644 CONTRIBUTING.md
 create mode 100644 LICENSE
 create mode 100644 SECURITY.md
 create mode 100644 api.md
 create mode 100644 bin/publish-pypi
 create mode 100644 examples/.keep
 create mode 100644 mypy.ini
 create mode 100644 noxfile.py
 create mode 100644 pyproject.toml
 create mode 100644 requirements-dev.lock
 create mode 100644 requirements.lock
 create mode 100755 scripts/bootstrap
 create mode 100755 scripts/format
 create mode 100755 scripts/lint
 create mode 100755 scripts/mock
 create mode 100755 scripts/test
 create mode 100644 scripts/utils/ruffen-docs.py
 create mode 100644 src/codex/__init__.py
 create mode 100644 src/codex/_base_client.py
 create mode 100644 src/codex/_client.py
 create mode 100644 src/codex/_compat.py
 create mode 100644 src/codex/_constants.py
 create mode 100644 src/codex/_exceptions.py
 create mode 100644 src/codex/_files.py
 create mode 100644 src/codex/_models.py
 create mode 100644 src/codex/_qs.py
 create mode 100644 src/codex/_resource.py
 create mode 100644 src/codex/_response.py
 create mode 100644 src/codex/_streaming.py
 create mode 100644 src/codex/_types.py
 create mode 100644 src/codex/_utils/__init__.py
 create mode 100644 src/codex/_utils/_logs.py
 create mode 100644 src/codex/_utils/_proxy.py
 create mode 100644 src/codex/_utils/_reflection.py
 create mode 100644 src/codex/_utils/_streams.py
 create mode 100644 src/codex/_utils/_sync.py
 create mode 100644 src/codex/_utils/_transform.py
 create mode 100644 src/codex/_utils/_typing.py
 create mode 100644 src/codex/_utils/_utils.py
 create mode 100644 src/codex/_version.py
 create mode 100644 src/codex/lib/.keep
 create mode 100644 src/codex/py.typed
 create mode 100644 src/codex/resources/__init__.py
 create mode 100644 src/codex/resources/health.py
 create mode 100644 src/codex/resources/organizations/__init__.py
 create mode 100644 src/codex/resources/organizations/billing.py
 create mode 100644 src/codex/resources/organizations/organizations.py
 create mode 100644 src/codex/resources/projects/__init__.py
 create mode 100644 src/codex/resources/projects/access_keys.py
 create mode 100644 src/codex/resources/projects/knowledge.py
 create mode 100644 src/codex/resources/projects/projects.py
 create mode 100644 src/codex/resources/users/__init__.py
 create mode 100644 src/codex/resources/users/myself/__init__.py
 create mode 100644 src/codex/resources/users/myself/api_key.py
 create mode 100644 src/codex/resources/users/myself/myself.py
 create mode 100644 src/codex/resources/users/myself/organizations.py
 create mode 100644 src/codex/resources/users/users.py
 create mode 100644 src/codex/types/__init__.py
 create mode 100644 src/codex/types/health_check_response.py
 create mode 100644 src/codex/types/organization_schema_public.py
 create mode 100644 src/codex/types/organizations/__init__.py
 create mode 100644 src/codex/types/organizations/organization_billing_invoices_schema.py
 create mode 100644 src/codex/types/organizations/organization_billing_usage_schema.py
 create mode 100644 src/codex/types/project_create_params.py
 create mode 100644 src/codex/types/project_list_params.py
 create mode 100644 src/codex/types/project_list_response.py
 create mode 100644 src/codex/types/project_return_schema.py
 create mode 100644 src/codex/types/project_update_params.py
 create mode 100644 src/codex/types/projects/__init__.py
 create mode 100644 src/codex/types/projects/access_key_create_params.py
 create mode 100644 src/codex/types/projects/access_key_list_response.py
 create mode 100644 src/codex/types/projects/access_key_schema.py
 create mode 100644 src/codex/types/projects/access_key_update_params.py
 create mode 100644 src/codex/types/projects/entry.py
 create mode 100644 src/codex/types/projects/knowledge_add_question_params.py
 create mode 100644 src/codex/types/projects/knowledge_create_params.py
 create mode 100644 src/codex/types/projects/knowledge_list_params.py
 create mode 100644 src/codex/types/projects/knowledge_query_params.py
 create mode 100644 src/codex/types/projects/knowledge_update_params.py
 create mode 100644 src/codex/types/projects/list_knowledge_response.py
 create mode 100644 src/codex/types/users/__init__.py
 create mode 100644 src/codex/types/users/myself/__init__.py
 create mode 100644 src/codex/types/users/myself/user_organizations_schema.py
 create mode 100644 src/codex/types/users/user_schema.py
 create mode 100644 src/codex/types/users/user_schema_public.py
 create mode 100644 tests/__init__.py
 create mode 100644 tests/api_resources/__init__.py
 create mode 100644 tests/api_resources/organizations/__init__.py
 create mode 100644 tests/api_resources/organizations/test_billing.py
 create mode 100644 tests/api_resources/projects/__init__.py
 create mode 100644 tests/api_resources/projects/test_access_keys.py
 create mode 100644 tests/api_resources/projects/test_knowledge.py
 create mode 100644 tests/api_resources/test_health.py
 create mode 100644 tests/api_resources/test_organizations.py
 create mode 100644 tests/api_resources/test_projects.py
 create mode 100644 tests/api_resources/users/__init__.py
 create mode 100644 tests/api_resources/users/myself/__init__.py
 create mode 100644 tests/api_resources/users/myself/test_api_key.py
 create mode 100644 tests/api_resources/users/myself/test_organizations.py
 create mode 100644 tests/api_resources/users/test_myself.py
 create mode 100644 tests/conftest.py
 create mode 100644 tests/sample_file.txt
 create mode 100644 tests/test_client.py
 create mode 100644 tests/test_deepcopy.py
 create mode 100644 tests/test_extract_files.py
 create mode 100644 tests/test_files.py
 create mode 100644 tests/test_models.py
 create mode 100644 tests/test_qs.py
 create mode 100644 tests/test_required_args.py
 create mode 100644 tests/test_response.py
 create mode 100644 tests/test_streaming.py
 create mode 100644 tests/test_transform.py
 create mode 100644 tests/test_utils/test_proxy.py
 create mode 100644 tests/test_utils/test_typing.py
 create mode 100644 tests/utils.py

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..ac9a2e75
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,9 @@
+ARG VARIANT="3.9"
+FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
+
+USER vscode
+
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
+ENV PATH=/home/vscode/.rye/shims:$PATH
+
+RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..bbeb30b1
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,40 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/debian
+{
+  "name": "Debian",
+  "build": {
+    "dockerfile": "Dockerfile",
+    "context": ".."
+  },
+
+  "postStartCommand": "rye sync --all-features",
+
+  "customizations": {
+    "vscode": {
+      "extensions": [
+        "ms-python.python"
+      ],
+      "settings": { 
+        "terminal.integrated.shell.linux": "/bin/bash",
+        "python.pythonPath": ".venv/bin/python",
+        "python.defaultInterpreterPath": ".venv/bin/python",
+        "python.typeChecking": "basic",
+        "terminal.integrated.env.linux": {
+          "PATH": "/home/vscode/.rye/shims:${env:PATH}"
+        }
+      }
+    }
+  }
+
+  // Features to add to the dev container. More info: https://containers.dev/features.
+  // "features": {},
+
+  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+  // "forwardPorts": [],
+
+  // Configure tool-specific properties.
+  // "customizations": {},
+
+  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+  // "remoteUser": "root"
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..40293964
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,53 @@
+name: CI
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+      - next
+
+jobs:
+  lint:
+    name: lint
+    runs-on: ubuntu-latest
+    
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.35.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Install dependencies
+        run: rye sync --all-features
+
+      - name: Run lints
+        run: ./scripts/lint
+  test:
+    name: test
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.35.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Bootstrap
+        run: ./scripts/bootstrap
+
+      - name: Run tests
+        run: ./scripts/test
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..87797408
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.prism.log
+.vscode
+_dev
+
+__pycache__
+.mypy_cache
+
+dist
+
+.venv
+.idea
+
+.env
+.envrc
+codegen.log
+Brewfile.lock.json
diff --git a/.python-version b/.python-version
new file mode 100644
index 00000000..43077b24
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.9.18
diff --git a/.stats.yml b/.stats.yml
new file mode 100644
index 00000000..71d28223
--- /dev/null
+++ b/.stats.yml
@@ -0,0 +1,2 @@
+configured_endpoints: 28
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-f4a89fd69be5055c12ea6d3aa795af4161605848eb9e010e1a7be22f4b1b712c.yml
diff --git a/Brewfile b/Brewfile
new file mode 100644
index 00000000..492ca37b
--- /dev/null
+++ b/Brewfile
@@ -0,0 +1,2 @@
+brew "rye"
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..7111bafc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,129 @@
+## Setting up the environment
+
+### With Rye
+
+We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run:
+
+```sh
+$ ./scripts/bootstrap
+```
+
+Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run:
+
+```sh
+$ rye sync --all-features
+```
+
+You can then run scripts using `rye run python script.py` or by activating the virtual environment:
+
+```sh
+$ rye shell
+# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
+$ source .venv/bin/activate
+
+# now you can omit the `rye run` prefix
+$ python script.py
+```
+
+### Without Rye
+
+Alternatively if you don't want to install `Rye`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command:
+
+```sh
+$ pip install -r requirements-dev.lock
+```
+
+## Modifying/Adding code
+
+Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
+result in merge conflicts between manual patches and changes from the generator. The generator will never
+modify the contents of the `src/codex/lib/` and `examples/` directories.
+
+## Adding and running examples
+
+All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
+
+```py
+# add an example to examples/<your-example>.py
+
+#!/usr/bin/env -S rye run python
+…
+```
+
+```sh
+$ chmod +x examples/<your-example>.py
+# run the example against your api
+$ ./examples/<your-example>.py
+```
+
+## Using the repository from source
+
+If you’d like to use the repository from source, you can either install from git or link to a cloned repository:
+
+To install via git:
+
+```sh
+$ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
+```
+
+Alternatively, you can build from source and install the wheel file:
+
+Building this package will create two files in the `dist/` directory, a `.tar.gz` containing the source files and a `.whl` that can be used to install the package efficiently.
+
+To create a distributable version of the library, all you have to do is run this command:
+
+```sh
+$ rye build
+# or
+$ python -m build
+```
+
+Then to install:
+
+```sh
+$ pip install ./path-to-wheel-file.whl
+```
+
+## Running tests
+
+Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
+
+```sh
+# you will need npm installed
+$ npx prism mock path/to/your/openapi.yml
+```
+
+```sh
+$ ./scripts/test
+```
+
+## Linting and formatting
+
+This repository uses [ruff](https://github.com/astral-sh/ruff) and
+[black](https://github.com/psf/black) to format the code in the repository.
+
+To lint:
+
+```sh
+$ ./scripts/lint
+```
+
+To format and fix all ruff issues automatically:
+
+```sh
+$ ./scripts/format
+```
+
+## Publishing and releases
+
+Changes made to this repository via the automated release PR pipeline should publish to PyPI automatically. If
+the changes aren't made through the automated pipeline, you may want to make releases manually.
+
+### Publish with a GitHub workflow
+
+You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
+
+### Publish manually
+
+If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on
+the environment.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..f00d3a6a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2025 Codex
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
index 40f5fc62..e4bf463c 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,356 @@
-# codex-python
\ No newline at end of file
+# Codex Python API library
+
+[![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/)
+
+The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+
+application. The library includes type definitions for all request params and response fields,
+and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
+
+It is generated with [Stainless](https://www.stainlessapi.com/).
+
+## Documentation
+
+The REST API documentation can be found on [docs.codex.com](https://docs.codex.com). The full API of this library can be found in [api.md](api.md).
+
+## Installation
+
+```sh
+# install from this staging repo
+pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
+```
+
+> [!NOTE]
+> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex`
+
+## Usage
+
+The full API of this library can be found in [api.md](api.md).
+
+```python
+from codex import Codex
+
+client = Codex()
+
+project_return_schema = client.projects.create(
+    config={},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(project_return_schema.id)
+```
+
+While you can provide a `bearer_token` keyword argument,
+we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
+to add `BEARER_TOKEN="My Bearer Token"` to your `.env` file
+so that your Bearer Token is not stored in source control.
+
+## Async usage
+
+Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
+
+```python
+import asyncio
+from codex import AsyncCodex
+
+client = AsyncCodex()
+
+
+async def main() -> None:
+    project_return_schema = await client.projects.create(
+        config={},
+        name="name",
+        organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+    )
+    print(project_return_schema.id)
+
+
+asyncio.run(main())
+```
+
+Functionality between the synchronous and asynchronous clients is otherwise identical.
+
+## Using types
+
+Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
+
+- Serializing back into JSON, `model.to_json()`
+- Converting to a dictionary, `model.to_dict()`
+
+Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
+
+## Handling errors
+
+When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised.
+
+When the API returns a non-success status code (that is, 4xx or 5xx
+response), a subclass of `codex.APIStatusError` is raised, containing `status_code` and `response` properties.
+
+All errors inherit from `codex.APIError`.
+
+```python
+import codex
+from codex import Codex
+
+client = Codex()
+
+try:
+    client.projects.create(
+        config={},
+        name="name",
+        organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+    )
+except codex.APIConnectionError as e:
+    print("The server could not be reached")
+    print(e.__cause__)  # an underlying Exception, likely raised within httpx.
+except codex.RateLimitError as e:
+    print("A 429 status code was received; we should back off a bit.")
+except codex.APIStatusError as e:
+    print("Another non-200-range status code was received")
+    print(e.status_code)
+    print(e.response)
+```
+
+Error codes are as follows:
+
+| Status Code | Error Type                 |
+| ----------- | -------------------------- |
+| 400         | `BadRequestError`          |
+| 401         | `AuthenticationError`      |
+| 403         | `PermissionDeniedError`    |
+| 404         | `NotFoundError`            |
+| 422         | `UnprocessableEntityError` |
+| 429         | `RateLimitError`           |
+| >=500       | `InternalServerError`      |
+| N/A         | `APIConnectionError`       |
+
+### Retries
+
+Certain errors are automatically retried 2 times by default, with a short exponential backoff.
+Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
+429 Rate Limit, and >=500 Internal errors are all retried by default.
+
+You can use the `max_retries` option to configure or disable retry settings:
+
+```python
+from codex import Codex
+
+# Configure the default for all requests:
+client = Codex(
+    # default is 2
+    max_retries=0,
+)
+
+# Or, configure per-request:
+client.with_options(max_retries=5).projects.create(
+    config={},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+```
+
+### Timeouts
+
+By default requests time out after 1 minute. You can configure this with a `timeout` option,
+which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
+
+```python
+from codex import Codex
+
+# Configure the default for all requests:
+client = Codex(
+    # 20 seconds (default is 1 minute)
+    timeout=20.0,
+)
+
+# More granular control:
+client = Codex(
+    timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
+)
+
+# Override per-request:
+client.with_options(timeout=5.0).projects.create(
+    config={},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+```
+
+On timeout, an `APITimeoutError` is thrown.
+
+Note that requests that time out are [retried twice by default](#retries).
+
+## Advanced
+
+### Logging
+
+We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
+
+You can enable logging by setting the environment variable `CODEX_LOG` to `info`.
+
+```shell
+$ export CODEX_LOG=info
+```
+
+Or to `debug` for more verbose logging.
+
+### How to tell whether `None` means `null` or missing
+
+In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`:
+
+```py
+if response.my_field is None:
+  if 'my_field' not in response.model_fields_set:
+    print('Got json like {}, without a "my_field" key present at all.')
+  else:
+    print('Got json like {"my_field": null}.')
+```
+
+### Accessing raw response data (e.g. headers)
+
+The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
+
+```py
+from codex import Codex
+
+client = Codex()
+response = client.projects.with_raw_response.create(
+    config={},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(response.headers.get('X-My-Header'))
+
+project = response.parse()  # get the object that `projects.create()` would have returned
+print(project.id)
+```
+
+These methods return an [`APIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) object.
+
+The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
+
+#### `.with_streaming_response`
+
+The above interface eagerly reads the full response body when you make the request, which may not always be what you want.
+
+To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
+
+```python
+with client.projects.with_streaming_response.create(
+    config={},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+) as response:
+    print(response.headers.get("X-My-Header"))
+
+    for line in response.iter_lines():
+        print(line)
+```
+
+The context manager is required so that the response will reliably be closed.
+
+### Making custom/undocumented requests
+
+This library is typed for convenient access to the documented API.
+
+If you need to access undocumented endpoints, params, or response properties, the library can still be used.
+
+#### Undocumented endpoints
+
+To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other
+http verbs. Options on the client will be respected (such as retries) when making this request.
+
+```py
+import httpx
+
+response = client.post(
+    "/foo",
+    cast_to=httpx.Response,
+    body={"my_param": True},
+)
+
+print(response.headers.get("x-foo"))
+```
+
+#### Undocumented request params
+
+If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
+options.
+
+#### Undocumented response properties
+
+To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You
+can also get all the extra fields on the Pydantic model as a dict with
+[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).
+
+### Configuring the HTTP client
+
+You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including:
+
+- Support for [proxies](https://www.python-httpx.org/advanced/proxies/)
+- Custom [transports](https://www.python-httpx.org/advanced/transports/)
+- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality
+
+```python
+import httpx
+from codex import Codex, DefaultHttpxClient
+
+client = Codex(
+    # Or use the `CODEX_BASE_URL` env var
+    base_url="http://my.test.server.example.com:8083",
+    http_client=DefaultHttpxClient(
+        proxy="http://my.test.proxy.example.com",
+        transport=httpx.HTTPTransport(local_address="0.0.0.0"),
+    ),
+)
+```
+
+You can also customize the client on a per-request basis by using `with_options()`:
+
+```python
+client.with_options(http_client=DefaultHttpxClient(...))
+```
+
+### Managing HTTP resources
+
+By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
+
+```py
+from codex import Codex
+
+with Codex() as client:
+  # make requests here
+  ...
+
+# HTTP client is now closed
+```
+
+## Versioning
+
+This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
+
+1. Changes that only affect static types, without breaking runtime behavior.
+2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
+3. Changes that we do not expect to impact the vast majority of users in practice.
+
+We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
+
+We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/codex-python/issues) with questions, bugs, or suggestions.
+
+### Determining the installed version
+
+If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version.
+
+You can determine the version that is being used at runtime with:
+
+```py
+import codex
+print(codex.__version__)
+```
+
+## Requirements
+
+Python 3.8 or higher.
+
+## Contributing
+
+See [the contributing documentation](./CONTRIBUTING.md).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..cf89cd01
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,27 @@
+# Security Policy
+
+## Reporting Security Issues
+
+This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
+
+To report a security issue, please contact the Stainless team at security@stainlessapi.com.
+
+## Responsible Disclosure
+
+We appreciate the efforts of security researchers and individuals who help us maintain the security of
+SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
+disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
+before making any information public.
+
+## Reporting Non-SDK Related Security Issues
+
+If you encounter security issues that are not directly related to SDKs but pertain to the services
+or products provided by Codex please follow the respective company's security reporting guidelines.
+
+### Codex Terms and Policies
+
+Please contact dev-feedback@codex.com for any questions or concerns regarding security of our services.
+
+---
+
+Thank you for helping us keep the SDKs and systems they interact with secure.
diff --git a/api.md b/api.md
new file mode 100644
index 00000000..c9ef6c6c
--- /dev/null
+++ b/api.md
@@ -0,0 +1,125 @@
+# Health
+
+Types:
+
+```python
+from codex.types import HealthCheckResponse
+```
+
+Methods:
+
+- <code title="get /api/health/">client.health.<a href="./src/codex/resources/health.py">check</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
+- <code title="get /api/health/db">client.health.<a href="./src/codex/resources/health.py">db</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
+- <code title="get /api/health/weaviate">client.health.<a href="./src/codex/resources/health.py">weaviate</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
+
+# Organizations
+
+Types:
+
+```python
+from codex.types import OrganizationSchemaPublic
+```
+
+Methods:
+
+- <code title="get /api/organizations/{organization_id}">client.organizations.<a href="./src/codex/resources/organizations/organizations.py">retrieve</a>(organization_id) -> <a href="./src/codex/types/organization_schema_public.py">OrganizationSchemaPublic</a></code>
+
+## Billing
+
+Types:
+
+```python
+from codex.types.organizations import (
+    OrganizationBillingInvoicesSchema,
+    OrganizationBillingUsageSchema,
+)
+```
+
+Methods:
+
+- <code title="get /api/organizations/{organization_id}/billing/invoices">client.organizations.billing.<a href="./src/codex/resources/organizations/billing.py">invoices</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_invoices_schema.py">OrganizationBillingInvoicesSchema</a></code>
+- <code title="get /api/organizations/{organization_id}/billing/usage">client.organizations.billing.<a href="./src/codex/resources/organizations/billing.py">usage</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_usage_schema.py">OrganizationBillingUsageSchema</a></code>
+
+# Users
+
+## Myself
+
+Types:
+
+```python
+from codex.types.users import UserSchema, UserSchemaPublic
+```
+
+Methods:
+
+- <code title="get /api/users/myself">client.users.myself.<a href="./src/codex/resources/users/myself/myself.py">retrieve</a>() -> <a href="./src/codex/types/users/user_schema_public.py">UserSchemaPublic</a></code>
+
+### APIKey
+
+Methods:
+
+- <code title="post /api/users/myself/api-key/refresh">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">refresh</a>() -> <a href="./src/codex/types/users/user_schema.py">UserSchema</a></code>
+
+### Organizations
+
+Types:
+
+```python
+from codex.types.users.myself import UserOrganizationsSchema
+```
+
+Methods:
+
+- <code title="get /api/users/myself/organizations">client.users.myself.organizations.<a href="./src/codex/resources/users/myself/organizations.py">list</a>() -> <a href="./src/codex/types/users/myself/user_organizations_schema.py">UserOrganizationsSchema</a></code>
+
+# Projects
+
+Types:
+
+```python
+from codex.types import ProjectReturnSchema, ProjectListResponse, ProjectExportResponse
+```
+
+Methods:
+
+- <code title="post /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">create</a>(\*\*<a href="src/codex/types/project_create_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="get /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve</a>(project_id) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="put /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">update</a>(project_id, \*\*<a href="src/codex/types/project_update_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
+- <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
+- <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
+
+## AccessKeys
+
+Types:
+
+```python
+from codex.types.projects import AccessKeySchema, AccessKeyListResponse
+```
+
+Methods:
+
+- <code title="post /api/projects/{project_id}/access_keys/">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/access_key_create_params.py">params</a>) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
+- <code title="get /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve</a>(access_key_id, \*, project_id) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
+- <code title="put /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">update</a>(access_key_id, \*, project_id, \*\*<a href="src/codex/types/projects/access_key_update_params.py">params</a>) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
+- <code title="get /api/projects/{project_id}/access_keys/">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">list</a>(project_id) -> <a href="./src/codex/types/projects/access_key_list_response.py">AccessKeyListResponse</a></code>
+- <code title="delete /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">delete</a>(access_key_id, \*, project_id) -> None</code>
+- <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
+
+## Knowledge
+
+Types:
+
+```python
+from codex.types.projects import Entry, ListKnowledgeResponse
+```
+
+Methods:
+
+- <code title="post /api/projects/{project_id}/knowledge/">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="get /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="put /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/knowledge_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="get /api/projects/{project_id}/knowledge/">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_list_params.py">params</a>) -> <a href="./src/codex/types/projects/list_knowledge_response.py">ListKnowledgeResponse</a></code>
+- <code title="delete /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">delete</a>(entry_id, \*, project_id) -> None</code>
+- <code title="post /api/projects/{project_id}/knowledge/add_question">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="post /api/projects/{project_id}/knowledge/query">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
diff --git a/bin/publish-pypi b/bin/publish-pypi
new file mode 100644
index 00000000..05bfccbb
--- /dev/null
+++ b/bin/publish-pypi
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -eux
+mkdir -p dist
+rye build --clean
+# Patching importlib-metadata version until upstream library version is updated
+# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
+"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
+rye publish --yes --token=$PYPI_TOKEN
diff --git a/examples/.keep b/examples/.keep
new file mode 100644
index 00000000..d8c73e93
--- /dev/null
+++ b/examples/.keep
@@ -0,0 +1,4 @@
+File generated from our OpenAPI spec by Stainless.
+
+This directory can be used to store example files demonstrating usage of this SDK.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
\ No newline at end of file
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..88ad64a6
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,50 @@
+[mypy]
+pretty = True
+show_error_codes = True
+
+# Exclude _files.py because mypy isn't smart enough to apply
+# the correct type narrowing and as this is an internal module
+# it's fine to just use Pyright.
+#
+# We also exclude our `tests` as mypy doesn't always infer
+# types correctly and Pyright will still catch any type errors.
+exclude = ^(src/codex/_files\.py|_dev/.*\.py|tests/.*)$
+
+strict_equality = True
+implicit_reexport = True
+check_untyped_defs = True
+no_implicit_optional = True
+
+warn_return_any = True
+warn_unreachable = True
+warn_unused_configs = True
+
+# Turn these options off as it could cause conflicts
+# with the Pyright options.
+warn_unused_ignores = False
+warn_redundant_casts = False
+
+disallow_any_generics = True
+disallow_untyped_defs = True
+disallow_untyped_calls = True
+disallow_subclassing_any = True
+disallow_incomplete_defs = True
+disallow_untyped_decorators = True
+cache_fine_grained = True
+
+# By default, mypy reports an error if you assign a value to the result
+# of a function call that doesn't return anything. We do this in our test
+# cases:
+# ```
+# result = ...
+# assert result is None
+# ```
+# Changing this codegen to make mypy happy would increase complexity
+# and would not be worth it.
+disable_error_code = func-returns-value
+
+# https://github.com/python/mypy/issues/12162
+[mypy.overrides]
+module = "black.files.*"
+ignore_errors = true
+ignore_missing_imports = true
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 00000000..53bca7ff
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,9 @@
+import nox
+
+
+@nox.session(reuse_venv=True, name="test-pydantic-v1")
+def test_pydantic_v1(session: nox.Session) -> None:
+    session.install("-r", "requirements-dev.lock")
+    session.install("pydantic<2")
+
+    session.run("pytest", "--showlocals", "--ignore=tests/functional", *session.posargs)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..5116f227
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,207 @@
+[project]
+name = "codex"
+version = "0.0.1-alpha.0"
+description = "The official Python library for the codex API"
+dynamic = ["readme"]
+license = "Apache-2.0"
+authors = [
+{ name = "Codex", email = "dev-feedback@codex.com" },
+]
+dependencies = [
+    "httpx>=0.23.0, <1",
+    "pydantic>=1.9.0, <3",
+    "typing-extensions>=4.10, <5",
+    "anyio>=3.5.0, <5",
+    "distro>=1.7.0, <2",
+    "sniffio",
+]
+requires-python = ">= 3.8"
+classifiers = [
+  "Typing :: Typed",
+  "Intended Audience :: Developers",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
+  "Operating System :: OS Independent",
+  "Operating System :: POSIX",
+  "Operating System :: MacOS",
+  "Operating System :: POSIX :: Linux",
+  "Operating System :: Microsoft :: Windows",
+  "Topic :: Software Development :: Libraries :: Python Modules",
+  "License :: OSI Approved :: Apache Software License"
+]
+
+[project.urls]
+Homepage = "https://github.com/stainless-sdks/codex-python"
+Repository = "https://github.com/stainless-sdks/codex-python"
+
+
+
+[tool.rye]
+managed = true
+# version pins are in requirements-dev.lock
+dev-dependencies = [
+    "pyright>=1.1.359",
+    "mypy",
+    "respx",
+    "pytest",
+    "pytest-asyncio",
+    "ruff",
+    "time-machine",
+    "nox",
+    "dirty-equals>=0.6.0",
+    "importlib-metadata>=6.7.0",
+    "rich>=13.7.1",
+    "nest_asyncio==1.6.0",
+]
+
+[tool.rye.scripts]
+format = { chain = [
+  "format:ruff",
+  "format:docs",
+  "fix:ruff",
+  # run formatting again to fix any inconsistencies when imports are stripped
+  "format:ruff",
+]}
+"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
+"format:ruff" = "ruff format"
+
+"lint" = { chain = [
+  "check:ruff",
+  "typecheck",
+  "check:importable",
+]}
+"check:ruff" = "ruff check ."
+"fix:ruff" = "ruff check --fix ."
+
+"check:importable" = "python -c 'import codex'"
+
+typecheck = { chain = [
+  "typecheck:pyright",
+  "typecheck:mypy"
+]}
+"typecheck:pyright" = "pyright"
+"typecheck:verify-types" = "pyright --verifytypes codex --ignoreexternal"
+"typecheck:mypy" = "mypy ."
+
+[build-system]
+requires = ["hatchling", "hatch-fancy-pypi-readme"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build]
+include = [
+  "src/*"
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/codex"]
+
+[tool.hatch.build.targets.sdist]
+# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
+include = [
+  "/*.toml",
+  "/*.json",
+  "/*.lock",
+  "/*.md",
+  "/mypy.ini",
+  "/noxfile.py",
+  "bin/*",
+  "examples/*",
+  "src/*",
+  "tests/*",
+]
+
+[tool.hatch.metadata.hooks.fancy-pypi-readme]
+content-type = "text/markdown"
+
+[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
+path = "README.md"
+
+[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
+# replace relative links with absolute links
+pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
+replacement = '[\1](https://github.com/stainless-sdks/codex-python/tree/main/\g<2>)'
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+addopts = "--tb=short"
+xfail_strict = true
+asyncio_mode = "auto"
+filterwarnings = [
+  "error"
+]
+
+[tool.pyright]
+# this enables practically every flag given by pyright.
+# there are a couple of flags that are still disabled by
+# default in strict mode as they are experimental and niche.
+typeCheckingMode = "strict"
+pythonVersion = "3.8"
+
+exclude = [
+    "_dev",
+    ".venv",
+    ".nox",
+]
+
+reportImplicitOverride = true
+
+reportImportCycles = false
+reportPrivateUsage = false
+
+
+[tool.ruff]
+line-length = 120
+output-format = "grouped"
+target-version = "py37"
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.ruff.lint]
+select = [
+  # isort
+  "I",
+  # bugbear rules
+  "B",
+  # remove unused imports
+  "F401",
+  # bare except statements
+  "E722",
+  # unused arguments
+  "ARG",
+  # print statements
+  "T201",
+  "T203",
+  # misuse of typing.TYPE_CHECKING
+  "TCH004",
+  # import rules
+  "TID251",
+]
+ignore = [
+  # mutable defaults
+  "B006",
+]
+unfixable = [
+  # disable auto fix for print statements
+  "T201",
+  "T203",
+]
+
+[tool.ruff.lint.flake8-tidy-imports.banned-api]
+"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"
+
+[tool.ruff.lint.isort]
+length-sort = true
+length-sort-straight = true
+combine-as-imports = true
+extra-standard-library = ["typing_extensions"]
+known-first-party = ["codex", "tests"]
+
+[tool.ruff.lint.per-file-ignores]
+"bin/**.py" = ["T201", "T203"]
+"scripts/**.py" = ["T201", "T203"]
+"tests/**.py" = ["T201", "T203"]
+"examples/**.py" = ["T201", "T203"]
diff --git a/requirements-dev.lock b/requirements-dev.lock
new file mode 100644
index 00000000..11bf46e6
--- /dev/null
+++ b/requirements-dev.lock
@@ -0,0 +1,103 @@
+# generated by rye
+# use `rye lock` or `rye sync` to update this lockfile
+#
+# last locked with the following flags:
+#   pre: false
+#   features: []
+#   all-features: true
+#   with-sources: false
+#   generate-hashes: false
+
+-e file:.
+annotated-types==0.6.0
+    # via pydantic
+anyio==4.4.0
+    # via codex
+    # via httpx
+argcomplete==3.1.2
+    # via nox
+certifi==2023.7.22
+    # via httpcore
+    # via httpx
+colorlog==6.7.0
+    # via nox
+dirty-equals==0.6.0
+distlib==0.3.7
+    # via virtualenv
+distro==1.8.0
+    # via codex
+exceptiongroup==1.2.2
+    # via anyio
+    # via pytest
+filelock==3.12.4
+    # via virtualenv
+h11==0.14.0
+    # via httpcore
+httpcore==1.0.2
+    # via httpx
+httpx==0.28.1
+    # via codex
+    # via respx
+idna==3.4
+    # via anyio
+    # via httpx
+importlib-metadata==7.0.0
+iniconfig==2.0.0
+    # via pytest
+markdown-it-py==3.0.0
+    # via rich
+mdurl==0.1.2
+    # via markdown-it-py
+mypy==1.13.0
+mypy-extensions==1.0.0
+    # via mypy
+nest-asyncio==1.6.0
+nodeenv==1.8.0
+    # via pyright
+nox==2023.4.22
+packaging==23.2
+    # via nox
+    # via pytest
+platformdirs==3.11.0
+    # via virtualenv
+pluggy==1.5.0
+    # via pytest
+pydantic==2.10.3
+    # via codex
+pydantic-core==2.27.1
+    # via pydantic
+pygments==2.18.0
+    # via rich
+pyright==1.1.390
+pytest==8.3.3
+    # via pytest-asyncio
+pytest-asyncio==0.24.0
+python-dateutil==2.8.2
+    # via time-machine
+pytz==2023.3.post1
+    # via dirty-equals
+respx==0.22.0
+rich==13.7.1
+ruff==0.6.9
+setuptools==68.2.2
+    # via nodeenv
+six==1.16.0
+    # via python-dateutil
+sniffio==1.3.0
+    # via anyio
+    # via codex
+time-machine==2.9.0
+tomli==2.0.2
+    # via mypy
+    # via pytest
+typing-extensions==4.12.2
+    # via anyio
+    # via codex
+    # via mypy
+    # via pydantic
+    # via pydantic-core
+    # via pyright
+virtualenv==20.24.5
+    # via nox
+zipp==3.17.0
+    # via importlib-metadata
diff --git a/requirements.lock b/requirements.lock
new file mode 100644
index 00000000..fb7b045f
--- /dev/null
+++ b/requirements.lock
@@ -0,0 +1,44 @@
+# generated by rye
+# use `rye lock` or `rye sync` to update this lockfile
+#
+# last locked with the following flags:
+#   pre: false
+#   features: []
+#   all-features: true
+#   with-sources: false
+#   generate-hashes: false
+
+-e file:.
+annotated-types==0.6.0
+    # via pydantic
+anyio==4.4.0
+    # via codex
+    # via httpx
+certifi==2023.7.22
+    # via httpcore
+    # via httpx
+distro==1.8.0
+    # via codex
+exceptiongroup==1.2.2
+    # via anyio
+h11==0.14.0
+    # via httpcore
+httpcore==1.0.2
+    # via httpx
+httpx==0.28.1
+    # via codex
+idna==3.4
+    # via anyio
+    # via httpx
+pydantic==2.10.3
+    # via codex
+pydantic-core==2.27.1
+    # via pydantic
+sniffio==1.3.0
+    # via anyio
+    # via codex
+typing-extensions==4.12.2
+    # via anyio
+    # via codex
+    # via pydantic
+    # via pydantic-core
diff --git a/scripts/bootstrap b/scripts/bootstrap
new file mode 100755
index 00000000..8c5c60eb
--- /dev/null
+++ b/scripts/bootstrap
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
+  brew bundle check >/dev/null 2>&1 || {
+    echo "==> Installing Homebrew dependencies…"
+    brew bundle
+  }
+fi
+
+echo "==> Installing Python dependencies…"
+
+# experimental uv support makes installations significantly faster
+rye config --set-bool behavior.use-uv=true
+
+rye sync --all-features
diff --git a/scripts/format b/scripts/format
new file mode 100755
index 00000000..667ec2d7
--- /dev/null
+++ b/scripts/format
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running formatters"
+rye run format
diff --git a/scripts/lint b/scripts/lint
new file mode 100755
index 00000000..6f2bb0d0
--- /dev/null
+++ b/scripts/lint
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running lints"
+rye run lint
+
+echo "==> Making sure it imports"
+rye run python -c 'import codex'
+
diff --git a/scripts/mock b/scripts/mock
new file mode 100755
index 00000000..d2814ae6
--- /dev/null
+++ b/scripts/mock
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [[ -n "$1" && "$1" != '--'* ]]; then
+  URL="$1"
+  shift
+else
+  URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)"
+fi
+
+# Check if the URL is empty
+if [ -z "$URL" ]; then
+  echo "Error: No OpenAPI spec path/url provided or found in .stats.yml"
+  exit 1
+fi
+
+echo "==> Starting mock server with URL ${URL}"
+
+# Run prism mock on the given spec
+if [ "$1" == "--daemon" ]; then
+  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log &
+
+  # Wait for server to come online
+  echo -n "Waiting for server"
+  while ! grep -q "✖  fatal\|Prism is listening" ".prism.log" ; do
+    echo -n "."
+    sleep 0.1
+  done
+
+  if grep -q "✖  fatal" ".prism.log"; then
+    cat .prism.log
+    exit 1
+  fi
+
+  echo
+else
+  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL"
+fi
diff --git a/scripts/test b/scripts/test
new file mode 100755
index 00000000..4fa5698b
--- /dev/null
+++ b/scripts/test
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+NC='\033[0m' # No Color
+
+function prism_is_running() {
+  curl --silent "http://localhost:4010" >/dev/null 2>&1
+}
+
+kill_server_on_port() {
+  pids=$(lsof -t -i tcp:"$1" || echo "")
+  if [ "$pids" != "" ]; then
+    kill "$pids"
+    echo "Stopped $pids."
+  fi
+}
+
+function is_overriding_api_base_url() {
+  [ -n "$TEST_API_BASE_URL" ]
+}
+
+if ! is_overriding_api_base_url && ! prism_is_running ; then
+  # When we exit this script, make sure to kill the background mock server process
+  trap 'kill_server_on_port 4010' EXIT
+
+  # Start the dev server
+  ./scripts/mock --daemon
+fi
+
+if is_overriding_api_base_url ; then
+  echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}"
+  echo
+elif ! prism_is_running ; then
+  echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server"
+  echo -e "running against your OpenAPI spec."
+  echo
+  echo -e "To run the server, pass in the path or url of your OpenAPI"
+  echo -e "spec to the prism command:"
+  echo
+  echo -e "  \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
+  echo
+
+  exit 1
+else
+  echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}"
+  echo
+fi
+
+echo "==> Running tests"
+rye run pytest "$@"
+
+echo "==> Running Pydantic v1 tests"
+rye run nox -s test-pydantic-v1 -- "$@"
diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py
new file mode 100644
index 00000000..37b3d94f
--- /dev/null
+++ b/scripts/utils/ruffen-docs.py
@@ -0,0 +1,167 @@
+# fork of https://github.com/asottile/blacken-docs adapted for ruff
+from __future__ import annotations
+
+import re
+import sys
+import argparse
+import textwrap
+import contextlib
+import subprocess
+from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
+
+MD_RE = re.compile(
+    r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)",
+    re.DOTALL | re.MULTILINE,
+)
+MD_PYCON_RE = re.compile(
+    r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)",
+    re.DOTALL | re.MULTILINE,
+)
+PYCON_PREFIX = ">>> "
+PYCON_CONTINUATION_PREFIX = "..."
+PYCON_CONTINUATION_RE = re.compile(
+    rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
+)
+DEFAULT_LINE_LENGTH = 100
+
+
+class CodeBlockError(NamedTuple):
+    offset: int
+    exc: Exception
+
+
+def format_str(
+    src: str,
+) -> tuple[str, Sequence[CodeBlockError]]:
+    errors: list[CodeBlockError] = []
+
+    @contextlib.contextmanager
+    def _collect_error(match: Match[str]) -> Generator[None, None, None]:
+        try:
+            yield
+        except Exception as e:
+            errors.append(CodeBlockError(match.start(), e))
+
+    def _md_match(match: Match[str]) -> str:
+        code = textwrap.dedent(match["code"])
+        with _collect_error(match):
+            code = format_code_block(code)
+        code = textwrap.indent(code, match["indent"])
+        return f'{match["before"]}{code}{match["after"]}'
+
+    def _pycon_match(match: Match[str]) -> str:
+        code = ""
+        fragment = cast(Optional[str], None)
+
+        def finish_fragment() -> None:
+            nonlocal code
+            nonlocal fragment
+
+            if fragment is not None:
+                with _collect_error(match):
+                    fragment = format_code_block(fragment)
+                fragment_lines = fragment.splitlines()
+                code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
+                for line in fragment_lines[1:]:
+                    # Skip blank lines to handle Black adding a blank above
+                    # functions within blocks. A blank line would end the REPL
+                    # continuation prompt.
+                    #
+                    # >>> if True:
+                    # ...     def f():
+                    # ...         pass
+                    # ...
+                    if line:
+                        code += f"{PYCON_CONTINUATION_PREFIX} {line}\n"
+                if fragment_lines[-1].startswith(" "):
+                    code += f"{PYCON_CONTINUATION_PREFIX}\n"
+                fragment = None
+
+        indentation = None
+        for line in match["code"].splitlines():
+            orig_line, line = line, line.lstrip()
+            if indentation is None and line:
+                indentation = len(orig_line) - len(line)
+            continuation_match = PYCON_CONTINUATION_RE.match(line)
+            if continuation_match and fragment is not None:
+                fragment += line[continuation_match.end() :] + "\n"
+            else:
+                finish_fragment()
+                if line.startswith(PYCON_PREFIX):
+                    fragment = line[len(PYCON_PREFIX) :] + "\n"
+                else:
+                    code += orig_line[indentation:] + "\n"
+        finish_fragment()
+        return code
+
+    def _md_pycon_match(match: Match[str]) -> str:
+        code = _pycon_match(match)
+        code = textwrap.indent(code, match["indent"])
+        return f'{match["before"]}{code}{match["after"]}'
+
+    src = MD_RE.sub(_md_match, src)
+    src = MD_PYCON_RE.sub(_md_pycon_match, src)
+    return src, errors
+
+
+def format_code_block(code: str) -> str:
+    return subprocess.check_output(
+        [
+            sys.executable,
+            "-m",
+            "ruff",
+            "format",
+            "--stdin-filename=script.py",
+            f"--line-length={DEFAULT_LINE_LENGTH}",
+        ],
+        encoding="utf-8",
+        input=code,
+    )
+
+
+def format_file(
+    filename: str,
+    skip_errors: bool,
+) -> int:
+    with open(filename, encoding="UTF-8") as f:
+        contents = f.read()
+    new_contents, errors = format_str(contents)
+    for error in errors:
+        lineno = contents[: error.offset].count("\n") + 1
+        print(f"{filename}:{lineno}: code block parse error {error.exc}")
+    if errors and not skip_errors:
+        return 1
+    if contents != new_contents:
+        print(f"{filename}: Rewriting...")
+        with open(filename, "w", encoding="UTF-8") as f:
+            f.write(new_contents)
+        return 0
+    else:
+        return 0
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-l",
+        "--line-length",
+        type=int,
+        default=DEFAULT_LINE_LENGTH,
+    )
+    parser.add_argument(
+        "-S",
+        "--skip-string-normalization",
+        action="store_true",
+    )
+    parser.add_argument("-E", "--skip-errors", action="store_true")
+    parser.add_argument("filenames", nargs="*")
+    args = parser.parse_args(argv)
+
+    retv = 0
+    for filename in args.filenames:
+        retv |= format_file(filename, skip_errors=args.skip_errors)
+    return retv
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
new file mode 100644
index 00000000..bdcdb71a
--- /dev/null
+++ b/src/codex/__init__.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from . import types
+from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
+from ._utils import file_from_path
+from ._client import Codex, Client, Stream, Timeout, Transport, AsyncCodex, AsyncClient, AsyncStream, RequestOptions
+from ._models import BaseModel
+from ._version import __title__, __version__
+from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
+from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
+from ._exceptions import (
+    APIError,
+    CodexError,
+    ConflictError,
+    NotFoundError,
+    APIStatusError,
+    RateLimitError,
+    APITimeoutError,
+    BadRequestError,
+    APIConnectionError,
+    AuthenticationError,
+    InternalServerError,
+    PermissionDeniedError,
+    UnprocessableEntityError,
+    APIResponseValidationError,
+)
+from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
+from ._utils._logs import setup_logging as _setup_logging
+
+__all__ = [
+    "types",
+    "__version__",
+    "__title__",
+    "NoneType",
+    "Transport",
+    "ProxiesTypes",
+    "NotGiven",
+    "NOT_GIVEN",
+    "Omit",
+    "CodexError",
+    "APIError",
+    "APIStatusError",
+    "APITimeoutError",
+    "APIConnectionError",
+    "APIResponseValidationError",
+    "BadRequestError",
+    "AuthenticationError",
+    "PermissionDeniedError",
+    "NotFoundError",
+    "ConflictError",
+    "UnprocessableEntityError",
+    "RateLimitError",
+    "InternalServerError",
+    "Timeout",
+    "RequestOptions",
+    "Client",
+    "AsyncClient",
+    "Stream",
+    "AsyncStream",
+    "Codex",
+    "AsyncCodex",
+    "file_from_path",
+    "BaseModel",
+    "DEFAULT_TIMEOUT",
+    "DEFAULT_MAX_RETRIES",
+    "DEFAULT_CONNECTION_LIMITS",
+    "DefaultHttpxClient",
+    "DefaultAsyncHttpxClient",
+]
+
+_setup_logging()
+
+# Update the __module__ attribute for exported symbols so that
+# error messages point to this module instead of the module
+# it was originally defined in, e.g.
+# codex._exceptions.NotFoundError -> codex.NotFoundError
+__locals = locals()
+for __name in __all__:
+    if not __name.startswith("__"):
+        try:
+            __locals[__name].__module__ = "codex"
+        except (TypeError, AttributeError):
+            # Some of our exported symbols are builtins which we can't set attributes for.
+            pass
diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
new file mode 100644
index 00000000..9090e758
--- /dev/null
+++ b/src/codex/_base_client.py
@@ -0,0 +1,2051 @@
+from __future__ import annotations
+
+import sys
+import json
+import time
+import uuid
+import email
+import asyncio
+import inspect
+import logging
+import platform
+import warnings
+import email.utils
+from types import TracebackType
+from random import random
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Dict,
+    Type,
+    Union,
+    Generic,
+    Mapping,
+    TypeVar,
+    Iterable,
+    Iterator,
+    Optional,
+    Generator,
+    AsyncIterator,
+    cast,
+    overload,
+)
+from typing_extensions import Literal, override, get_origin
+
+import anyio
+import httpx
+import distro
+import pydantic
+from httpx import URL, Limits
+from pydantic import PrivateAttr
+
+from . import _exceptions
+from ._qs import Querystring
+from ._files import to_httpx_files, async_to_httpx_files
+from ._types import (
+    NOT_GIVEN,
+    Body,
+    Omit,
+    Query,
+    Headers,
+    Timeout,
+    NotGiven,
+    ResponseT,
+    Transport,
+    AnyMapping,
+    PostParser,
+    ProxiesTypes,
+    RequestFiles,
+    HttpxSendArgs,
+    AsyncTransport,
+    RequestOptions,
+    HttpxRequestFiles,
+    ModelBuilderProtocol,
+)
+from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
+from ._compat import model_copy, model_dump
+from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
+from ._response import (
+    APIResponse,
+    BaseAPIResponse,
+    AsyncAPIResponse,
+    extract_response_type,
+)
+from ._constants import (
+    DEFAULT_TIMEOUT,
+    MAX_RETRY_DELAY,
+    DEFAULT_MAX_RETRIES,
+    INITIAL_RETRY_DELAY,
+    RAW_RESPONSE_HEADER,
+    OVERRIDE_CAST_TO_HEADER,
+    DEFAULT_CONNECTION_LIMITS,
+)
+from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
+from ._exceptions import (
+    APIStatusError,
+    APITimeoutError,
+    APIConnectionError,
+    APIResponseValidationError,
+)
+
+log: logging.Logger = logging.getLogger(__name__)
+
+# TODO: make base page type vars covariant
+SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
+AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")
+
+
+_T = TypeVar("_T")
+_T_co = TypeVar("_T_co", covariant=True)
+
+_StreamT = TypeVar("_StreamT", bound=Stream[Any])
+_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
+
+if TYPE_CHECKING:
+    from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+else:
+    try:
+        from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+    except ImportError:
+        # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
+        HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)
+
+
+class PageInfo:
+    """Stores the necessary information to build the request to retrieve the next page.
+
+    Either `url` or `params` must be set.
+    """
+
+    url: URL | NotGiven
+    params: Query | NotGiven
+
+    @overload
+    def __init__(
+        self,
+        *,
+        url: URL,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self,
+        *,
+        params: Query,
+    ) -> None: ...
+
+    def __init__(
+        self,
+        *,
+        url: URL | NotGiven = NOT_GIVEN,
+        params: Query | NotGiven = NOT_GIVEN,
+    ) -> None:
+        self.url = url
+        self.params = params
+
+    @override
+    def __repr__(self) -> str:
+        if self.url:
+            return f"{self.__class__.__name__}(url={self.url})"
+        return f"{self.__class__.__name__}(params={self.params})"
+
+
+class BasePage(GenericModel, Generic[_T]):
+    """
+    Defines the core interface for pagination.
+
+    Type Args:
+        ModelT: The pydantic model that represents an item in the response.
+
+    Methods:
+        has_next_page(): Check if there is another page available
+        next_page_info(): Get the necessary information to make a request for the next page
+    """
+
+    _options: FinalRequestOptions = PrivateAttr()
+    _model: Type[_T] = PrivateAttr()
+
+    def has_next_page(self) -> bool:
+        items = self._get_page_items()
+        if not items:
+            return False
+        return self.next_page_info() is not None
+
+    def next_page_info(self) -> Optional[PageInfo]: ...
+
+    def _get_page_items(self) -> Iterable[_T]:  # type: ignore[empty-body]
+        ...
+
+    def _params_from_url(self, url: URL) -> httpx.QueryParams:
+        # TODO: do we have to preprocess params here?
+        return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)
+
+    def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
+        options = model_copy(self._options)
+        options._strip_raw_response_header()
+
+        if not isinstance(info.params, NotGiven):
+            options.params = {**options.params, **info.params}
+            return options
+
+        if not isinstance(info.url, NotGiven):
+            params = self._params_from_url(info.url)
+            url = info.url.copy_with(params=params)
+            options.params = dict(url.params)
+            options.url = str(url)
+            return options
+
+        raise ValueError("Unexpected PageInfo state")
+
+
+class BaseSyncPage(BasePage[_T], Generic[_T]):
+    _client: SyncAPIClient = pydantic.PrivateAttr()
+
+    def _set_private_attributes(
+        self,
+        client: SyncAPIClient,
+        model: Type[_T],
+        options: FinalRequestOptions,
+    ) -> None:
+        self._model = model
+        self._client = client
+        self._options = options
+
+    # Pydantic uses a custom `__iter__` method to support casting BaseModels
+    # to dictionaries. e.g. dict(model).
+    # As we want to support `for item in page`, this is inherently incompatible
+    # with the default pydantic behaviour. It is not possible to support both
+    # use cases at once. Fortunately, this is not a big deal as all other pydantic
+    # methods should continue to work as expected as there is an alternative method
+    # to cast a model to a dictionary, model.dict(), which is used internally
+    # by pydantic.
+    def __iter__(self) -> Iterator[_T]:  # type: ignore
+        for page in self.iter_pages():
+            for item in page._get_page_items():
+                yield item
+
+    def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]:
+        page = self
+        while True:
+            yield page
+            if page.has_next_page():
+                page = page.get_next_page()
+            else:
+                return
+
+    def get_next_page(self: SyncPageT) -> SyncPageT:
+        info = self.next_page_info()
+        if not info:
+            raise RuntimeError(
+                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+            )
+
+        options = self._info_to_options(info)
+        return self._client._request_api_list(self._model, page=self.__class__, options=options)
+
+
+class AsyncPaginator(Generic[_T, AsyncPageT]):
+    def __init__(
+        self,
+        client: AsyncAPIClient,
+        options: FinalRequestOptions,
+        page_cls: Type[AsyncPageT],
+        model: Type[_T],
+    ) -> None:
+        self._model = model
+        self._client = client
+        self._options = options
+        self._page_cls = page_cls
+
+    def __await__(self) -> Generator[Any, None, AsyncPageT]:
+        return self._get_page().__await__()
+
+    async def _get_page(self) -> AsyncPageT:
+        def _parser(resp: AsyncPageT) -> AsyncPageT:
+            resp._set_private_attributes(
+                model=self._model,
+                options=self._options,
+                client=self._client,
+            )
+            return resp
+
+        self._options.post_parser = _parser
+
+        return await self._client.request(self._page_cls, self._options)
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        # https://github.com/microsoft/pyright/issues/3464
+        page = cast(
+            AsyncPageT,
+            await self,  # type: ignore
+        )
+        async for item in page:
+            yield item
+
+
+class BaseAsyncPage(BasePage[_T], Generic[_T]):
+    _client: AsyncAPIClient = pydantic.PrivateAttr()
+
+    def _set_private_attributes(
+        self,
+        model: Type[_T],
+        client: AsyncAPIClient,
+        options: FinalRequestOptions,
+    ) -> None:
+        self._model = model
+        self._client = client
+        self._options = options
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        async for page in self.iter_pages():
+            for item in page._get_page_items():
+                yield item
+
+    async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
+        page = self
+        while True:
+            yield page
+            if page.has_next_page():
+                page = await page.get_next_page()
+            else:
+                return
+
+    async def get_next_page(self: AsyncPageT) -> AsyncPageT:
+        info = self.next_page_info()
+        if not info:
+            raise RuntimeError(
+                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+            )
+
+        options = self._info_to_options(info)
+        return await self._client._request_api_list(self._model, page=self.__class__, options=options)
+
+
+_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
+_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])
+
+
+class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
+    _client: _HttpxClientT
+    _version: str
+    _base_url: URL
+    max_retries: int
+    timeout: Union[float, Timeout, None]
+    _limits: httpx.Limits
+    _proxies: ProxiesTypes | None
+    _transport: Transport | AsyncTransport | None
+    _strict_response_validation: bool
+    _idempotency_header: str | None
+    _default_stream_cls: type[_DefaultStreamT] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        _strict_response_validation: bool,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None = DEFAULT_TIMEOUT,
+        limits: httpx.Limits,
+        transport: Transport | AsyncTransport | None,
+        proxies: ProxiesTypes | None,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+    ) -> None:
+        self._version = version
+        self._base_url = self._enforce_trailing_slash(URL(base_url))
+        self.max_retries = max_retries
+        self.timeout = timeout
+        self._limits = limits
+        self._proxies = proxies
+        self._transport = transport
+        self._custom_headers = custom_headers or {}
+        self._custom_query = custom_query or {}
+        self._strict_response_validation = _strict_response_validation
+        self._idempotency_header = None
+        self._platform: Platform | None = None
+
+        if max_retries is None:  # pyright: ignore[reportUnnecessaryComparison]
+            raise TypeError(
+                "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `codex.DEFAULT_MAX_RETRIES`"
+            )
+
+    def _enforce_trailing_slash(self, url: URL) -> URL:
+        if url.raw_path.endswith(b"/"):
+            return url
+        return url.copy_with(raw_path=url.raw_path + b"/")
+
+    def _make_status_error_from_response(
+        self,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.is_closed and not response.is_stream_consumed:
+            # We can't read the response body as it has been closed
+            # before it was read. This can happen if an event hook
+            # raises a status error.
+            body = None
+            err_msg = f"Error code: {response.status_code}"
+        else:
+            err_text = response.text.strip()
+            body = err_text
+
+            try:
+                body = json.loads(err_text)
+                err_msg = f"Error code: {response.status_code} - {body}"
+            except Exception:
+                err_msg = err_text or f"Error code: {response.status_code}"
+
+        return self._make_status_error(err_msg, body=body, response=response)
+
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> _exceptions.APIStatusError:
+        raise NotImplementedError()
+
+    def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
+        custom_headers = options.headers or {}
+        headers_dict = _merge_mappings(self.default_headers, custom_headers)
+        self._validate_headers(headers_dict, custom_headers)
+
+        # headers are case-insensitive while dictionaries are not.
+        headers = httpx.Headers(headers_dict)
+
+        idempotency_header = self._idempotency_header
+        if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
+            headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
+
+        # Don't set the retry count header if it was already set or removed by the caller. We check
+        # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
+        if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
+            headers["x-stainless-retry-count"] = str(retries_taken)
+
+        return headers
+
+    def _prepare_url(self, url: str) -> URL:
+        """
+        Merge a URL argument together with any 'base_url' on the client,
+        to create the URL used for the outgoing request.
+        """
+        # Copied from httpx's `_merge_url` method.
+        merge_url = URL(url)
+        if merge_url.is_relative_url:
+            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
+            return self.base_url.copy_with(raw_path=merge_raw_path)
+
+        return merge_url
+
+    def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
+        return SSEDecoder()
+
+    def _build_request(
+        self,
+        options: FinalRequestOptions,
+        *,
+        retries_taken: int = 0,
+    ) -> httpx.Request:
+        if log.isEnabledFor(logging.DEBUG):
+            log.debug("Request options: %s", model_dump(options, exclude_unset=True))
+
+        kwargs: dict[str, Any] = {}
+
+        json_data = options.json_data
+        if options.extra_json is not None:
+            if json_data is None:
+                json_data = cast(Body, options.extra_json)
+            elif is_mapping(json_data):
+                json_data = _merge_mappings(json_data, options.extra_json)
+            else:
+                raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
+
+        headers = self._build_headers(options, retries_taken=retries_taken)
+        params = _merge_mappings(self.default_query, options.params)
+        content_type = headers.get("Content-Type")
+        files = options.files
+
+        # If the given Content-Type header is multipart/form-data then it
+        # has to be removed so that httpx can generate the header with
+        # additional information for us as it has to be in this form
+        # for the server to be able to correctly parse the request:
+        # multipart/form-data; boundary=---abc--
+        if content_type is not None and content_type.startswith("multipart/form-data"):
+            if "boundary" not in content_type:
+                # only remove the header if the boundary hasn't been explicitly set
+                # as the caller doesn't want httpx to come up with their own boundary
+                headers.pop("Content-Type")
+
+            # As we are now sending multipart/form-data instead of application/json
+            # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
+            if json_data:
+                if not is_dict(json_data):
+                    raise TypeError(
+                        f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
+                    )
+                kwargs["data"] = self._serialize_multipartform(json_data)
+
+            # httpx determines whether or not to send a "multipart/form-data"
+            # request based on the truthiness of the "files" argument.
+            # This gets around that issue by generating a dict value that
+            # evaluates to true.
+            #
+            # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
+            if not files:
+                files = cast(HttpxRequestFiles, ForceMultipartDict())
+
+        prepared_url = self._prepare_url(options.url)
+        if "_" in prepared_url.host:
+            # work around https://github.com/encode/httpx/discussions/2880
+            kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
+
+        # TODO: report this error to httpx
+        return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
+            headers=headers,
+            timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
+            method=options.method,
+            url=prepared_url,
+            # the `Query` type that we use is incompatible with qs'
+            # `Params` type as it needs to be typed as `Mapping[str, object]`
+            # so that passing a `TypedDict` doesn't cause an error.
+            # https://github.com/microsoft/pyright/issues/3526#event-6715453066
+            params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
+            json=json_data,
+            files=files,
+            **kwargs,
+        )
+
+    def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
+        items = self.qs.stringify_items(
+            # TODO: type ignore is required as stringify_items is well typed but we can't be
+            # well typed without heavy validation.
+            data,  # type: ignore
+            array_format="brackets",
+        )
+        serialized: dict[str, object] = {}
+        for key, value in items:
+            existing = serialized.get(key)
+
+            if not existing:
+                serialized[key] = value
+                continue
+
+            # If a value has already been set for this key then that
+            # means we're sending data like `array[]=[1, 2, 3]` and we
+            # need to tell httpx that we want to send multiple values with
+            # the same key which is done by using a list or a tuple.
+            #
+            # Note: 2d arrays should never result in the same key at both
+            # levels so it's safe to assume that if the value is a list,
+            # it was because we changed it to be a list.
+            if is_list(existing):
+                existing.append(value)
+            else:
+                serialized[key] = [existing, value]
+
+        return serialized
+
+    def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]:
+        if not is_given(options.headers):
+            return cast_to
+
+        # make a copy of the headers so we don't mutate user-input
+        headers = dict(options.headers)
+
+        # we internally support defining a temporary header to override the
+        # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
+        # see _response.py for implementation details
+        override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN)
+        if is_given(override_cast_to):
+            options.headers = headers
+            return cast(Type[ResponseT], override_cast_to)
+
+        return cast_to
+
+    def _should_stream_response_body(self, request: httpx.Request) -> bool:
+        return request.headers.get(RAW_RESPONSE_HEADER) == "stream"  # type: ignore[no-any-return]
+
+    def _process_response_data(
+        self,
+        *,
+        data: object,
+        cast_to: type[ResponseT],
+        response: httpx.Response,
+    ) -> ResponseT:
+        if data is None:
+            return cast(ResponseT, None)
+
+        if cast_to is object:
+            return cast(ResponseT, data)
+
+        try:
+            if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
+                return cast(ResponseT, cast_to.build(response=response, data=data))
+
+            if self._strict_response_validation:
+                return cast(ResponseT, validate_type(type_=cast_to, value=data))
+
+            return cast(ResponseT, construct_type(type_=cast_to, value=data))
+        except pydantic.ValidationError as err:
+            raise APIResponseValidationError(response=response, body=data) from err
+
+    @property
+    def qs(self) -> Querystring:
+        return Querystring()
+
+    @property
+    def custom_auth(self) -> httpx.Auth | None:
+        return None
+
+    @property
+    def auth_headers(self) -> dict[str, str]:
+        return {}
+
+    @property
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            "Accept": "application/json",
+            "Content-Type": "application/json",
+            "User-Agent": self.user_agent,
+            **self.platform_headers(),
+            **self.auth_headers,
+            **self._custom_headers,
+        }
+
+    @property
+    def default_query(self) -> dict[str, object]:
+        return {
+            **self._custom_query,
+        }
+
+    def _validate_headers(
+        self,
+        headers: Headers,  # noqa: ARG002
+        custom_headers: Headers,  # noqa: ARG002
+    ) -> None:
+        """Validate the given default headers and custom headers.
+
+        Does nothing by default.
+        """
+        return
+
+    @property
+    def user_agent(self) -> str:
+        return f"{self.__class__.__name__}/Python {self._version}"
+
+    @property
+    def base_url(self) -> URL:
+        return self._base_url
+
+    @base_url.setter
+    def base_url(self, url: URL | str) -> None:
+        self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))
+
+    def platform_headers(self) -> Dict[str, str]:
+        # the actual implementation is in a separate `lru_cache` decorated
+        # function because adding `lru_cache` to methods will leak memory
+        # https://github.com/python/cpython/issues/88476
+        return platform_headers(self._version, platform=self._platform)
+
+    def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
+        """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
+
+        About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
+        See also  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
+        """
+        if response_headers is None:
+            return None
+
+        # First, try the non-standard `retry-after-ms` header for milliseconds,
+        # which is more precise than integer-seconds `retry-after`
+        try:
+            retry_ms_header = response_headers.get("retry-after-ms", None)
+            return float(retry_ms_header) / 1000
+        except (TypeError, ValueError):
+            pass
+
+        # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
+        retry_header = response_headers.get("retry-after")
+        try:
+            # note: the spec indicates that this should only ever be an integer
+            # but if someone sends a float there's no reason for us to not respect it
+            return float(retry_header)
+        except (TypeError, ValueError):
+            pass
+
+        # Last, try parsing `retry-after` as a date.
+        retry_date_tuple = email.utils.parsedate_tz(retry_header)
+        if retry_date_tuple is None:
+            return None
+
+        retry_date = email.utils.mktime_tz(retry_date_tuple)
+        return float(retry_date - time.time())
+
+    def _calculate_retry_timeout(
+        self,
+        remaining_retries: int,
+        options: FinalRequestOptions,
+        response_headers: Optional[httpx.Headers] = None,
+    ) -> float:
+        max_retries = options.get_max_retries(self.max_retries)
+
+        # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
+        retry_after = self._parse_retry_after_header(response_headers)
+        if retry_after is not None and 0 < retry_after <= 60:
+            return retry_after
+
+        # Also cap retry count to 1000 to avoid any potential overflows with `pow`
+        nb_retries = min(max_retries - remaining_retries, 1000)
+
+        # Apply exponential backoff, but not more than the max.
+        sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)
+
+        # Apply some jitter, plus-or-minus half a second.
+        jitter = 1 - 0.25 * random()
+        timeout = sleep_seconds * jitter
+        return timeout if timeout >= 0 else 0
+
+    def _should_retry(self, response: httpx.Response) -> bool:
+        # Note: this is not a standard header
+        should_retry_header = response.headers.get("x-should-retry")
+
+        # If the server explicitly says whether or not to retry, obey.
+        if should_retry_header == "true":
+            log.debug("Retrying as header `x-should-retry` is set to `true`")
+            return True
+        if should_retry_header == "false":
+            log.debug("Not retrying as header `x-should-retry` is set to `false`")
+            return False
+
+        # Retry on request timeouts.
+        if response.status_code == 408:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry on lock timeouts.
+        if response.status_code == 409:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry on rate limits.
+        if response.status_code == 429:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        # Retry internal errors.
+        if response.status_code >= 500:
+            log.debug("Retrying due to status code %i", response.status_code)
+            return True
+
+        log.debug("Not retrying")
+        return False
+
+    def _idempotency_key(self) -> str:
+        return f"stainless-python-retry-{uuid.uuid4()}"
+
+
+class _DefaultHttpxClient(httpx.Client):
+    def __init__(self, **kwargs: Any) -> None:
+        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+        kwargs.setdefault("follow_redirects", True)
+        super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+    DefaultHttpxClient = httpx.Client
+    """An alias to `httpx.Client` that provides the same defaults that this SDK
+    uses internally.
+
+    This is useful because overriding the `http_client` with your own instance of
+    `httpx.Client` will result in httpx's defaults being used, not ours.
+    """
+else:
+    DefaultHttpxClient = _DefaultHttpxClient
+
+
+class SyncHttpxClientWrapper(DefaultHttpxClient):
+    def __del__(self) -> None:
+        if self.is_closed:
+            return
+
+        try:
+            self.close()
+        except Exception:
+            pass
+
+
+class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
+    _client: httpx.Client
+    _default_stream_cls: type[Stream[Any]] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        transport: Transport | None = None,
+        proxies: ProxiesTypes | None = None,
+        limits: Limits | None = None,
+        http_client: httpx.Client | None = None,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+        _strict_response_validation: bool,
+    ) -> None:
+        kwargs: dict[str, Any] = {}
+        if limits is not None:
+            warnings.warn(
+                "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
+        else:
+            limits = DEFAULT_CONNECTION_LIMITS
+
+        if transport is not None:
+            kwargs["transport"] = transport
+            warnings.warn(
+                "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
+
+        if proxies is not None:
+            kwargs["proxies"] = proxies
+            warnings.warn(
+                "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
+
+        if not is_given(timeout):
+            # if the user passed in a custom http client with a non-default
+            # timeout set then we use that timeout.
+            #
+            # note: there is an edge case here where the user passes in a client
+            # where they've explicitly set the timeout to match the default timeout
+            # as this check is structural, meaning that we'll think they didn't
+            # pass in a timeout and will ignore it
+            if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+                timeout = http_client.timeout
+            else:
+                timeout = DEFAULT_TIMEOUT
+
+        if http_client is not None and not isinstance(http_client, httpx.Client):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(
+                f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}"
+            )
+
+        super().__init__(
+            version=version,
+            limits=limits,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            proxies=proxies,
+            base_url=base_url,
+            transport=transport,
+            max_retries=max_retries,
+            custom_query=custom_query,
+            custom_headers=custom_headers,
+            _strict_response_validation=_strict_response_validation,
+        )
+        self._client = http_client or SyncHttpxClientWrapper(
+            base_url=base_url,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            limits=limits,
+            follow_redirects=True,
+            **kwargs,  # type: ignore
+        )
+
+    def is_closed(self) -> bool:
+        return self._client.is_closed
+
+    def close(self) -> None:
+        """Close the underlying HTTPX client.
+
+        The client will *not* be usable after this.
+        """
+        # If an error is thrown while constructing a client, self._client
+        # may not be present
+        if hasattr(self, "_client"):
+            self._client.close()
+
+    def __enter__(self: _T) -> _T:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def _prepare_options(
+        self,
+        options: FinalRequestOptions,  # noqa: ARG002
+    ) -> FinalRequestOptions:
+        """Hook for mutating the given options"""
+        return options
+
+    def _prepare_request(
+        self,
+        request: httpx.Request,  # noqa: ARG002
+    ) -> None:
+        """This method is used as a callback for mutating the `Request` object
+        after it has been constructed.
+        This is useful for cases where you want to add certain headers based off of
+        the request properties, e.g. `url`, `method` etc.
+        """
+        return None
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        remaining_retries: Optional[int] = None,
+        *,
+        stream: Literal[True],
+        stream_cls: Type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        remaining_retries: Optional[int] = None,
+        *,
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        remaining_retries: Optional[int] = None,
+        *,
+        stream: bool = False,
+        stream_cls: Type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        remaining_retries: Optional[int] = None,
+        *,
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        if remaining_retries is not None:
+            retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
+        else:
+            retries_taken = 0
+
+        return self._request(
+            cast_to=cast_to,
+            options=options,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    def _request(
+        self,
+        *,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        retries_taken: int,
+        stream: bool,
+        stream_cls: type[_StreamT] | None,
+    ) -> ResponseT | _StreamT:
+        # create a copy of the options we were given so that if the
+        # options are mutated later & we then retry, the retries are
+        # given the original options
+        input_options = model_copy(options)
+
+        cast_to = self._maybe_override_cast_to(cast_to, options)
+        options = self._prepare_options(options)
+
+        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+        request = self._build_request(options, retries_taken=retries_taken)
+        self._prepare_request(request)
+
+        kwargs: HttpxSendArgs = {}
+        if self.custom_auth is not None:
+            kwargs["auth"] = self.custom_auth
+
+        log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+
+        try:
+            response = self._client.send(
+                request,
+                stream=stream or self._should_stream_response_body(request=request),
+                **kwargs,
+            )
+        except httpx.TimeoutException as err:
+            log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+            if remaining_retries > 0:
+                return self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    response_headers=None,
+                )
+
+            log.debug("Raising timeout error")
+            raise APITimeoutError(request=request) from err
+        except Exception as err:
+            log.debug("Encountered Exception", exc_info=True)
+
+            if remaining_retries > 0:
+                return self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    response_headers=None,
+                )
+
+            log.debug("Raising connection error")
+            raise APIConnectionError(request=request) from err
+
+        log.debug(
+            'HTTP Response: %s %s "%i %s" %s',
+            request.method,
+            request.url,
+            response.status_code,
+            response.reason_phrase,
+            response.headers,
+        )
+
+        try:
+            response.raise_for_status()
+        except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+            log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+            if remaining_retries > 0 and self._should_retry(err.response):
+                err.response.close()
+                return self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    response_headers=err.response.headers,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                )
+
+            # If the response is streamed then we need to explicitly read the response
+            # to completion before attempting to access the response text.
+            if not err.response.is_closed:
+                err.response.read()
+
+            log.debug("Re-raising status error")
+            raise self._make_status_error_from_response(err.response) from None
+
+        return self._process_response(
+            cast_to=cast_to,
+            options=options,
+            response=response,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    def _retry_request(
+        self,
+        options: FinalRequestOptions,
+        cast_to: Type[ResponseT],
+        *,
+        retries_taken: int,
+        response_headers: httpx.Headers | None,
+        stream: bool,
+        stream_cls: type[_StreamT] | None,
+    ) -> ResponseT | _StreamT:
+        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+        if remaining_retries == 1:
+            log.debug("1 retry left")
+        else:
+            log.debug("%i retries left", remaining_retries)
+
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+        log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+        # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
+        # different thread if necessary.
+        time.sleep(timeout)
+
+        return self._request(
+            options=options,
+            cast_to=cast_to,
+            retries_taken=retries_taken + 1,
+            stream=stream,
+            stream_cls=stream_cls,
+        )
+
+    def _process_response(
+        self,
+        *,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        response: httpx.Response,
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        retries_taken: int = 0,
+    ) -> ResponseT:
+        origin = get_origin(cast_to) or cast_to
+
+        if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
+            if not issubclass(origin, APIResponse):
+                raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
+
+            response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+            return cast(
+                ResponseT,
+                response_cls(
+                    raw=response,
+                    client=self,
+                    cast_to=extract_response_type(response_cls),
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    options=options,
+                    retries_taken=retries_taken,
+                ),
+            )
+
+        if cast_to == httpx.Response:
+            return cast(ResponseT, response)
+
+        api_response = APIResponse(
+            raw=response,
+            client=self,
+            cast_to=cast("type[ResponseT]", cast_to),  # pyright: ignore[reportUnnecessaryCast]
+            stream=stream,
+            stream_cls=stream_cls,
+            options=options,
+            retries_taken=retries_taken,
+        )
+        if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+            return cast(ResponseT, api_response)
+
+        return api_response.parse()
+
+    def _request_api_list(
+        self,
+        model: Type[object],
+        page: Type[SyncPageT],
+        options: FinalRequestOptions,
+    ) -> SyncPageT:
+        def _parser(resp: SyncPageT) -> SyncPageT:
+            resp._set_private_attributes(
+                client=self,
+                model=model,
+                options=options,
+            )
+            return resp
+
+        options.post_parser = _parser
+
+        return self.request(page, options, stream=False)
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        opts = FinalRequestOptions.construct(method="get", url=path, **options)
+        # cast is required because mypy complains about returning Any even though
+        # it understands the type variables
+        return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: Literal[True],
+        stream_cls: type[_StreamT],
+    ) -> _StreamT: ...
+
+    @overload
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: bool,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT: ...
+
+    def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        files: RequestFiles | None = None,
+        stream: bool = False,
+        stream_cls: type[_StreamT] | None = None,
+    ) -> ResponseT | _StreamT:
+        opts = FinalRequestOptions.construct(
+            method="post", url=path, json_data=body, files=to_httpx_files(files), **options
+        )
+        return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
+
+    def patch(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+        return self.request(cast_to, opts)
+
+    def put(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(
+            method="put", url=path, json_data=body, files=to_httpx_files(files), **options
+        )
+        return self.request(cast_to, opts)
+
+    def delete(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+        return self.request(cast_to, opts)
+
+    def get_api_list(
+        self,
+        path: str,
+        *,
+        model: Type[object],
+        page: Type[SyncPageT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        method: str = "get",
+    ) -> SyncPageT:
+        opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+        return self._request_api_list(model, page, opts)
+
+
+class _DefaultAsyncHttpxClient(httpx.AsyncClient):
+    def __init__(self, **kwargs: Any) -> None:
+        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+        kwargs.setdefault("follow_redirects", True)
+        super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+    DefaultAsyncHttpxClient = httpx.AsyncClient
+    """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
+    uses internally.
+
+    This is useful because overriding the `http_client` with your own instance of
+    `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
+    """
+else:
+    DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
+
+
+class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
+    def __del__(self) -> None:
+        if self.is_closed:
+            return
+
+        try:
+            # TODO(someday): support non asyncio runtimes here
+            asyncio.get_running_loop().create_task(self.aclose())
+        except Exception:
+            pass
+
+
+class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
+    _client: httpx.AsyncClient
+    _default_stream_cls: type[AsyncStream[Any]] | None = None
+
+    def __init__(
+        self,
+        *,
+        version: str,
+        base_url: str | URL,
+        _strict_response_validation: bool,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        transport: AsyncTransport | None = None,
+        proxies: ProxiesTypes | None = None,
+        limits: Limits | None = None,
+        http_client: httpx.AsyncClient | None = None,
+        custom_headers: Mapping[str, str] | None = None,
+        custom_query: Mapping[str, object] | None = None,
+    ) -> None:
+        kwargs: dict[str, Any] = {}
+        if limits is not None:
+            warnings.warn(
+                "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
+        else:
+            limits = DEFAULT_CONNECTION_LIMITS
+
+        if transport is not None:
+            kwargs["transport"] = transport
+            warnings.warn(
+                "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
+
+        if proxies is not None:
+            kwargs["proxies"] = proxies
+            warnings.warn(
+                "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            if http_client is not None:
+                raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
+
+        if not is_given(timeout):
+            # if the user passed in a custom http client with a non-default
+            # timeout set then we use that timeout.
+            #
+            # note: there is an edge case here where the user passes in a client
+            # where they've explicitly set the timeout to match the default timeout
+            # as this check is structural, meaning that we'll think they didn't
+            # pass in a timeout and will ignore it
+            if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+                timeout = http_client.timeout
+            else:
+                timeout = DEFAULT_TIMEOUT
+
+        if http_client is not None and not isinstance(http_client, httpx.AsyncClient):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(
+                f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}"
+            )
+
+        super().__init__(
+            version=version,
+            base_url=base_url,
+            limits=limits,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            proxies=proxies,
+            transport=transport,
+            max_retries=max_retries,
+            custom_query=custom_query,
+            custom_headers=custom_headers,
+            _strict_response_validation=_strict_response_validation,
+        )
+        self._client = http_client or AsyncHttpxClientWrapper(
+            base_url=base_url,
+            # cast to a valid type because mypy doesn't understand our type narrowing
+            timeout=cast(Timeout, timeout),
+            limits=limits,
+            follow_redirects=True,
+            **kwargs,  # type: ignore
+        )
+
+    def is_closed(self) -> bool:
+        return self._client.is_closed
+
+    async def close(self) -> None:
+        """Close the underlying HTTPX client.
+
+        The client will *not* be usable after this.
+        """
+        await self._client.aclose()
+
+    async def __aenter__(self: _T) -> _T:
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.close()
+
+    async def _prepare_options(
+        self,
+        options: FinalRequestOptions,  # noqa: ARG002
+    ) -> FinalRequestOptions:
+        """Hook for mutating the given options"""
+        return options
+
+    async def _prepare_request(
+        self,
+        request: httpx.Request,  # noqa: ARG002
+    ) -> None:
+        """This method is used as a callback for mutating the `Request` object
+        after it has been constructed.
+        This is useful for cases where you want to add certain headers based off of
+        the request properties, e.g. `url`, `method` etc.
+        """
+        return None
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[False] = False,
+        remaining_retries: Optional[int] = None,
+    ) -> ResponseT: ...
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+        remaining_retries: Optional[int] = None,
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+        remaining_retries: Optional[int] = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+        remaining_retries: Optional[int] = None,
+    ) -> ResponseT | _AsyncStreamT:
+        if remaining_retries is not None:
+            retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
+        else:
+            retries_taken = 0
+
+        return await self._request(
+            cast_to=cast_to,
+            options=options,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    async def _request(
+        self,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        *,
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None,
+        retries_taken: int,
+    ) -> ResponseT | _AsyncStreamT:
+        if self._platform is None:
+            # `get_platform` can make blocking IO calls so we
+            # execute it earlier while we are in an async context
+            self._platform = await asyncify(get_platform)()
+
+        # create a copy of the options we were given so that if the
+        # options are mutated later & we then retry, the retries are
+        # given the original options
+        input_options = model_copy(options)
+
+        cast_to = self._maybe_override_cast_to(cast_to, options)
+        options = await self._prepare_options(options)
+
+        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+        request = self._build_request(options, retries_taken=retries_taken)
+        await self._prepare_request(request)
+
+        kwargs: HttpxSendArgs = {}
+        if self.custom_auth is not None:
+            kwargs["auth"] = self.custom_auth
+
+        try:
+            response = await self._client.send(
+                request,
+                stream=stream or self._should_stream_response_body(request=request),
+                **kwargs,
+            )
+        except httpx.TimeoutException as err:
+            log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+            if remaining_retries > 0:
+                return await self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    response_headers=None,
+                )
+
+            log.debug("Raising timeout error")
+            raise APITimeoutError(request=request) from err
+        except Exception as err:
+            log.debug("Encountered Exception", exc_info=True)
+
+            if remaining_retries > 0:
+                return await self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    response_headers=None,
+                )
+
+            log.debug("Raising connection error")
+            raise APIConnectionError(request=request) from err
+
+        log.debug(
+            'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
+        )
+
+        try:
+            response.raise_for_status()
+        except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+            log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+            if remaining_retries > 0 and self._should_retry(err.response):
+                await err.response.aclose()
+                return await self._retry_request(
+                    input_options,
+                    cast_to,
+                    retries_taken=retries_taken,
+                    response_headers=err.response.headers,
+                    stream=stream,
+                    stream_cls=stream_cls,
+                )
+
+            # If the response is streamed then we need to explicitly read the response
+            # to completion before attempting to access the response text.
+            if not err.response.is_closed:
+                await err.response.aread()
+
+            log.debug("Re-raising status error")
+            raise self._make_status_error_from_response(err.response) from None
+
+        return await self._process_response(
+            cast_to=cast_to,
+            options=options,
+            response=response,
+            stream=stream,
+            stream_cls=stream_cls,
+            retries_taken=retries_taken,
+        )
+
+    async def _retry_request(
+        self,
+        options: FinalRequestOptions,
+        cast_to: Type[ResponseT],
+        *,
+        retries_taken: int,
+        response_headers: httpx.Headers | None,
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None,
+    ) -> ResponseT | _AsyncStreamT:
+        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+        if remaining_retries == 1:
+            log.debug("1 retry left")
+        else:
+            log.debug("%i retries left", remaining_retries)
+
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+        log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+        await anyio.sleep(timeout)
+
+        return await self._request(
+            options=options,
+            cast_to=cast_to,
+            retries_taken=retries_taken + 1,
+            stream=stream,
+            stream_cls=stream_cls,
+        )
+
+    async def _process_response(
+        self,
+        *,
+        cast_to: Type[ResponseT],
+        options: FinalRequestOptions,
+        response: httpx.Response,
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        retries_taken: int = 0,
+    ) -> ResponseT:
+        origin = get_origin(cast_to) or cast_to
+
+        if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
+            if not issubclass(origin, AsyncAPIResponse):
+                raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
+
+            response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+            return cast(
+                "ResponseT",
+                response_cls(
+                    raw=response,
+                    client=self,
+                    cast_to=extract_response_type(response_cls),
+                    stream=stream,
+                    stream_cls=stream_cls,
+                    options=options,
+                    retries_taken=retries_taken,
+                ),
+            )
+
+        if cast_to == httpx.Response:
+            return cast(ResponseT, response)
+
+        api_response = AsyncAPIResponse(
+            raw=response,
+            client=self,
+            cast_to=cast("type[ResponseT]", cast_to),  # pyright: ignore[reportUnnecessaryCast]
+            stream=stream,
+            stream_cls=stream_cls,
+            options=options,
+            retries_taken=retries_taken,
+        )
+        if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+            return cast(ResponseT, api_response)
+
+        return await api_response.parse()
+
+    def _request_api_list(
+        self,
+        model: Type[_T],
+        page: Type[AsyncPageT],
+        options: FinalRequestOptions,
+    ) -> AsyncPaginator[_T, AsyncPageT]:
+        return AsyncPaginator(client=self, options=options, page_cls=page, model=model)
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def get(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT:
+        opts = FinalRequestOptions.construct(method="get", url=path, **options)
+        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: Literal[False] = False,
+    ) -> ResponseT: ...
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: Literal[True],
+        stream_cls: type[_AsyncStreamT],
+    ) -> _AsyncStreamT: ...
+
+    @overload
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: bool,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT: ...
+
+    async def post(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+        stream: bool = False,
+        stream_cls: type[_AsyncStreamT] | None = None,
+    ) -> ResponseT | _AsyncStreamT:
+        opts = FinalRequestOptions.construct(
+            method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+        )
+        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
+
+    async def patch(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+        return await self.request(cast_to, opts)
+
+    async def put(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        files: RequestFiles | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(
+            method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+        )
+        return await self.request(cast_to, opts)
+
+    async def delete(
+        self,
+        path: str,
+        *,
+        cast_to: Type[ResponseT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+    ) -> ResponseT:
+        opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+        return await self.request(cast_to, opts)
+
+    def get_api_list(
+        self,
+        path: str,
+        *,
+        model: Type[_T],
+        page: Type[AsyncPageT],
+        body: Body | None = None,
+        options: RequestOptions = {},
+        method: str = "get",
+    ) -> AsyncPaginator[_T, AsyncPageT]:
+        opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+        return self._request_api_list(model, page, opts)
+
+
+def make_request_options(
+    *,
+    query: Query | None = None,
+    extra_headers: Headers | None = None,
+    extra_query: Query | None = None,
+    extra_body: Body | None = None,
+    idempotency_key: str | None = None,
+    timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    post_parser: PostParser | NotGiven = NOT_GIVEN,
+) -> RequestOptions:
+    """Create a dict of type RequestOptions without keys of NotGiven values."""
+    options: RequestOptions = {}
+    if extra_headers is not None:
+        options["headers"] = extra_headers
+
+    if extra_body is not None:
+        options["extra_json"] = cast(AnyMapping, extra_body)
+
+    if query is not None:
+        options["params"] = query
+
+    if extra_query is not None:
+        options["params"] = {**options.get("params", {}), **extra_query}
+
+    if not isinstance(timeout, NotGiven):
+        options["timeout"] = timeout
+
+    if idempotency_key is not None:
+        options["idempotency_key"] = idempotency_key
+
+    if is_given(post_parser):
+        # internal
+        options["post_parser"] = post_parser  # type: ignore
+
+    return options
+
+
+class ForceMultipartDict(Dict[str, None]):
+    def __bool__(self) -> bool:
+        return True
+
+
+class OtherPlatform:
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    @override
+    def __str__(self) -> str:
+        return f"Other:{self.name}"
+
+
+Platform = Union[
+    OtherPlatform,
+    Literal[
+        "MacOS",
+        "Linux",
+        "Windows",
+        "FreeBSD",
+        "OpenBSD",
+        "iOS",
+        "Android",
+        "Unknown",
+    ],
+]
+
+
+def get_platform() -> Platform:
+    try:
+        system = platform.system().lower()
+        platform_name = platform.platform().lower()
+    except Exception:
+        return "Unknown"
+
+    if "iphone" in platform_name or "ipad" in platform_name:
+        # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7
+        # system is Darwin and platform_name is a string like:
+        # - Darwin-21.6.0-iPhone12,1-64bit
+        # - Darwin-21.6.0-iPad7,11-64bit
+        return "iOS"
+
+    if system == "darwin":
+        return "MacOS"
+
+    if system == "windows":
+        return "Windows"
+
+    if "android" in platform_name:
+        # Tested using Pydroid 3
+        # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc'
+        return "Android"
+
+    if system == "linux":
+        # https://distro.readthedocs.io/en/latest/#distro.id
+        distro_id = distro.id()
+        if distro_id == "freebsd":
+            return "FreeBSD"
+
+        if distro_id == "openbsd":
+            return "OpenBSD"
+
+        return "Linux"
+
+    if platform_name:
+        return OtherPlatform(platform_name)
+
+    return "Unknown"
+
+
+@lru_cache(maxsize=None)
+def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
+    return {
+        "X-Stainless-Lang": "python",
+        "X-Stainless-Package-Version": version,
+        "X-Stainless-OS": str(platform or get_platform()),
+        "X-Stainless-Arch": str(get_architecture()),
+        "X-Stainless-Runtime": get_python_runtime(),
+        "X-Stainless-Runtime-Version": get_python_version(),
+    }
+
+
+class OtherArch:
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    @override
+    def __str__(self) -> str:
+        return f"other:{self.name}"
+
+
+Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]]
+
+
+def get_python_runtime() -> str:
+    try:
+        return platform.python_implementation()
+    except Exception:
+        return "unknown"
+
+
+def get_python_version() -> str:
+    try:
+        return platform.python_version()
+    except Exception:
+        return "unknown"
+
+
+def get_architecture() -> Arch:
+    try:
+        machine = platform.machine().lower()
+    except Exception:
+        return "unknown"
+
+    if machine in ("arm64", "aarch64"):
+        return "arm64"
+
+    # TODO: untested
+    if machine == "arm":
+        return "arm"
+
+    if machine == "x86_64":
+        return "x64"
+
+    # TODO: untested
+    if sys.maxsize <= 2**32:
+        return "x32"
+
+    if machine:
+        return OtherArch(machine)
+
+    return "unknown"
+
+
+def _merge_mappings(
+    obj1: Mapping[_T_co, Union[_T, Omit]],
+    obj2: Mapping[_T_co, Union[_T, Omit]],
+) -> Dict[_T_co, _T]:
+    """Merge two mappings of the same type, removing any values that are instances of `Omit`.
+
+    In cases with duplicate keys the second mapping takes precedence.
+    """
+    merged = {**obj1, **obj2}
+    return {key: value for key, value in merged.items() if not isinstance(value, Omit)}
diff --git a/src/codex/_client.py b/src/codex/_client.py
new file mode 100644
index 00000000..bda9e89c
--- /dev/null
+++ b/src/codex/_client.py
@@ -0,0 +1,518 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Union, Mapping
+from typing_extensions import Self, override
+
+import httpx
+
+from . import _exceptions
+from ._qs import Querystring
+from ._types import (
+    NOT_GIVEN,
+    Omit,
+    Timeout,
+    NotGiven,
+    Transport,
+    ProxiesTypes,
+    RequestOptions,
+)
+from ._utils import (
+    is_given,
+    get_async_library,
+)
+from ._version import __version__
+from .resources import health
+from ._streaming import Stream as Stream, AsyncStream as AsyncStream
+from ._exceptions import CodexError, APIStatusError
+from ._base_client import (
+    DEFAULT_MAX_RETRIES,
+    SyncAPIClient,
+    AsyncAPIClient,
+)
+from .resources.users import users
+from .resources.projects import projects
+from .resources.organizations import organizations
+
+__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Codex", "AsyncCodex", "Client", "AsyncClient"]
+
+
+class Codex(SyncAPIClient):
+    health: health.HealthResource
+    organizations: organizations.OrganizationsResource
+    users: users.UsersResource
+    projects: projects.ProjectsResource
+    with_raw_response: CodexWithRawResponse
+    with_streaming_response: CodexWithStreamedResponse
+
+    # client options
+    bearer_token: str
+    api_key: str
+    access_key: str
+
+    def __init__(
+        self,
+        *,
+        bearer_token: str | None = None,
+        api_key: str | None = None,
+        access_key: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        # Configure a custom httpx client.
+        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
+        http_client: httpx.Client | None = None,
+        # Enable or disable schema validation for data returned by the API.
+        # When enabled an error APIResponseValidationError is raised
+        # if the API responds with invalid data for the expected schema.
+        #
+        # This parameter may be removed or changed in the future.
+        # If you rely on this feature, please open a GitHub issue
+        # outlining your use-case to help us decide if it should be
+        # part of our public interface in the future.
+        _strict_response_validation: bool = False,
+    ) -> None:
+        """Construct a new synchronous codex client instance.
+
+        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+        - `bearer_token` from `BEARER_TOKEN`
+        - `api_key` from `AUTHENTICATED_API_KEY`
+        - `access_key` from `PUBLIC_ACCESS_KEY`
+        """
+        if bearer_token is None:
+            bearer_token = os.environ.get("BEARER_TOKEN")
+        if bearer_token is None:
+            raise CodexError(
+                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable"
+            )
+        self.bearer_token = bearer_token
+
+        if api_key is None:
+            api_key = os.environ.get("AUTHENTICATED_API_KEY")
+        if api_key is None:
+            raise CodexError(
+                "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable"
+            )
+        self.api_key = api_key
+
+        if access_key is None:
+            access_key = os.environ.get("PUBLIC_ACCESS_KEY")
+        if access_key is None:
+            raise CodexError(
+                "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable"
+            )
+        self.access_key = access_key
+
+        if base_url is None:
+            base_url = os.environ.get("CODEX_BASE_URL")
+        if base_url is None:
+            base_url = f"https://localhost:8080/test-api"
+
+        super().__init__(
+            version=__version__,
+            base_url=base_url,
+            max_retries=max_retries,
+            timeout=timeout,
+            http_client=http_client,
+            custom_headers=default_headers,
+            custom_query=default_query,
+            _strict_response_validation=_strict_response_validation,
+        )
+
+        self.health = health.HealthResource(self)
+        self.organizations = organizations.OrganizationsResource(self)
+        self.users = users.UsersResource(self)
+        self.projects = projects.ProjectsResource(self)
+        self.with_raw_response = CodexWithRawResponse(self)
+        self.with_streaming_response = CodexWithStreamedResponse(self)
+
+    @property
+    @override
+    def qs(self) -> Querystring:
+        return Querystring(array_format="comma")
+
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        if self._http_bearer:
+            return self._http_bearer
+        if self._authenticated_api_key:
+            return self._authenticated_api_key
+        if self._public_access_key:
+            return self._public_access_key
+        return {}
+
+    @property
+    def _http_bearer(self) -> dict[str, str]:
+        bearer_token = self.bearer_token
+        return {"Authorization": f"Bearer {bearer_token}"}
+
+    @property
+    def _authenticated_api_key(self) -> dict[str, str]:
+        api_key = self.api_key
+        return {"X-API-Key": api_key}
+
+    @property
+    def _public_access_key(self) -> dict[str, str]:
+        access_key = self.access_key
+        return {"X-Access-Key": access_key}
+
+    @property
+    @override
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            **super().default_headers,
+            "X-Stainless-Async": "false",
+            **self._custom_headers,
+        }
+
+    def copy(
+        self,
+        *,
+        bearer_token: str | None = None,
+        api_key: str | None = None,
+        access_key: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.Client | None = None,
+        max_retries: int | NotGiven = NOT_GIVEN,
+        default_headers: Mapping[str, str] | None = None,
+        set_default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        set_default_query: Mapping[str, object] | None = None,
+        _extra_kwargs: Mapping[str, Any] = {},
+    ) -> Self:
+        """
+        Create a new client instance re-using the same options given to the current client with optional overriding.
+        """
+        if default_headers is not None and set_default_headers is not None:
+            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+        if default_query is not None and set_default_query is not None:
+            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+        headers = self._custom_headers
+        if default_headers is not None:
+            headers = {**headers, **default_headers}
+        elif set_default_headers is not None:
+            headers = set_default_headers
+
+        params = self._custom_query
+        if default_query is not None:
+            params = {**params, **default_query}
+        elif set_default_query is not None:
+            params = set_default_query
+
+        http_client = http_client or self._client
+        return self.__class__(
+            bearer_token=bearer_token or self.bearer_token,
+            api_key=api_key or self.api_key,
+            access_key=access_key or self.access_key,
+            base_url=base_url or self.base_url,
+            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
+            http_client=http_client,
+            max_retries=max_retries if is_given(max_retries) else self.max_retries,
+            default_headers=headers,
+            default_query=params,
+            **_extra_kwargs,
+        )
+
+    # Alias for `copy` for nicer inline usage, e.g.
+    # client.with_options(timeout=10).foo.create(...)
+    with_options = copy
+
+    @override
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.status_code == 400:
+            return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+        if response.status_code == 401:
+            return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+        if response.status_code == 403:
+            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+
+        if response.status_code == 404:
+            return _exceptions.NotFoundError(err_msg, response=response, body=body)
+
+        if response.status_code == 409:
+            return _exceptions.ConflictError(err_msg, response=response, body=body)
+
+        if response.status_code == 422:
+            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+
+        if response.status_code == 429:
+            return _exceptions.RateLimitError(err_msg, response=response, body=body)
+
+        if response.status_code >= 500:
+            return _exceptions.InternalServerError(err_msg, response=response, body=body)
+        return APIStatusError(err_msg, response=response, body=body)
+
+
+class AsyncCodex(AsyncAPIClient):
+    health: health.AsyncHealthResource
+    organizations: organizations.AsyncOrganizationsResource
+    users: users.AsyncUsersResource
+    projects: projects.AsyncProjectsResource
+    with_raw_response: AsyncCodexWithRawResponse
+    with_streaming_response: AsyncCodexWithStreamedResponse
+
+    # client options
+    bearer_token: str
+    api_key: str
+    access_key: str
+
+    def __init__(
+        self,
+        *,
+        bearer_token: str | None = None,
+        api_key: str | None = None,
+        access_key: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
+        max_retries: int = DEFAULT_MAX_RETRIES,
+        default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        # Configure a custom httpx client.
+        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
+        http_client: httpx.AsyncClient | None = None,
+        # Enable or disable schema validation for data returned by the API.
+        # When enabled an error APIResponseValidationError is raised
+        # if the API responds with invalid data for the expected schema.
+        #
+        # This parameter may be removed or changed in the future.
+        # If you rely on this feature, please open a GitHub issue
+        # outlining your use-case to help us decide if it should be
+        # part of our public interface in the future.
+        _strict_response_validation: bool = False,
+    ) -> None:
+        """Construct a new async codex client instance.
+
+        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+        - `bearer_token` from `BEARER_TOKEN`
+        - `api_key` from `AUTHENTICATED_API_KEY`
+        - `access_key` from `PUBLIC_ACCESS_KEY`
+        """
+        if bearer_token is None:
+            bearer_token = os.environ.get("BEARER_TOKEN")
+        if bearer_token is None:
+            raise CodexError(
+                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable"
+            )
+        self.bearer_token = bearer_token
+
+        if api_key is None:
+            api_key = os.environ.get("AUTHENTICATED_API_KEY")
+        if api_key is None:
+            raise CodexError(
+                "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable"
+            )
+        self.api_key = api_key
+
+        if access_key is None:
+            access_key = os.environ.get("PUBLIC_ACCESS_KEY")
+        if access_key is None:
+            raise CodexError(
+                "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable"
+            )
+        self.access_key = access_key
+
+        if base_url is None:
+            base_url = os.environ.get("CODEX_BASE_URL")
+        if base_url is None:
+            base_url = f"https://localhost:8080/test-api"
+
+        super().__init__(
+            version=__version__,
+            base_url=base_url,
+            max_retries=max_retries,
+            timeout=timeout,
+            http_client=http_client,
+            custom_headers=default_headers,
+            custom_query=default_query,
+            _strict_response_validation=_strict_response_validation,
+        )
+
+        self.health = health.AsyncHealthResource(self)
+        self.organizations = organizations.AsyncOrganizationsResource(self)
+        self.users = users.AsyncUsersResource(self)
+        self.projects = projects.AsyncProjectsResource(self)
+        self.with_raw_response = AsyncCodexWithRawResponse(self)
+        self.with_streaming_response = AsyncCodexWithStreamedResponse(self)
+
+    @property
+    @override
+    def qs(self) -> Querystring:
+        return Querystring(array_format="comma")
+
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        if self._http_bearer:
+            return self._http_bearer
+        if self._authenticated_api_key:
+            return self._authenticated_api_key
+        if self._public_access_key:
+            return self._public_access_key
+        return {}
+
+    @property
+    def _http_bearer(self) -> dict[str, str]:
+        bearer_token = self.bearer_token
+        return {"Authorization": f"Bearer {bearer_token}"}
+
+    @property
+    def _authenticated_api_key(self) -> dict[str, str]:
+        api_key = self.api_key
+        return {"X-API-Key": api_key}
+
+    @property
+    def _public_access_key(self) -> dict[str, str]:
+        access_key = self.access_key
+        return {"X-Access-Key": access_key}
+
+    @property
+    @override
+    def default_headers(self) -> dict[str, str | Omit]:
+        return {
+            **super().default_headers,
+            "X-Stainless-Async": f"async:{get_async_library()}",
+            **self._custom_headers,
+        }
+
+    def copy(
+        self,
+        *,
+        bearer_token: str | None = None,
+        api_key: str | None = None,
+        access_key: str | None = None,
+        base_url: str | httpx.URL | None = None,
+        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
+        http_client: httpx.AsyncClient | None = None,
+        max_retries: int | NotGiven = NOT_GIVEN,
+        default_headers: Mapping[str, str] | None = None,
+        set_default_headers: Mapping[str, str] | None = None,
+        default_query: Mapping[str, object] | None = None,
+        set_default_query: Mapping[str, object] | None = None,
+        _extra_kwargs: Mapping[str, Any] = {},
+    ) -> Self:
+        """
+        Create a new client instance re-using the same options given to the current client with optional overriding.
+        """
+        if default_headers is not None and set_default_headers is not None:
+            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+        if default_query is not None and set_default_query is not None:
+            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+        headers = self._custom_headers
+        if default_headers is not None:
+            headers = {**headers, **default_headers}
+        elif set_default_headers is not None:
+            headers = set_default_headers
+
+        params = self._custom_query
+        if default_query is not None:
+            params = {**params, **default_query}
+        elif set_default_query is not None:
+            params = set_default_query
+
+        http_client = http_client or self._client
+        return self.__class__(
+            bearer_token=bearer_token or self.bearer_token,
+            api_key=api_key or self.api_key,
+            access_key=access_key or self.access_key,
+            base_url=base_url or self.base_url,
+            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
+            http_client=http_client,
+            max_retries=max_retries if is_given(max_retries) else self.max_retries,
+            default_headers=headers,
+            default_query=params,
+            **_extra_kwargs,
+        )
+
+    # Alias for `copy` for nicer inline usage, e.g.
+    # client.with_options(timeout=10).foo.create(...)
+    with_options = copy
+
+    @override
+    def _make_status_error(
+        self,
+        err_msg: str,
+        *,
+        body: object,
+        response: httpx.Response,
+    ) -> APIStatusError:
+        if response.status_code == 400:
+            return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+        if response.status_code == 401:
+            return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+        if response.status_code == 403:
+            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+
+        if response.status_code == 404:
+            return _exceptions.NotFoundError(err_msg, response=response, body=body)
+
+        if response.status_code == 409:
+            return _exceptions.ConflictError(err_msg, response=response, body=body)
+
+        if response.status_code == 422:
+            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+
+        if response.status_code == 429:
+            return _exceptions.RateLimitError(err_msg, response=response, body=body)
+
+        if response.status_code >= 500:
+            return _exceptions.InternalServerError(err_msg, response=response, body=body)
+        return APIStatusError(err_msg, response=response, body=body)
+
+
+class CodexWithRawResponse:
+    def __init__(self, client: Codex) -> None:
+        self.health = health.HealthResourceWithRawResponse(client.health)
+        self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
+        self.users = users.UsersResourceWithRawResponse(client.users)
+        self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
+
+
+class AsyncCodexWithRawResponse:
+    def __init__(self, client: AsyncCodex) -> None:
+        self.health = health.AsyncHealthResourceWithRawResponse(client.health)
+        self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
+        self.users = users.AsyncUsersResourceWithRawResponse(client.users)
+        self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
+
+
+class CodexWithStreamedResponse:
+    def __init__(self, client: Codex) -> None:
+        self.health = health.HealthResourceWithStreamingResponse(client.health)
+        self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
+        self.users = users.UsersResourceWithStreamingResponse(client.users)
+        self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
+
+
+class AsyncCodexWithStreamedResponse:
+    def __init__(self, client: AsyncCodex) -> None:
+        self.health = health.AsyncHealthResourceWithStreamingResponse(client.health)
+        self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
+        self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
+        self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
+
+
+Client = Codex
+
+AsyncClient = AsyncCodex
diff --git a/src/codex/_compat.py b/src/codex/_compat.py
new file mode 100644
index 00000000..92d9ee61
--- /dev/null
+++ b/src/codex/_compat.py
@@ -0,0 +1,219 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
+from datetime import date, datetime
+from typing_extensions import Self, Literal
+
+import pydantic
+from pydantic.fields import FieldInfo
+
+from ._types import IncEx, StrBytesIntFloat
+
+_T = TypeVar("_T")
+_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)
+
+# --------------- Pydantic v2 compatibility ---------------
+
+# Pyright incorrectly reports some of our functions as overriding a method when they don't
+# pyright: reportIncompatibleMethodOverride=false
+
+PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
+
+# v1 re-exports
+if TYPE_CHECKING:
+
+    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
+        ...
+
+    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
+        ...
+
+    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
+        ...
+
+    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
+        ...
+
+    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
+        ...
+
+    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
+        ...
+
+    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
+        ...
+
+else:
+    if PYDANTIC_V2:
+        from pydantic.v1.typing import (
+            get_args as get_args,
+            is_union as is_union,
+            get_origin as get_origin,
+            is_typeddict as is_typeddict,
+            is_literal_type as is_literal_type,
+        )
+        from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
+    else:
+        from pydantic.typing import (
+            get_args as get_args,
+            is_union as is_union,
+            get_origin as get_origin,
+            is_typeddict as is_typeddict,
+            is_literal_type as is_literal_type,
+        )
+        from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
+
+
+# refactored config
+if TYPE_CHECKING:
+    from pydantic import ConfigDict as ConfigDict
+else:
+    if PYDANTIC_V2:
+        from pydantic import ConfigDict
+    else:
+        # TODO: provide an error message here?
+        ConfigDict = None
+
+
+# renamed methods / properties
+def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_validate(value)
+    else:
+        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+
+
+def field_is_required(field: FieldInfo) -> bool:
+    if PYDANTIC_V2:
+        return field.is_required()
+    return field.required  # type: ignore
+
+
+def field_get_default(field: FieldInfo) -> Any:
+    value = field.get_default()
+    if PYDANTIC_V2:
+        from pydantic_core import PydanticUndefined
+
+        if value == PydanticUndefined:
+            return None
+        return value
+    return value
+
+
+def field_outer_type(field: FieldInfo) -> Any:
+    if PYDANTIC_V2:
+        return field.annotation
+    return field.outer_type_  # type: ignore
+
+
+def get_model_config(model: type[pydantic.BaseModel]) -> Any:
+    if PYDANTIC_V2:
+        return model.model_config
+    return model.__config__  # type: ignore
+
+
+def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
+    if PYDANTIC_V2:
+        return model.model_fields
+    return model.__fields__  # type: ignore
+
+
+def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_copy(deep=deep)
+    return model.copy(deep=deep)  # type: ignore
+
+
+def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
+    if PYDANTIC_V2:
+        return model.model_dump_json(indent=indent)
+    return model.json(indent=indent)  # type: ignore
+
+
+def model_dump(
+    model: pydantic.BaseModel,
+    *,
+    exclude: IncEx | None = None,
+    exclude_unset: bool = False,
+    exclude_defaults: bool = False,
+    warnings: bool = True,
+    mode: Literal["json", "python"] = "python",
+) -> dict[str, Any]:
+    if PYDANTIC_V2 or hasattr(model, "model_dump"):
+        return model.model_dump(
+            mode=mode,
+            exclude=exclude,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            # warnings are not supported in Pydantic v1
+            warnings=warnings if PYDANTIC_V2 else True,
+        )
+    return cast(
+        "dict[str, Any]",
+        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+            exclude=exclude,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+        ),
+    )
+
+
+def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
+    if PYDANTIC_V2:
+        return model.model_validate(data)
+    return model.parse_obj(data)  # pyright: ignore[reportDeprecated]
+
+
+# generic models
+if TYPE_CHECKING:
+
+    class GenericModel(pydantic.BaseModel): ...
+
+else:
+    if PYDANTIC_V2:
+        # there no longer needs to be a distinction in v2 but
+        # we still have to create our own subclass to avoid
+        # inconsistent MRO ordering errors
+        class GenericModel(pydantic.BaseModel): ...
+
+    else:
+        import pydantic.generics
+
+        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
+
+
+# cached properties
+if TYPE_CHECKING:
+    cached_property = property
+
+    # we define a separate type (copied from typeshed)
+    # that represents that `cached_property` is `set`able
+    # at runtime, which differs from `@property`.
+    #
+    # this is a separate type as editors likely special case
+    # `@property` and we don't want to cause issues just to have
+    # more helpful internal types.
+
+    class typed_cached_property(Generic[_T]):
+        func: Callable[[Any], _T]
+        attrname: str | None
+
+        def __init__(self, func: Callable[[Any], _T]) -> None: ...
+
+        @overload
+        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...
+
+        @overload
+        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...
+
+        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
+            raise NotImplementedError()
+
+        def __set_name__(self, owner: type[Any], name: str) -> None: ...
+
+        # __set__ is not defined at runtime, but @cached_property is designed to be settable
+        def __set__(self, instance: object, value: _T) -> None: ...
+else:
+    from functools import cached_property as cached_property
+
+    typed_cached_property = cached_property
diff --git a/src/codex/_constants.py b/src/codex/_constants.py
new file mode 100644
index 00000000..a2ac3b6f
--- /dev/null
+++ b/src/codex/_constants.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import httpx
+
+RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
+OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
+
+# default timeout is 1 minute
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0)
+DEFAULT_MAX_RETRIES = 2
+DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
+
+INITIAL_RETRY_DELAY = 0.5
+MAX_RETRY_DELAY = 8.0
diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py
new file mode 100644
index 00000000..90164ee4
--- /dev/null
+++ b/src/codex/_exceptions.py
@@ -0,0 +1,108 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+__all__ = [
+    "BadRequestError",
+    "AuthenticationError",
+    "PermissionDeniedError",
+    "NotFoundError",
+    "ConflictError",
+    "UnprocessableEntityError",
+    "RateLimitError",
+    "InternalServerError",
+]
+
+
+class CodexError(Exception):
+    pass
+
+
+class APIError(CodexError):
+    message: str
+    request: httpx.Request
+
+    body: object | None
+    """The API response body.
+
+    If the API responded with a valid JSON structure then this property will be the
+    decoded result.
+
+    If it isn't a valid JSON structure then this will be the raw response.
+
+    If there was no response associated with this error then it will be `None`.
+    """
+
+    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:  # noqa: ARG002
+        super().__init__(message)
+        self.request = request
+        self.message = message
+        self.body = body
+
+
+class APIResponseValidationError(APIError):
+    response: httpx.Response
+    status_code: int
+
+    def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
+        super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
+        self.response = response
+        self.status_code = response.status_code
+
+
+class APIStatusError(APIError):
+    """Raised when an API response has a status code of 4xx or 5xx."""
+
+    response: httpx.Response
+    status_code: int
+
+    def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
+        super().__init__(message, response.request, body=body)
+        self.response = response
+        self.status_code = response.status_code
+
+
+class APIConnectionError(APIError):
+    def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
+        super().__init__(message, request, body=None)
+
+
+class APITimeoutError(APIConnectionError):
+    def __init__(self, request: httpx.Request) -> None:
+        super().__init__(message="Request timed out.", request=request)
+
+
+class BadRequestError(APIStatusError):
+    status_code: Literal[400] = 400  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class AuthenticationError(APIStatusError):
+    status_code: Literal[401] = 401  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class PermissionDeniedError(APIStatusError):
+    status_code: Literal[403] = 403  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class NotFoundError(APIStatusError):
+    status_code: Literal[404] = 404  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class ConflictError(APIStatusError):
+    status_code: Literal[409] = 409  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class UnprocessableEntityError(APIStatusError):
+    status_code: Literal[422] = 422  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class RateLimitError(APIStatusError):
+    status_code: Literal[429] = 429  # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class InternalServerError(APIStatusError):
+    pass
diff --git a/src/codex/_files.py b/src/codex/_files.py
new file mode 100644
index 00000000..715cc207
--- /dev/null
+++ b/src/codex/_files.py
@@ -0,0 +1,123 @@
+from __future__ import annotations
+
+import io
+import os
+import pathlib
+from typing import overload
+from typing_extensions import TypeGuard
+
+import anyio
+
+from ._types import (
+    FileTypes,
+    FileContent,
+    RequestFiles,
+    HttpxFileTypes,
+    Base64FileInput,
+    HttpxFileContent,
+    HttpxRequestFiles,
+)
+from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
+
+
+def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
+    return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+
+
+def is_file_content(obj: object) -> TypeGuard[FileContent]:
+    return (
+        isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+    )
+
+
+def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
+    if not is_file_content(obj):
+        prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
+        raise RuntimeError(
+            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
+        ) from None
+
+
+@overload
+def to_httpx_files(files: None) -> None: ...
+
+
+@overload
+def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+    if files is None:
+        return None
+
+    if is_mapping_t(files):
+        files = {key: _transform_file(file) for key, file in files.items()}
+    elif is_sequence_t(files):
+        files = [(key, _transform_file(file)) for key, file in files]
+    else:
+        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
+
+    return files
+
+
+def _transform_file(file: FileTypes) -> HttpxFileTypes:
+    if is_file_content(file):
+        if isinstance(file, os.PathLike):
+            path = pathlib.Path(file)
+            return (path.name, path.read_bytes())
+
+        return file
+
+    if is_tuple_t(file):
+        return (file[0], _read_file_content(file[1]), *file[2:])
+
+    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+def _read_file_content(file: FileContent) -> HttpxFileContent:
+    if isinstance(file, os.PathLike):
+        return pathlib.Path(file).read_bytes()
+    return file
+
+
+@overload
+async def async_to_httpx_files(files: None) -> None: ...
+
+
+@overload
+async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+    if files is None:
+        return None
+
+    if is_mapping_t(files):
+        files = {key: await _async_transform_file(file) for key, file in files.items()}
+    elif is_sequence_t(files):
+        files = [(key, await _async_transform_file(file)) for key, file in files]
+    else:
+        raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
+
+    return files
+
+
+async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
+    if is_file_content(file):
+        if isinstance(file, os.PathLike):
+            path = anyio.Path(file)
+            return (path.name, await path.read_bytes())
+
+        return file
+
+    if is_tuple_t(file):
+        return (file[0], await _async_read_file_content(file[1]), *file[2:])
+
+    raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+async def _async_read_file_content(file: FileContent) -> HttpxFileContent:
+    if isinstance(file, os.PathLike):
+        return await anyio.Path(file).read_bytes()
+
+    return file
diff --git a/src/codex/_models.py b/src/codex/_models.py
new file mode 100644
index 00000000..9a918aab
--- /dev/null
+++ b/src/codex/_models.py
@@ -0,0 +1,795 @@
+from __future__ import annotations
+
+import os
+import inspect
+from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast
+from datetime import date, datetime
+from typing_extensions import (
+    Unpack,
+    Literal,
+    ClassVar,
+    Protocol,
+    Required,
+    ParamSpec,
+    TypedDict,
+    TypeGuard,
+    final,
+    override,
+    runtime_checkable,
+)
+
+import pydantic
+import pydantic.generics
+from pydantic.fields import FieldInfo
+
+from ._types import (
+    Body,
+    IncEx,
+    Query,
+    ModelT,
+    Headers,
+    Timeout,
+    NotGiven,
+    AnyMapping,
+    HttpxRequestFiles,
+)
+from ._utils import (
+    PropertyInfo,
+    is_list,
+    is_given,
+    json_safe,
+    lru_cache,
+    is_mapping,
+    parse_date,
+    coerce_boolean,
+    parse_datetime,
+    strip_not_given,
+    extract_type_arg,
+    is_annotated_type,
+    is_type_alias_type,
+    strip_annotated_type,
+)
+from ._compat import (
+    PYDANTIC_V2,
+    ConfigDict,
+    GenericModel as BaseGenericModel,
+    get_args,
+    is_union,
+    parse_obj,
+    get_origin,
+    is_literal_type,
+    get_model_config,
+    get_model_fields,
+    field_get_default,
+)
+from ._constants import RAW_RESPONSE_HEADER
+
+if TYPE_CHECKING:
+    from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema
+
+__all__ = ["BaseModel", "GenericModel"]
+
+_T = TypeVar("_T")
+_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")
+
+P = ParamSpec("P")
+
+
+@runtime_checkable
+class _ConfigProtocol(Protocol):
+    allow_population_by_field_name: bool
+
+
+class BaseModel(pydantic.BaseModel):
+    if PYDANTIC_V2:
+        model_config: ClassVar[ConfigDict] = ConfigDict(
+            extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
+        )
+    else:
+
+        @property
+        @override
+        def model_fields_set(self) -> set[str]:
+            # a forwards-compat shim for pydantic v2
+            return self.__fields_set__  # type: ignore
+
+        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
+            extra: Any = pydantic.Extra.allow  # type: ignore
+
+    def to_dict(
+        self,
+        *,
+        mode: Literal["json", "python"] = "python",
+        use_api_names: bool = True,
+        exclude_unset: bool = True,
+        exclude_defaults: bool = False,
+        exclude_none: bool = False,
+        warnings: bool = True,
+    ) -> dict[str, object]:
+        """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+        By default, fields that were not set by the API will not be included,
+        and keys will match the API response, *not* the property names from the model.
+
+        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+        Args:
+            mode:
+                If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
+                If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`
+
+            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+            exclude_unset: Whether to exclude fields that have not been explicitly set.
+            exclude_defaults: Whether to exclude fields that are set to their default value from the output.
+            exclude_none: Whether to exclude fields that have a value of `None` from the output.
+            warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
+        """
+        return self.model_dump(
+            mode=mode,
+            by_alias=use_api_names,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+            warnings=warnings,
+        )
+
+    def to_json(
+        self,
+        *,
+        indent: int | None = 2,
+        use_api_names: bool = True,
+        exclude_unset: bool = True,
+        exclude_defaults: bool = False,
+        exclude_none: bool = False,
+        warnings: bool = True,
+    ) -> str:
+        """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).
+
+        By default, fields that were not set by the API will not be included,
+        and keys will match the API response, *not* the property names from the model.
+
+        For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+        the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+        Args:
+            indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
+            use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+            exclude_unset: Whether to exclude fields that have not been explicitly set.
+            exclude_defaults: Whether to exclude fields that have the default value.
+            exclude_none: Whether to exclude fields that have a value of `None`.
+            warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
+        """
+        return self.model_dump_json(
+            indent=indent,
+            by_alias=use_api_names,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+            warnings=warnings,
+        )
+
+    @override
+    def __str__(self) -> str:
+        # mypy complains about an invalid self arg
+        return f'{self.__repr_name__()}({self.__repr_str__(", ")})'  # type: ignore[misc]
+
+    # Override the 'construct' method in a way that supports recursive parsing without validation.
+    # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
+    @classmethod
+    @override
+    def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
+        __cls: Type[ModelT],
+        _fields_set: set[str] | None = None,
+        **values: object,
+    ) -> ModelT:
+        m = __cls.__new__(__cls)
+        fields_values: dict[str, object] = {}
+
+        config = get_model_config(__cls)
+        populate_by_name = (
+            config.allow_population_by_field_name
+            if isinstance(config, _ConfigProtocol)
+            else config.get("populate_by_name")
+        )
+
+        if _fields_set is None:
+            _fields_set = set()
+
+        model_fields = get_model_fields(__cls)
+        for name, field in model_fields.items():
+            key = field.alias
+            if key is None or (key not in values and populate_by_name):
+                key = name
+
+            if key in values:
+                fields_values[name] = _construct_field(value=values[key], field=field, key=key)
+                _fields_set.add(name)
+            else:
+                fields_values[name] = field_get_default(field)
+
+        _extra = {}
+        for key, value in values.items():
+            if key not in model_fields:
+                if PYDANTIC_V2:
+                    _extra[key] = value
+                else:
+                    _fields_set.add(key)
+                    fields_values[key] = value
+
+        object.__setattr__(m, "__dict__", fields_values)
+
+        if PYDANTIC_V2:
+            # these properties are copied from Pydantic's `model_construct()` method
+            object.__setattr__(m, "__pydantic_private__", None)
+            object.__setattr__(m, "__pydantic_extra__", _extra)
+            object.__setattr__(m, "__pydantic_fields_set__", _fields_set)
+        else:
+            # init_private_attributes() does not exist in v2
+            m._init_private_attributes()  # type: ignore
+
+            # copied from Pydantic v1's `construct()` method
+            object.__setattr__(m, "__fields_set__", _fields_set)
+
+        return m
+
+    if not TYPE_CHECKING:
+        # type checkers incorrectly complain about this assignment
+        # because the type signatures are technically different
+        # although not in practice
+        model_construct = construct
+
+    if not PYDANTIC_V2:
+        # we define aliases for some of the new pydantic v2 methods so
+        # that we can just document these methods without having to specify
+        # a specific pydantic version as some users may not know which
+        # pydantic version they are currently using
+
+        @override
+        def model_dump(
+            self,
+            *,
+            mode: Literal["json", "python"] | str = "python",
+            include: IncEx | None = None,
+            exclude: IncEx | None = None,
+            by_alias: bool = False,
+            exclude_unset: bool = False,
+            exclude_defaults: bool = False,
+            exclude_none: bool = False,
+            round_trip: bool = False,
+            warnings: bool | Literal["none", "warn", "error"] = True,
+            context: dict[str, Any] | None = None,
+            serialize_as_any: bool = False,
+        ) -> dict[str, Any]:
+            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
+
+            Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+            Args:
+                mode: The mode in which `to_python` should run.
+                    If mode is 'json', the dictionary will only contain JSON serializable types.
+                    If mode is 'python', the dictionary may contain any Python objects.
+                include: A list of fields to include in the output.
+                exclude: A list of fields to exclude from the output.
+                by_alias: Whether to use the field's alias in the dictionary key if defined.
+                exclude_unset: Whether to exclude fields that are unset or None from the output.
+                exclude_defaults: Whether to exclude fields that are set to their default value from the output.
+                exclude_none: Whether to exclude fields that have a value of `None` from the output.
+                round_trip: Whether to enable serialization and deserialization round-trip support.
+                warnings: Whether to log warnings when invalid fields are encountered.
+
+            Returns:
+                A dictionary representation of the model.
+            """
+            if mode not in {"json", "python"}:
+                raise ValueError("mode must be either 'json' or 'python'")
+            if round_trip != False:
+                raise ValueError("round_trip is only supported in Pydantic v2")
+            if warnings != True:
+                raise ValueError("warnings is only supported in Pydantic v2")
+            if context is not None:
+                raise ValueError("context is only supported in Pydantic v2")
+            if serialize_as_any != False:
+                raise ValueError("serialize_as_any is only supported in Pydantic v2")
+            dumped = super().dict(  # pyright: ignore[reportDeprecated]
+                include=include,
+                exclude=exclude,
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+
+            return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped
+
+        @override
+        def model_dump_json(
+            self,
+            *,
+            indent: int | None = None,
+            include: IncEx | None = None,
+            exclude: IncEx | None = None,
+            by_alias: bool = False,
+            exclude_unset: bool = False,
+            exclude_defaults: bool = False,
+            exclude_none: bool = False,
+            round_trip: bool = False,
+            warnings: bool | Literal["none", "warn", "error"] = True,
+            context: dict[str, Any] | None = None,
+            serialize_as_any: bool = False,
+        ) -> str:
+            """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json
+
+            Generates a JSON representation of the model using Pydantic's `to_json` method.
+
+            Args:
+                indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
+                include: Field(s) to include in the JSON output. Can take either a string or set of strings.
+                exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
+                by_alias: Whether to serialize using field aliases.
+                exclude_unset: Whether to exclude fields that have not been explicitly set.
+                exclude_defaults: Whether to exclude fields that have the default value.
+                exclude_none: Whether to exclude fields that have a value of `None`.
+                round_trip: Whether to use serialization/deserialization between JSON and class instance.
+                warnings: Whether to show any warnings that occurred during serialization.
+
+            Returns:
+                A JSON string representation of the model.
+            """
+            if round_trip != False:
+                raise ValueError("round_trip is only supported in Pydantic v2")
+            if warnings != True:
+                raise ValueError("warnings is only supported in Pydantic v2")
+            if context is not None:
+                raise ValueError("context is only supported in Pydantic v2")
+            if serialize_as_any != False:
+                raise ValueError("serialize_as_any is only supported in Pydantic v2")
+            return super().json(  # type: ignore[reportDeprecated]
+                indent=indent,
+                include=include,
+                exclude=exclude,
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+
+
+def _construct_field(value: object, field: FieldInfo, key: str) -> object:
+    if value is None:
+        return field_get_default(field)
+
+    if PYDANTIC_V2:
+        type_ = field.annotation
+    else:
+        type_ = cast(type, field.outer_type_)  # type: ignore
+
+    if type_ is None:
+        raise RuntimeError(f"Unexpected field type is None for {key}")
+
+    return construct_type(value=value, type_=type_)
+
+
+def is_basemodel(type_: type) -> bool:
+    """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
+    if is_union(type_):
+        for variant in get_args(type_):
+            if is_basemodel(variant):
+                return True
+
+        return False
+
+    return is_basemodel_type(type_)
+
+
+def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
+    origin = get_origin(type_) or type_
+    if not inspect.isclass(origin):
+        return False
+    return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)
+
+
+def build(
+    base_model_cls: Callable[P, _BaseModelT],
+    *args: P.args,
+    **kwargs: P.kwargs,
+) -> _BaseModelT:
+    """Construct a BaseModel class without validation.
+
+    This is useful for cases where you need to instantiate a `BaseModel`
+    from an API response as this provides type-safe params which isn't supported
+    by helpers like `construct_type()`.
+
+    ```py
+    build(MyModel, my_field_a="foo", my_field_b=123)
+    ```
+    """
+    if args:
+        raise TypeError(
+            "Received positional arguments which are not supported; Keyword arguments must be used instead",
+        )
+
+    return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))
+
+
+def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
+    """Loose coercion to the expected type with construction of nested values.
+
+    Note: the returned value from this function is not guaranteed to match the
+    given type.
+    """
+    return cast(_T, construct_type(value=value, type_=type_))
+
+
+def construct_type(*, value: object, type_: object) -> object:
+    """Loose coercion to the expected type with construction of nested values.
+
+    If the given value does not match the expected type then it is returned as-is.
+    """
+    # we allow `object` as the input type because otherwise, passing things like
+    # `Literal['value']` will be reported as a type error by type checkers
+    type_ = cast("type[object]", type_)
+    if is_type_alias_type(type_):
+        type_ = type_.__value__  # type: ignore[unreachable]
+
+    # unwrap `Annotated[T, ...]` -> `T`
+    if is_annotated_type(type_):
+        meta: tuple[Any, ...] = get_args(type_)[1:]
+        type_ = extract_type_arg(type_, 0)
+    else:
+        meta = tuple()
+
+    # we need to use the origin class for any types that are subscripted generics
+    # e.g. Dict[str, object]
+    origin = get_origin(type_) or type_
+    args = get_args(type_)
+
+    if is_union(origin):
+        try:
+            return validate_type(type_=cast("type[object]", type_), value=value)
+        except Exception:
+            pass
+
+        # if the type is a discriminated union then we want to construct the right variant
+        # in the union, even if the data doesn't match exactly, otherwise we'd break code
+        # that relies on the constructed class types, e.g.
+        #
+        # class FooType:
+        #   kind: Literal['foo']
+        #   value: str
+        #
+        # class BarType:
+        #   kind: Literal['bar']
+        #   value: int
+        #
+        # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
+        # we'd end up constructing `FooType` when it should be `BarType`.
+        discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
+        if discriminator and is_mapping(value):
+            variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
+            if variant_value and isinstance(variant_value, str):
+                variant_type = discriminator.mapping.get(variant_value)
+                if variant_type:
+                    return construct_type(type_=variant_type, value=value)
+
+        # if the data is not valid, use the first variant that doesn't fail while deserializing
+        for variant in args:
+            try:
+                return construct_type(value=value, type_=variant)
+            except Exception:
+                continue
+
+        raise RuntimeError(f"Could not convert data into a valid instance of {type_}")
+
+    if origin == dict:
+        if not is_mapping(value):
+            return value
+
+        _, items_type = get_args(type_)  # Dict[_, items_type]
+        return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}
+
+    if (
+        not is_literal_type(type_)
+        and inspect.isclass(origin)
+        and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
+    ):
+        if is_list(value):
+            return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]
+
+        if is_mapping(value):
+            if issubclass(type_, BaseModel):
+                return type_.construct(**value)  # type: ignore[arg-type]
+
+            return cast(Any, type_).construct(**value)
+
+    if origin == list:
+        if not is_list(value):
+            return value
+
+        inner_type = args[0]  # List[inner_type]
+        return [construct_type(value=entry, type_=inner_type) for entry in value]
+
+    if origin == float:
+        if isinstance(value, int):
+            coerced = float(value)
+            if coerced != value:
+                return value
+            return coerced
+
+        return value
+
+    if type_ == datetime:
+        try:
+            return parse_datetime(value)  # type: ignore
+        except Exception:
+            return value
+
+    if type_ == date:
+        try:
+            return parse_date(value)  # type: ignore
+        except Exception:
+            return value
+
+    return value
+
+
+@runtime_checkable
+class CachedDiscriminatorType(Protocol):
+    __discriminator__: DiscriminatorDetails
+
+
+class DiscriminatorDetails:
+    field_name: str
+    """The name of the discriminator field in the variant class, e.g.
+
+    ```py
+    class Foo(BaseModel):
+        type: Literal['foo']
+    ```
+
+    Will result in field_name='type'
+    """
+
+    field_alias_from: str | None
+    """The name of the discriminator field in the API response, e.g.
+
+    ```py
+    class Foo(BaseModel):
+        type: Literal['foo'] = Field(alias='type_from_api')
+    ```
+
+    Will result in field_alias_from='type_from_api'
+    """
+
+    mapping: dict[str, type]
+    """Mapping of discriminator value to variant type, e.g.
+
+    {'foo': FooVariant, 'bar': BarVariant}
+    """
+
+    def __init__(
+        self,
+        *,
+        mapping: dict[str, type],
+        discriminator_field: str,
+        discriminator_alias: str | None,
+    ) -> None:
+        self.mapping = mapping
+        self.field_name = discriminator_field
+        self.field_alias_from = discriminator_alias
+
+
+def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
+    if isinstance(union, CachedDiscriminatorType):
+        return union.__discriminator__
+
+    discriminator_field_name: str | None = None
+
+    for annotation in meta_annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
+            discriminator_field_name = annotation.discriminator
+            break
+
+    if not discriminator_field_name:
+        return None
+
+    mapping: dict[str, type] = {}
+    discriminator_alias: str | None = None
+
+    for variant in get_args(union):
+        variant = strip_annotated_type(variant)
+        if is_basemodel_type(variant):
+            if PYDANTIC_V2:
+                field = _extract_field_schema_pv2(variant, discriminator_field_name)
+                if not field:
+                    continue
+
+                # Note: if one variant defines an alias then they all should
+                discriminator_alias = field.get("serialization_alias")
+
+                field_schema = field["schema"]
+
+                if field_schema["type"] == "literal":
+                    for entry in cast("LiteralSchema", field_schema)["expected"]:
+                        if isinstance(entry, str):
+                            mapping[entry] = variant
+            else:
+                field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name)  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+                if not field_info:
+                    continue
+
+                # Note: if one variant defines an alias then they all should
+                discriminator_alias = field_info.alias
+
+                if field_info.annotation and is_literal_type(field_info.annotation):
+                    for entry in get_args(field_info.annotation):
+                        if isinstance(entry, str):
+                            mapping[entry] = variant
+
+    if not mapping:
+        return None
+
+    details = DiscriminatorDetails(
+        mapping=mapping,
+        discriminator_field=discriminator_field_name,
+        discriminator_alias=discriminator_alias,
+    )
+    cast(CachedDiscriminatorType, union).__discriminator__ = details
+    return details
+
+
+def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
+    schema = model.__pydantic_core_schema__
+    if schema["type"] != "model":
+        return None
+
+    fields_schema = schema["schema"]
+    if fields_schema["type"] != "model-fields":
+        return None
+
+    fields_schema = cast("ModelFieldsSchema", fields_schema)
+
+    field = fields_schema["fields"].get(field_name)
+    if not field:
+        return None
+
+    return cast("ModelField", field)  # pyright: ignore[reportUnnecessaryCast]
+
+
+def validate_type(*, type_: type[_T], value: object) -> _T:
+    """Strict validation that the given value matches the expected type"""
+    if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel):
+        return cast(_T, parse_obj(type_, value))
+
+    return cast(_T, _validate_non_model_type(type_=type_, value=value))
+
+
+def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
+    """Add a pydantic config for the given type.
+
+    Note: this is a no-op on Pydantic v1.
+    """
+    setattr(typ, "__pydantic_config__", config)  # noqa: B010
+
+
+# our use of subclasssing here causes weirdness for type checkers,
+# so we just pretend that we don't subclass
+if TYPE_CHECKING:
+    GenericModel = BaseModel
+else:
+
+    class GenericModel(BaseGenericModel, BaseModel):
+        pass
+
+
+if PYDANTIC_V2:
+    from pydantic import TypeAdapter as _TypeAdapter
+
+    _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter))
+
+    if TYPE_CHECKING:
+        from pydantic import TypeAdapter
+    else:
+        TypeAdapter = _CachedTypeAdapter
+
+    def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+        return TypeAdapter(type_).validate_python(value)
+
+elif not TYPE_CHECKING:  # TODO: condition is weird
+
+    class RootModel(GenericModel, Generic[_T]):
+        """Used as a placeholder to easily convert runtime types to a Pydantic format
+        to provide validation.
+
+        For example:
+        ```py
+        validated = RootModel[int](__root__="5").__root__
+        # validated: 5
+        ```
+        """
+
+        __root__: _T
+
+    def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+        model = _create_pydantic_model(type_).validate(value)
+        return cast(_T, model.__root__)
+
+    def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]:
+        return RootModel[type_]  # type: ignore
+
+
+class FinalRequestOptionsInput(TypedDict, total=False):
+    method: Required[str]
+    url: Required[str]
+    params: Query
+    headers: Headers
+    max_retries: int
+    timeout: float | Timeout | None
+    files: HttpxRequestFiles | None
+    idempotency_key: str
+    json_data: Body
+    extra_json: AnyMapping
+
+
+@final
+class FinalRequestOptions(pydantic.BaseModel):
+    method: str
+    url: str
+    params: Query = {}
+    headers: Union[Headers, NotGiven] = NotGiven()
+    max_retries: Union[int, NotGiven] = NotGiven()
+    timeout: Union[float, Timeout, None, NotGiven] = NotGiven()
+    files: Union[HttpxRequestFiles, None] = None
+    idempotency_key: Union[str, None] = None
+    post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
+
+    # It should be noted that we cannot use `json` here as that would override
+    # a BaseModel method in an incompatible fashion.
+    json_data: Union[Body, None] = None
+    extra_json: Union[AnyMapping, None] = None
+
+    if PYDANTIC_V2:
+        model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
+    else:
+
+        class Config(pydantic.BaseConfig):  # pyright: ignore[reportDeprecated]
+            arbitrary_types_allowed: bool = True
+
+    def get_max_retries(self, max_retries: int) -> int:
+        if isinstance(self.max_retries, NotGiven):
+            return max_retries
+        return self.max_retries
+
+    def _strip_raw_response_header(self) -> None:
+        if not is_given(self.headers):
+            return
+
+        if self.headers.get(RAW_RESPONSE_HEADER):
+            self.headers = {**self.headers}
+            self.headers.pop(RAW_RESPONSE_HEADER)
+
+    # override the `construct` method so that we can run custom transformations.
+    # this is necessary as we don't want to do any actual runtime type checking
+    # (which means we can't use validators) but we do want to ensure that `NotGiven`
+    # values are not present
+    #
+    # type ignore required because we're adding explicit types to `**values`
+    @classmethod
+    def construct(  # type: ignore
+        cls,
+        _fields_set: set[str] | None = None,
+        **values: Unpack[FinalRequestOptionsInput],
+    ) -> FinalRequestOptions:
+        kwargs: dict[str, Any] = {
+            # we unconditionally call `strip_not_given` on any value
+            # as it will just ignore any non-mapping types
+            key: strip_not_given(value)
+            for key, value in values.items()
+        }
+        if PYDANTIC_V2:
+            return super().model_construct(_fields_set, **kwargs)
+        return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs))  # pyright: ignore[reportDeprecated]
+
+    if not TYPE_CHECKING:
+        # type checkers incorrectly complain about this assignment
+        model_construct = construct
diff --git a/src/codex/_qs.py b/src/codex/_qs.py
new file mode 100644
index 00000000..274320ca
--- /dev/null
+++ b/src/codex/_qs.py
@@ -0,0 +1,150 @@
+from __future__ import annotations
+
+from typing import Any, List, Tuple, Union, Mapping, TypeVar
+from urllib.parse import parse_qs, urlencode
+from typing_extensions import Literal, get_args
+
+from ._types import NOT_GIVEN, NotGiven, NotGivenOr
+from ._utils import flatten
+
+_T = TypeVar("_T")
+
+
+ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
+NestedFormat = Literal["dots", "brackets"]
+
+PrimitiveData = Union[str, int, float, bool, None]
+# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
+# https://github.com/microsoft/pyright/issues/3555
+Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
+Params = Mapping[str, Data]
+
+
+class Querystring:
+    array_format: ArrayFormat
+    nested_format: NestedFormat
+
+    def __init__(
+        self,
+        *,
+        array_format: ArrayFormat = "repeat",
+        nested_format: NestedFormat = "brackets",
+    ) -> None:
+        self.array_format = array_format
+        self.nested_format = nested_format
+
+    def parse(self, query: str) -> Mapping[str, object]:
+        # Note: custom format syntax is not supported yet
+        return parse_qs(query)
+
+    def stringify(
+        self,
+        params: Params,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> str:
+        return urlencode(
+            self.stringify_items(
+                params,
+                array_format=array_format,
+                nested_format=nested_format,
+            )
+        )
+
+    def stringify_items(
+        self,
+        params: Params,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> list[tuple[str, str]]:
+        opts = Options(
+            qs=self,
+            array_format=array_format,
+            nested_format=nested_format,
+        )
+        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])
+
+    def _stringify_item(
+        self,
+        key: str,
+        value: Data,
+        opts: Options,
+    ) -> list[tuple[str, str]]:
+        if isinstance(value, Mapping):
+            items: list[tuple[str, str]] = []
+            nested_format = opts.nested_format
+            for subkey, subvalue in value.items():
+                items.extend(
+                    self._stringify_item(
+                        # TODO: error if unknown format
+                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
+                        subvalue,
+                        opts,
+                    )
+                )
+            return items
+
+        if isinstance(value, (list, tuple)):
+            array_format = opts.array_format
+            if array_format == "comma":
+                return [
+                    (
+                        key,
+                        ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
+                    ),
+                ]
+            elif array_format == "repeat":
+                items = []
+                for item in value:
+                    items.extend(self._stringify_item(key, item, opts))
+                return items
+            elif array_format == "indices":
+                raise NotImplementedError("The array indices format is not supported yet")
+            elif array_format == "brackets":
+                items = []
+                key = key + "[]"
+                for item in value:
+                    items.extend(self._stringify_item(key, item, opts))
+                return items
+            else:
+                raise NotImplementedError(
+                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+                )
+
+        serialised = self._primitive_value_to_str(value)
+        if not serialised:
+            return []
+        return [(key, serialised)]
+
+    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
+        # copied from httpx
+        if value is True:
+            return "true"
+        elif value is False:
+            return "false"
+        elif value is None:
+            return ""
+        return str(value)
+
+
+_qs = Querystring()
+parse = _qs.parse
+stringify = _qs.stringify
+stringify_items = _qs.stringify_items
+
+
+class Options:
+    array_format: ArrayFormat
+    nested_format: NestedFormat
+
+    def __init__(
+        self,
+        qs: Querystring = _qs,
+        *,
+        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
+        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
+    ) -> None:
+        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
+        self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format
diff --git a/src/codex/_resource.py b/src/codex/_resource.py
new file mode 100644
index 00000000..7ba43eeb
--- /dev/null
+++ b/src/codex/_resource.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import time
+from typing import TYPE_CHECKING
+
+import anyio
+
+if TYPE_CHECKING:
+    from ._client import Codex, AsyncCodex
+
+
+class SyncAPIResource:
+    _client: Codex
+
+    def __init__(self, client: Codex) -> None:
+        self._client = client
+        self._get = client.get
+        self._post = client.post
+        self._patch = client.patch
+        self._put = client.put
+        self._delete = client.delete
+        self._get_api_list = client.get_api_list
+
+    def _sleep(self, seconds: float) -> None:
+        time.sleep(seconds)
+
+
+class AsyncAPIResource:
+    _client: AsyncCodex
+
+    def __init__(self, client: AsyncCodex) -> None:
+        self._client = client
+        self._get = client.get
+        self._post = client.post
+        self._patch = client.patch
+        self._put = client.put
+        self._delete = client.delete
+        self._get_api_list = client.get_api_list
+
+    async def _sleep(self, seconds: float) -> None:
+        await anyio.sleep(seconds)
diff --git a/src/codex/_response.py b/src/codex/_response.py
new file mode 100644
index 00000000..b63926f4
--- /dev/null
+++ b/src/codex/_response.py
@@ -0,0 +1,824 @@
+from __future__ import annotations
+
+import os
+import inspect
+import logging
+import datetime
+import functools
+from types import TracebackType
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Union,
+    Generic,
+    TypeVar,
+    Callable,
+    Iterator,
+    AsyncIterator,
+    cast,
+    overload,
+)
+from typing_extensions import Awaitable, ParamSpec, override, get_origin
+
+import anyio
+import httpx
+import pydantic
+
+from ._types import NoneType
+from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
+from ._models import BaseModel, is_basemodel
+from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
+from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
+from ._exceptions import CodexError, APIResponseValidationError
+
+if TYPE_CHECKING:
+    from ._models import FinalRequestOptions
+    from ._base_client import BaseClient
+
+
+P = ParamSpec("P")
+R = TypeVar("R")
+_T = TypeVar("_T")
+_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
+_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")
+
+log: logging.Logger = logging.getLogger(__name__)
+
+
+class BaseAPIResponse(Generic[R]):
+    _cast_to: type[R]
+    _client: BaseClient[Any, Any]
+    _parsed_by_type: dict[type[Any], Any]
+    _is_sse_stream: bool
+    _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
+    _options: FinalRequestOptions
+
+    http_response: httpx.Response
+
+    retries_taken: int
+    """The number of retries made. If no retries happened this will be `0`"""
+
+    def __init__(
+        self,
+        *,
+        raw: httpx.Response,
+        cast_to: type[R],
+        client: BaseClient[Any, Any],
+        stream: bool,
+        stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+        options: FinalRequestOptions,
+        retries_taken: int = 0,
+    ) -> None:
+        self._cast_to = cast_to
+        self._client = client
+        self._parsed_by_type = {}
+        self._is_sse_stream = stream
+        self._stream_cls = stream_cls
+        self._options = options
+        self.http_response = raw
+        self.retries_taken = retries_taken
+
+    @property
+    def headers(self) -> httpx.Headers:
+        return self.http_response.headers
+
+    @property
+    def http_request(self) -> httpx.Request:
+        """Returns the httpx Request instance associated with the current response."""
+        return self.http_response.request
+
+    @property
+    def status_code(self) -> int:
+        return self.http_response.status_code
+
+    @property
+    def url(self) -> httpx.URL:
+        """Returns the URL for which the request was made."""
+        return self.http_response.url
+
+    @property
+    def method(self) -> str:
+        return self.http_request.method
+
+    @property
+    def http_version(self) -> str:
+        return self.http_response.http_version
+
+    @property
+    def elapsed(self) -> datetime.timedelta:
+        """The time taken for the complete request/response cycle to complete."""
+        return self.http_response.elapsed
+
+    @property
+    def is_closed(self) -> bool:
+        """Whether or not the response body has been closed.
+
+        If this is False then there is response data that has not been read yet.
+        You must either fully consume the response body or call `.close()`
+        before discarding the response to prevent resource leaks.
+        """
+        return self.http_response.is_closed
+
+    @override
+    def __repr__(self) -> str:
+        return (
+            f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
+        )
+
+    def _parse(self, *, to: type[_T] | None = None) -> R | _T:
+        cast_to = to if to is not None else self._cast_to
+
+        # unwrap `TypeAlias('Name', T)` -> `T`
+        if is_type_alias_type(cast_to):
+            cast_to = cast_to.__value__  # type: ignore[unreachable]
+
+        # unwrap `Annotated[T, ...]` -> `T`
+        if cast_to and is_annotated_type(cast_to):
+            cast_to = extract_type_arg(cast_to, 0)
+
+        if self._is_sse_stream:
+            if to:
+                if not is_stream_class_type(to):
+                    raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")
+
+                return cast(
+                    _T,
+                    to(
+                        cast_to=extract_stream_chunk_type(
+                            to,
+                            failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
+                        ),
+                        response=self.http_response,
+                        client=cast(Any, self._client),
+                    ),
+                )
+
+            if self._stream_cls:
+                return cast(
+                    R,
+                    self._stream_cls(
+                        cast_to=extract_stream_chunk_type(self._stream_cls),
+                        response=self.http_response,
+                        client=cast(Any, self._client),
+                    ),
+                )
+
+            stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
+            if stream_cls is None:
+                raise MissingStreamClassError()
+
+            return cast(
+                R,
+                stream_cls(
+                    cast_to=cast_to,
+                    response=self.http_response,
+                    client=cast(Any, self._client),
+                ),
+            )
+
+        if cast_to is NoneType:
+            return cast(R, None)
+
+        response = self.http_response
+        if cast_to == str:
+            return cast(R, response.text)
+
+        if cast_to == bytes:
+            return cast(R, response.content)
+
+        if cast_to == int:
+            return cast(R, int(response.text))
+
+        if cast_to == float:
+            return cast(R, float(response.text))
+
+        if cast_to == bool:
+            return cast(R, response.text.lower() == "true")
+
+        origin = get_origin(cast_to) or cast_to
+
+        if origin == APIResponse:
+            raise RuntimeError("Unexpected state - cast_to is `APIResponse`")
+
+        if inspect.isclass(origin) and issubclass(origin, httpx.Response):
+            # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
+            # and pass that class to our request functions. We cannot change the variance to be either
+            # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
+            # the response class ourselves but that is something that should be supported directly in httpx
+            # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
+            if cast_to != httpx.Response:
+                raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
+            return cast(R, response)
+
+        if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel):
+            raise TypeError("Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`")
+
+        if (
+            cast_to is not object
+            and not origin is list
+            and not origin is dict
+            and not origin is Union
+            and not issubclass(origin, BaseModel)
+        ):
+            raise RuntimeError(
+                f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
+            )
+
+        # split is required to handle cases where additional information is included
+        # in the response, e.g. application/json; charset=utf-8
+        content_type, *_ = response.headers.get("content-type", "*").split(";")
+        if content_type != "application/json":
+            if is_basemodel(cast_to):
+                try:
+                    data = response.json()
+                except Exception as exc:
+                    log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
+                else:
+                    return self._client._process_response_data(
+                        data=data,
+                        cast_to=cast_to,  # type: ignore
+                        response=response,
+                    )
+
+            if self._client._strict_response_validation:
+                raise APIResponseValidationError(
+                    response=response,
+                    message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
+                    body=response.text,
+                )
+
+            # If the API responds with content that isn't JSON then we just return
+            # the (decoded) text without performing any parsing so that you can still
+            # handle the response however you need to.
+            return response.text  # type: ignore
+
+        data = response.json()
+
+        return self._client._process_response_data(
+            data=data,
+            cast_to=cast_to,  # type: ignore
+            response=response,
+        )
+
+
+class APIResponse(BaseAPIResponse[R]):
+    @overload
+    def parse(self, *, to: type[_T]) -> _T: ...
+
+    @overload
+    def parse(self) -> R: ...
+
+    def parse(self, *, to: type[_T] | None = None) -> R | _T:
+        """Returns the rich python representation of this response's data.
+
+        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+        You can customise the type that the response is parsed into through
+        the `to` argument, e.g.
+
+        ```py
+        from codex import BaseModel
+
+
+        class MyModel(BaseModel):
+            foo: str
+
+
+        obj = response.parse(to=MyModel)
+        print(obj.foo)
+        ```
+
+        We support parsing:
+          - `BaseModel`
+          - `dict`
+          - `list`
+          - `Union`
+          - `str`
+          - `int`
+          - `float`
+          - `httpx.Response`
+        """
+        cache_key = to if to is not None else self._cast_to
+        cached = self._parsed_by_type.get(cache_key)
+        if cached is not None:
+            return cached  # type: ignore[no-any-return]
+
+        if not self._is_sse_stream:
+            self.read()
+
+        parsed = self._parse(to=to)
+        if is_given(self._options.post_parser):
+            parsed = self._options.post_parser(parsed)
+
+        self._parsed_by_type[cache_key] = parsed
+        return parsed
+
+    def read(self) -> bytes:
+        """Read and return the binary response content."""
+        try:
+            return self.http_response.read()
+        except httpx.StreamConsumed as exc:
+            # The default error raised by httpx isn't very
+            # helpful in our case so we re-raise it with
+            # a different error message.
+            raise StreamAlreadyConsumed() from exc
+
+    def text(self) -> str:
+        """Read and decode the response content into a string."""
+        self.read()
+        return self.http_response.text
+
+    def json(self) -> object:
+        """Read and decode the JSON response content."""
+        self.read()
+        return self.http_response.json()
+
+    def close(self) -> None:
+        """Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        self.http_response.close()
+
+    def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
+        """
+        A byte-iterator over the decoded response content.
+
+        This automatically handles gzip, deflate and brotli encoded responses.
+        """
+        for chunk in self.http_response.iter_bytes(chunk_size):
+            yield chunk
+
+    def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
+        """A str-iterator over the decoded response content
+        that handles both gzip, deflate, etc but also detects the content's
+        string encoding.
+        """
+        for chunk in self.http_response.iter_text(chunk_size):
+            yield chunk
+
+    def iter_lines(self) -> Iterator[str]:
+        """Like `iter_text()` but will only yield chunks for each line"""
+        for chunk in self.http_response.iter_lines():
+            yield chunk
+
+
+class AsyncAPIResponse(BaseAPIResponse[R]):
+    @overload
+    async def parse(self, *, to: type[_T]) -> _T: ...
+
+    @overload
+    async def parse(self) -> R: ...
+
+    async def parse(self, *, to: type[_T] | None = None) -> R | _T:
+        """Returns the rich python representation of this response's data.
+
+        For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+        You can customise the type that the response is parsed into through
+        the `to` argument, e.g.
+
+        ```py
+        from codex import BaseModel
+
+
+        class MyModel(BaseModel):
+            foo: str
+
+
+        obj = response.parse(to=MyModel)
+        print(obj.foo)
+        ```
+
+        We support parsing:
+          - `BaseModel`
+          - `dict`
+          - `list`
+          - `Union`
+          - `str`
+          - `httpx.Response`
+        """
+        cache_key = to if to is not None else self._cast_to
+        cached = self._parsed_by_type.get(cache_key)
+        if cached is not None:
+            return cached  # type: ignore[no-any-return]
+
+        if not self._is_sse_stream:
+            await self.read()
+
+        parsed = self._parse(to=to)
+        if is_given(self._options.post_parser):
+            parsed = self._options.post_parser(parsed)
+
+        self._parsed_by_type[cache_key] = parsed
+        return parsed
+
+    async def read(self) -> bytes:
+        """Read and return the binary response content."""
+        try:
+            return await self.http_response.aread()
+        except httpx.StreamConsumed as exc:
+            # the default error raised by httpx isn't very
+            # helpful in our case so we re-raise it with
+            # a different error message
+            raise StreamAlreadyConsumed() from exc
+
+    async def text(self) -> str:
+        """Read and decode the response content into a string."""
+        await self.read()
+        return self.http_response.text
+
+    async def json(self) -> object:
+        """Read and decode the JSON response content."""
+        await self.read()
+        return self.http_response.json()
+
+    async def close(self) -> None:
+        """Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        await self.http_response.aclose()
+
+    async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
+        """
+        A byte-iterator over the decoded response content.
+
+        This automatically handles gzip, deflate and brotli encoded responses.
+        """
+        async for chunk in self.http_response.aiter_bytes(chunk_size):
+            yield chunk
+
+    async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
+        """A str-iterator over the decoded response content
+        that handles both gzip, deflate, etc but also detects the content's
+        string encoding.
+        """
+        async for chunk in self.http_response.aiter_text(chunk_size):
+            yield chunk
+
+    async def iter_lines(self) -> AsyncIterator[str]:
+        """Like `iter_text()` but will only yield chunks for each line"""
+        async for chunk in self.http_response.aiter_lines():
+            yield chunk
+
+
+class BinaryAPIResponse(APIResponse[bytes]):
+    """Subclass of APIResponse providing helpers for dealing with binary data.
+
+    Note: If you want to stream the response data instead of eagerly reading it
+    all at once then you should use `.with_streaming_response` when making
+    the API request, e.g. `.with_streaming_response.get_binary_response()`
+    """
+
+    def write_to_file(
+        self,
+        file: str | os.PathLike[str],
+    ) -> None:
+        """Write the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+
+        Note: if you want to stream the data to the file instead of writing
+        all at once then you should use `.with_streaming_response` when making
+        the API request, e.g. `.with_streaming_response.get_binary_response()`
+        """
+        with open(file, mode="wb") as f:
+            for data in self.iter_bytes():
+                f.write(data)
+
+
+class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
+    """Subclass of APIResponse providing helpers for dealing with binary data.
+
+    Note: If you want to stream the response data instead of eagerly reading it
+    all at once then you should use `.with_streaming_response` when making
+    the API request, e.g. `.with_streaming_response.get_binary_response()`
+    """
+
+    async def write_to_file(
+        self,
+        file: str | os.PathLike[str],
+    ) -> None:
+        """Write the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+
+        Note: if you want to stream the data to the file instead of writing
+        all at once then you should use `.with_streaming_response` when making
+        the API request, e.g. `.with_streaming_response.get_binary_response()`
+        """
+        path = anyio.Path(file)
+        async with await path.open(mode="wb") as f:
+            async for data in self.iter_bytes():
+                await f.write(data)
+
+
+class StreamedBinaryAPIResponse(APIResponse[bytes]):
+    def stream_to_file(
+        self,
+        file: str | os.PathLike[str],
+        *,
+        chunk_size: int | None = None,
+    ) -> None:
+        """Streams the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+        """
+        with open(file, mode="wb") as f:
+            for data in self.iter_bytes(chunk_size):
+                f.write(data)
+
+
+class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
+    async def stream_to_file(
+        self,
+        file: str | os.PathLike[str],
+        *,
+        chunk_size: int | None = None,
+    ) -> None:
+        """Streams the output to the given file.
+
+        Accepts a filename or any path-like object, e.g. pathlib.Path
+        """
+        path = anyio.Path(file)
+        async with await path.open(mode="wb") as f:
+            async for data in self.iter_bytes(chunk_size):
+                await f.write(data)
+
+
+class MissingStreamClassError(TypeError):
+    def __init__(self) -> None:
+        super().__init__(
+            "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `codex._streaming` for reference",
+        )
+
+
+class StreamAlreadyConsumed(CodexError):
+    """
+    Attempted to read or stream content, but the content has already
+    been streamed.
+
+    This can happen if you use a method like `.iter_lines()` and then attempt
+    to read th entire response body afterwards, e.g.
+
+    ```py
+    response = await client.post(...)
+    async for line in response.iter_lines():
+        ...  # do something with `line`
+
+    content = await response.read()
+    # ^ error
+    ```
+
+    If you want this behaviour you'll need to either manually accumulate the response
+    content or call `await response.read()` before iterating over the stream.
+    """
+
+    def __init__(self) -> None:
+        message = (
+            "Attempted to read or stream some content, but the content has "
+            "already been streamed. "
+            "This could be due to attempting to stream the response "
+            "content more than once."
+            "\n\n"
+            "You can fix this by manually accumulating the response content while streaming "
+            "or by calling `.read()` before starting to stream."
+        )
+        super().__init__(message)
+
+
+class ResponseContextManager(Generic[_APIResponseT]):
+    """Context manager for ensuring that a request is not made
+    until it is entered and that the response will always be closed
+    when the context manager exits
+    """
+
+    def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
+        self._request_func = request_func
+        self.__response: _APIResponseT | None = None
+
+    def __enter__(self) -> _APIResponseT:
+        self.__response = self._request_func()
+        return self.__response
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        if self.__response is not None:
+            self.__response.close()
+
+
+class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
+    """Context manager for ensuring that a request is not made
+    until it is entered and that the response will always be closed
+    when the context manager exits
+    """
+
+    def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
+        self._api_request = api_request
+        self.__response: _AsyncAPIResponseT | None = None
+
+    async def __aenter__(self) -> _AsyncAPIResponseT:
+        self.__response = await self._api_request
+        return self.__response
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        if self.__response is not None:
+            await self.__response.close()
+
+
+def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support streaming and returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = functools.partial(func, *args, **kwargs)
+
+        return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))
+
+    return wrapped
+
+
+def async_to_streamed_response_wrapper(
+    func: Callable[P, Awaitable[R]],
+) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support streaming and returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = func(*args, **kwargs)
+
+        return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))
+
+    return wrapped
+
+
+def to_custom_streamed_response_wrapper(
+    func: Callable[P, object],
+    response_cls: type[_APIResponseT],
+) -> Callable[P, ResponseContextManager[_APIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support streaming and returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = functools.partial(func, *args, **kwargs)
+
+        return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))
+
+    return wrapped
+
+
+def async_to_custom_streamed_response_wrapper(
+    func: Callable[P, Awaitable[object]],
+    response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support streaming and returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "stream"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        make_request = func(*args, **kwargs)
+
+        return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))
+
+    return wrapped
+
+
+def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(APIResponse[R], func(*args, **kwargs))
+
+    return wrapped
+
+
+def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
+    """Higher order function that takes one of our bound API methods and wraps it
+    to support returning the raw `APIResponse` object directly.
+    """
+
+    @functools.wraps(func)
+    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
+        extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(AsyncAPIResponse[R], await func(*args, **kwargs))
+
+    return wrapped
+
+
+def to_custom_raw_response_wrapper(
+    func: Callable[P, object],
+    response_cls: type[_APIResponseT],
+) -> Callable[P, _APIResponseT]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(_APIResponseT, func(*args, **kwargs))
+
+    return wrapped
+
+
+def async_to_custom_raw_response_wrapper(
+    func: Callable[P, Awaitable[object]],
+    response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
+    """Higher order function that takes one of our bound API methods and an `APIResponse` class
+    and wraps the method to support returning the given response class directly.
+
+    Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+    """
+
+    @functools.wraps(func)
+    def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
+        extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+        extra_headers[RAW_RESPONSE_HEADER] = "raw"
+        extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+        kwargs["extra_headers"] = extra_headers
+
+        return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))
+
+    return wrapped
+
+
+def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
+    """Given a type like `APIResponse[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyResponse(APIResponse[bytes]):
+        ...
+
+    extract_response_type(MyResponse) -> bytes
+    ```
+    """
+    return extract_type_var_from_base(
+        typ,
+        generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
+        index=0,
+    )
diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py
new file mode 100644
index 00000000..3af102ce
--- /dev/null
+++ b/src/codex/_streaming.py
@@ -0,0 +1,333 @@
+# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
+from __future__ import annotations
+
+import json
+import inspect
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
+from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable
+
+import httpx
+
+from ._utils import extract_type_var_from_base
+
+if TYPE_CHECKING:
+    from ._client import Codex, AsyncCodex
+
+
+_T = TypeVar("_T")
+
+
+class Stream(Generic[_T]):
+    """Provides the core interface to iterate over a synchronous stream response."""
+
+    response: httpx.Response
+
+    _decoder: SSEBytesDecoder
+
+    def __init__(
+        self,
+        *,
+        cast_to: type[_T],
+        response: httpx.Response,
+        client: Codex,
+    ) -> None:
+        self.response = response
+        self._cast_to = cast_to
+        self._client = client
+        self._decoder = client._make_sse_decoder()
+        self._iterator = self.__stream__()
+
+    def __next__(self) -> _T:
+        return self._iterator.__next__()
+
+    def __iter__(self) -> Iterator[_T]:
+        for item in self._iterator:
+            yield item
+
+    def _iter_events(self) -> Iterator[ServerSentEvent]:
+        yield from self._decoder.iter_bytes(self.response.iter_bytes())
+
+    def __stream__(self) -> Iterator[_T]:
+        cast_to = cast(Any, self._cast_to)
+        response = self.response
+        process_data = self._client._process_response_data
+        iterator = self._iter_events()
+
+        for sse in iterator:
+            yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+
+        # Ensure the entire stream is consumed
+        for _sse in iterator:
+            ...
+
+    def __enter__(self) -> Self:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def close(self) -> None:
+        """
+        Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        self.response.close()
+
+
+class AsyncStream(Generic[_T]):
+    """Provides the core interface to iterate over an asynchronous stream response."""
+
+    response: httpx.Response
+
+    _decoder: SSEDecoder | SSEBytesDecoder
+
+    def __init__(
+        self,
+        *,
+        cast_to: type[_T],
+        response: httpx.Response,
+        client: AsyncCodex,
+    ) -> None:
+        self.response = response
+        self._cast_to = cast_to
+        self._client = client
+        self._decoder = client._make_sse_decoder()
+        self._iterator = self.__stream__()
+
+    async def __anext__(self) -> _T:
+        return await self._iterator.__anext__()
+
+    async def __aiter__(self) -> AsyncIterator[_T]:
+        async for item in self._iterator:
+            yield item
+
+    async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
+        async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
+            yield sse
+
+    async def __stream__(self) -> AsyncIterator[_T]:
+        cast_to = cast(Any, self._cast_to)
+        response = self.response
+        process_data = self._client._process_response_data
+        iterator = self._iter_events()
+
+        async for sse in iterator:
+            yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+
+        # Ensure the entire stream is consumed
+        async for _sse in iterator:
+            ...
+
+    async def __aenter__(self) -> Self:
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.close()
+
+    async def close(self) -> None:
+        """
+        Close the response and release the connection.
+
+        Automatically called if the response body is read to completion.
+        """
+        await self.response.aclose()
+
+
+class ServerSentEvent:
+    def __init__(
+        self,
+        *,
+        event: str | None = None,
+        data: str | None = None,
+        id: str | None = None,
+        retry: int | None = None,
+    ) -> None:
+        if data is None:
+            data = ""
+
+        self._id = id
+        self._data = data
+        self._event = event or None
+        self._retry = retry
+
+    @property
+    def event(self) -> str | None:
+        return self._event
+
+    @property
+    def id(self) -> str | None:
+        return self._id
+
+    @property
+    def retry(self) -> int | None:
+        return self._retry
+
+    @property
+    def data(self) -> str:
+        return self._data
+
+    def json(self) -> Any:
+        return json.loads(self.data)
+
+    @override
+    def __repr__(self) -> str:
+        return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"
+
+
+class SSEDecoder:
+    _data: list[str]
+    _event: str | None
+    _retry: int | None
+    _last_event_id: str | None
+
+    def __init__(self) -> None:
+        self._event = None
+        self._data = []
+        self._last_event_id = None
+        self._retry = None
+
+    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        for chunk in self._iter_chunks(iterator):
+            # Split before decoding so splitlines() only uses \r and \n
+            for raw_line in chunk.splitlines():
+                line = raw_line.decode("utf-8")
+                sse = self.decode(line)
+                if sse:
+                    yield sse
+
+    def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
+        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+        data = b""
+        for chunk in iterator:
+            for line in chunk.splitlines(keepends=True):
+                data += line
+                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+                    yield data
+                    data = b""
+        if data:
+            yield data
+
+    async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        async for chunk in self._aiter_chunks(iterator):
+            # Split before decoding so splitlines() only uses \r and \n
+            for raw_line in chunk.splitlines():
+                line = raw_line.decode("utf-8")
+                sse = self.decode(line)
+                if sse:
+                    yield sse
+
+    async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
+        """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+        data = b""
+        async for chunk in iterator:
+            for line in chunk.splitlines(keepends=True):
+                data += line
+                if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+                    yield data
+                    data = b""
+        if data:
+            yield data
+
+    def decode(self, line: str) -> ServerSentEvent | None:
+        # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation  # noqa: E501
+
+        if not line:
+            if not self._event and not self._data and not self._last_event_id and self._retry is None:
+                return None
+
+            sse = ServerSentEvent(
+                event=self._event,
+                data="\n".join(self._data),
+                id=self._last_event_id,
+                retry=self._retry,
+            )
+
+            # NOTE: as per the SSE spec, do not reset last_event_id.
+            self._event = None
+            self._data = []
+            self._retry = None
+
+            return sse
+
+        if line.startswith(":"):
+            return None
+
+        fieldname, _, value = line.partition(":")
+
+        if value.startswith(" "):
+            value = value[1:]
+
+        if fieldname == "event":
+            self._event = value
+        elif fieldname == "data":
+            self._data.append(value)
+        elif fieldname == "id":
+            if "\0" in value:
+                pass
+            else:
+                self._last_event_id = value
+        elif fieldname == "retry":
+            try:
+                self._retry = int(value)
+            except (TypeError, ValueError):
+                pass
+        else:
+            pass  # Field is ignored.
+
+        return None
+
+
+@runtime_checkable
+class SSEBytesDecoder(Protocol):
+    def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+        """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        ...
+
+    def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+        """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
+        ...
+
+
+def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
+    """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
+    origin = get_origin(typ) or typ
+    return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))
+
+
+def extract_stream_chunk_type(
+    stream_cls: type,
+    *,
+    failure_message: str | None = None,
+) -> type:
+    """Given a type like `Stream[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyStream(Stream[bytes]):
+        ...
+
+    extract_stream_chunk_type(MyStream) -> bytes
+    ```
+    """
+    from ._base_client import Stream, AsyncStream
+
+    return extract_type_var_from_base(
+        stream_cls,
+        index=0,
+        generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
+        failure_message=failure_message,
+    )
diff --git a/src/codex/_types.py b/src/codex/_types.py
new file mode 100644
index 00000000..dfa51c2f
--- /dev/null
+++ b/src/codex/_types.py
@@ -0,0 +1,217 @@
+from __future__ import annotations
+
+from os import PathLike
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    Dict,
+    List,
+    Type,
+    Tuple,
+    Union,
+    Mapping,
+    TypeVar,
+    Callable,
+    Optional,
+    Sequence,
+)
+from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
+
+import httpx
+import pydantic
+from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport
+
+if TYPE_CHECKING:
+    from ._models import BaseModel
+    from ._response import APIResponse, AsyncAPIResponse
+
+Transport = BaseTransport
+AsyncTransport = AsyncBaseTransport
+Query = Mapping[str, object]
+Body = object
+AnyMapping = Mapping[str, object]
+ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
+_T = TypeVar("_T")
+
+
+# Approximates httpx internal ProxiesTypes and RequestFiles types
+# while adding support for `PathLike` instances
+ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
+ProxiesTypes = Union[str, Proxy, ProxiesDict]
+if TYPE_CHECKING:
+    Base64FileInput = Union[IO[bytes], PathLike[str]]
+    FileContent = Union[IO[bytes], bytes, PathLike[str]]
+else:
+    Base64FileInput = Union[IO[bytes], PathLike]
+    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.
+FileTypes = Union[
+    # file (or bytes)
+    FileContent,
+    # (filename, file (or bytes))
+    Tuple[Optional[str], FileContent],
+    # (filename, file (or bytes), content_type)
+    Tuple[Optional[str], FileContent, Optional[str]],
+    # (filename, file (or bytes), content_type, headers)
+    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
+]
+RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
+
+# duplicate of the above but without our custom file support
+HttpxFileContent = Union[IO[bytes], bytes]
+HttpxFileTypes = Union[
+    # file (or bytes)
+    HttpxFileContent,
+    # (filename, file (or bytes))
+    Tuple[Optional[str], HttpxFileContent],
+    # (filename, file (or bytes), content_type)
+    Tuple[Optional[str], HttpxFileContent, Optional[str]],
+    # (filename, file (or bytes), content_type, headers)
+    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
+]
+HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]
+
+# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
+# where ResponseT includes `None`. In order to support directly
+# passing `None`, overloads would have to be defined for every
+# method that uses `ResponseT` which would lead to an unacceptable
+# amount of code duplication and make it unreadable. See _base_client.py
+# for example usage.
+#
+# This unfortunately means that you will either have
+# to import this type and pass it explicitly:
+#
+# from codex import NoneType
+# client.get('/foo', cast_to=NoneType)
+#
+# or build it yourself:
+#
+# client.get('/foo', cast_to=type(None))
+if TYPE_CHECKING:
+    NoneType: Type[None]
+else:
+    NoneType = type(None)
+
+
+class RequestOptions(TypedDict, total=False):
+    headers: Headers
+    max_retries: int
+    timeout: float | Timeout | None
+    params: Query
+    extra_json: AnyMapping
+    idempotency_key: str
+
+
+# Sentinel class used until PEP 0661 is accepted
+class NotGiven:
+    """
+    A sentinel singleton class used to distinguish omitted keyword arguments
+    from those passed in with the value None (which may have different behavior).
+
+    For example:
+
+    ```py
+    def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...
+
+
+    get(timeout=1)  # 1s timeout
+    get(timeout=None)  # No timeout
+    get()  # Default timeout behavior, which may not be statically known at the method definition.
+    ```
+    """
+
+    def __bool__(self) -> Literal[False]:
+        return False
+
+    @override
+    def __repr__(self) -> str:
+        return "NOT_GIVEN"
+
+
+NotGivenOr = Union[_T, NotGiven]
+NOT_GIVEN = NotGiven()
+
+
+class Omit:
+    """In certain situations you need to be able to represent a case where a default value has
+    to be explicitly removed and `None` is not an appropriate substitute, for example:
+
+    ```py
+    # as the default `Content-Type` header is `application/json` that will be sent
+    client.post("/upload/files", files={"file": b"my raw file content"})
+
+    # you can't explicitly override the header as it has to be dynamically generated
+    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
+    client.post(..., headers={"Content-Type": "multipart/form-data"})
+
+    # instead you can remove the default `application/json` header by passing Omit
+    client.post(..., headers={"Content-Type": Omit()})
+    ```
+    """
+
+    def __bool__(self) -> Literal[False]:
+        return False
+
+
+@runtime_checkable
+class ModelBuilderProtocol(Protocol):
+    @classmethod
+    def build(
+        cls: type[_T],
+        *,
+        response: Response,
+        data: object,
+    ) -> _T: ...
+
+
+Headers = Mapping[str, Union[str, Omit]]
+
+
+class HeadersLikeProtocol(Protocol):
+    def get(self, __key: str) -> str | None: ...
+
+
+HeadersLike = Union[Headers, HeadersLikeProtocol]
+
+ResponseT = TypeVar(
+    "ResponseT",
+    bound=Union[
+        object,
+        str,
+        None,
+        "BaseModel",
+        List[Any],
+        Dict[str, Any],
+        Response,
+        ModelBuilderProtocol,
+        "APIResponse[Any]",
+        "AsyncAPIResponse[Any]",
+    ],
+)
+
+StrBytesIntFloat = Union[str, bytes, int, float]
+
+# Note: copied from Pydantic
+# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
+IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]
+
+PostParser = Callable[[Any], Any]
+
+
+@runtime_checkable
+class InheritsGeneric(Protocol):
+    """Represents a type that has inherited from `Generic`
+
+    The `__orig_bases__` property can be used to determine the resolved
+    type variable for a given base class.
+    """
+
+    __orig_bases__: tuple[_GenericAlias]
+
+
+class _GenericAlias(Protocol):
+    __origin__: type[object]
+
+
+class HttpxSendArgs(TypedDict, total=False):
+    auth: httpx.Auth
diff --git a/src/codex/_utils/__init__.py b/src/codex/_utils/__init__.py
new file mode 100644
index 00000000..d4fda26f
--- /dev/null
+++ b/src/codex/_utils/__init__.py
@@ -0,0 +1,57 @@
+from ._sync import asyncify as asyncify
+from ._proxy import LazyProxy as LazyProxy
+from ._utils import (
+    flatten as flatten,
+    is_dict as is_dict,
+    is_list as is_list,
+    is_given as is_given,
+    is_tuple as is_tuple,
+    json_safe as json_safe,
+    lru_cache as lru_cache,
+    is_mapping as is_mapping,
+    is_tuple_t as is_tuple_t,
+    parse_date as parse_date,
+    is_iterable as is_iterable,
+    is_sequence as is_sequence,
+    coerce_float as coerce_float,
+    is_mapping_t as is_mapping_t,
+    removeprefix as removeprefix,
+    removesuffix as removesuffix,
+    extract_files as extract_files,
+    is_sequence_t as is_sequence_t,
+    required_args as required_args,
+    coerce_boolean as coerce_boolean,
+    coerce_integer as coerce_integer,
+    file_from_path as file_from_path,
+    parse_datetime as parse_datetime,
+    strip_not_given as strip_not_given,
+    deepcopy_minimal as deepcopy_minimal,
+    get_async_library as get_async_library,
+    maybe_coerce_float as maybe_coerce_float,
+    get_required_header as get_required_header,
+    maybe_coerce_boolean as maybe_coerce_boolean,
+    maybe_coerce_integer as maybe_coerce_integer,
+)
+from ._typing import (
+    is_list_type as is_list_type,
+    is_union_type as is_union_type,
+    extract_type_arg as extract_type_arg,
+    is_iterable_type as is_iterable_type,
+    is_required_type as is_required_type,
+    is_annotated_type as is_annotated_type,
+    is_type_alias_type as is_type_alias_type,
+    strip_annotated_type as strip_annotated_type,
+    extract_type_var_from_base as extract_type_var_from_base,
+)
+from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
+from ._transform import (
+    PropertyInfo as PropertyInfo,
+    transform as transform,
+    async_transform as async_transform,
+    maybe_transform as maybe_transform,
+    async_maybe_transform as async_maybe_transform,
+)
+from ._reflection import (
+    function_has_argument as function_has_argument,
+    assert_signatures_in_sync as assert_signatures_in_sync,
+)
diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py
new file mode 100644
index 00000000..80932c4f
--- /dev/null
+++ b/src/codex/_utils/_logs.py
@@ -0,0 +1,25 @@
+import os
+import logging
+
+logger: logging.Logger = logging.getLogger("codex")
+httpx_logger: logging.Logger = logging.getLogger("httpx")
+
+
+def _basic_config() -> None:
+    # e.g. [2023-10-05 14:12:26 - codex._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
+    logging.basicConfig(
+        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
+        datefmt="%Y-%m-%d %H:%M:%S",
+    )
+
+
+def setup_logging() -> None:
+    env = os.environ.get("CODEX_LOG")
+    if env == "debug":
+        _basic_config()
+        logger.setLevel(logging.DEBUG)
+        httpx_logger.setLevel(logging.DEBUG)
+    elif env == "info":
+        _basic_config()
+        logger.setLevel(logging.INFO)
+        httpx_logger.setLevel(logging.INFO)
diff --git a/src/codex/_utils/_proxy.py b/src/codex/_utils/_proxy.py
new file mode 100644
index 00000000..ffd883e9
--- /dev/null
+++ b/src/codex/_utils/_proxy.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Generic, TypeVar, Iterable, cast
+from typing_extensions import override
+
+T = TypeVar("T")
+
+
+class LazyProxy(Generic[T], ABC):
+    """Implements data methods to pretend that an instance is another instance.
+
+    This includes forwarding attribute access and other methods.
+    """
+
+    # Note: we have to special case proxies that themselves return proxies
+    # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`
+
+    def __getattr__(self, attr: str) -> object:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied  # pyright: ignore
+        return getattr(proxied, attr)
+
+    @override
+    def __repr__(self) -> str:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied.__class__.__name__
+        return repr(self.__get_proxied__())
+
+    @override
+    def __str__(self) -> str:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return proxied.__class__.__name__
+        return str(proxied)
+
+    @override
+    def __dir__(self) -> Iterable[str]:
+        proxied = self.__get_proxied__()
+        if isinstance(proxied, LazyProxy):
+            return []
+        return proxied.__dir__()
+
+    @property  # type: ignore
+    @override
+    def __class__(self) -> type:  # pyright: ignore
+        proxied = self.__get_proxied__()
+        if issubclass(type(proxied), LazyProxy):
+            return type(proxied)
+        return proxied.__class__
+
+    def __get_proxied__(self) -> T:
+        return self.__load__()
+
+    def __as_proxied__(self) -> T:
+        """Helper method that returns the current proxy, typed as the loaded object"""
+        return cast(T, self)
+
+    @abstractmethod
+    def __load__(self) -> T: ...
diff --git a/src/codex/_utils/_reflection.py b/src/codex/_utils/_reflection.py
new file mode 100644
index 00000000..89aa712a
--- /dev/null
+++ b/src/codex/_utils/_reflection.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, Callable
+
+
+def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
+    """Returns whether or not the given function has a specific parameter"""
+    sig = inspect.signature(func)
+    return arg_name in sig.parameters
+
+
+def assert_signatures_in_sync(
+    source_func: Callable[..., Any],
+    check_func: Callable[..., Any],
+    *,
+    exclude_params: set[str] = set(),
+) -> None:
+    """Ensure that the signature of the second function matches the first."""
+
+    check_sig = inspect.signature(check_func)
+    source_sig = inspect.signature(source_func)
+
+    errors: list[str] = []
+
+    for name, source_param in source_sig.parameters.items():
+        if name in exclude_params:
+            continue
+
+        custom_param = check_sig.parameters.get(name)
+        if not custom_param:
+            errors.append(f"the `{name}` param is missing")
+            continue
+
+        if custom_param.annotation != source_param.annotation:
+            errors.append(
+                f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
+            )
+            continue
+
+    if errors:
+        raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))
diff --git a/src/codex/_utils/_streams.py b/src/codex/_utils/_streams.py
new file mode 100644
index 00000000..f4a0208f
--- /dev/null
+++ b/src/codex/_utils/_streams.py
@@ -0,0 +1,12 @@
+from typing import Any
+from typing_extensions import Iterator, AsyncIterator
+
+
+def consume_sync_iterator(iterator: Iterator[Any]) -> None:
+    for _ in iterator:
+        ...
+
+
+async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
+    async for _ in iterator:
+        ...
diff --git a/src/codex/_utils/_sync.py b/src/codex/_utils/_sync.py
new file mode 100644
index 00000000..8b3aaf2b
--- /dev/null
+++ b/src/codex/_utils/_sync.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+import sys
+import asyncio
+import functools
+import contextvars
+from typing import Any, TypeVar, Callable, Awaitable
+from typing_extensions import ParamSpec
+
+T_Retval = TypeVar("T_Retval")
+T_ParamSpec = ParamSpec("T_ParamSpec")
+
+
+if sys.version_info >= (3, 9):
+    to_thread = asyncio.to_thread
+else:
+    # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
+    # for Python 3.8 support
+    async def to_thread(
+        func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
+    ) -> Any:
+        """Asynchronously run function *func* in a separate thread.
+
+        Any *args and **kwargs supplied for this function are directly passed
+        to *func*. Also, the current :class:`contextvars.Context` is propagated,
+        allowing context variables from the main thread to be accessed in the
+        separate thread.
+
+        Returns a coroutine that can be awaited to get the eventual result of *func*.
+        """
+        loop = asyncio.events.get_running_loop()
+        ctx = contextvars.copy_context()
+        func_call = functools.partial(ctx.run, func, *args, **kwargs)
+        return await loop.run_in_executor(None, func_call)
+
+
+# inspired by `asyncer`, https://github.com/tiangolo/asyncer
+def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
+    """
+    Take a blocking function and create an async one that receives the same
+    positional and keyword arguments. For python version 3.9 and above, it uses
+    asyncio.to_thread to run the function in a separate thread. For python version
+    3.8, it uses locally defined copy of the asyncio.to_thread function which was
+    introduced in python 3.9.
+
+    Usage:
+
+    ```python
+    def blocking_func(arg1, arg2, kwarg1=None):
+        # blocking code
+        return result
+
+
+    result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
+    ```
+
+    ## Arguments
+
+    `function`: a blocking regular callable (e.g. a function)
+
+    ## Return
+
+    An async function that takes the same positional and keyword arguments as the
+    original one, that when called runs the same original function in a thread worker
+    and returns the result.
+    """
+
+    async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
+        return await to_thread(function, *args, **kwargs)
+
+    return wrapper
diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
new file mode 100644
index 00000000..a6b62cad
--- /dev/null
+++ b/src/codex/_utils/_transform.py
@@ -0,0 +1,392 @@
+from __future__ import annotations
+
+import io
+import base64
+import pathlib
+from typing import Any, Mapping, TypeVar, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, override, get_type_hints
+
+import anyio
+import pydantic
+
+from ._utils import (
+    is_list,
+    is_mapping,
+    is_iterable,
+)
+from .._files import is_base64_file_input
+from ._typing import (
+    is_list_type,
+    is_union_type,
+    extract_type_arg,
+    is_iterable_type,
+    is_required_type,
+    is_annotated_type,
+    strip_annotated_type,
+)
+from .._compat import model_dump, is_typeddict
+
+_T = TypeVar("_T")
+
+
+# TODO: support for drilling globals() and locals()
+# TODO: ensure works correctly with forward references in all cases
+
+
+PropertyFormat = Literal["iso8601", "base64", "custom"]
+
+
+class PropertyInfo:
+    """Metadata class to be used in Annotated types to provide information about a given type.
+
+    For example:
+
+    class MyParams(TypedDict):
+        account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]
+
+    This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
+    """
+
+    alias: str | None
+    format: PropertyFormat | None
+    format_template: str | None
+    discriminator: str | None
+
+    def __init__(
+        self,
+        *,
+        alias: str | None = None,
+        format: PropertyFormat | None = None,
+        format_template: str | None = None,
+        discriminator: str | None = None,
+    ) -> None:
+        self.alias = alias
+        self.format = format
+        self.format_template = format_template
+        self.discriminator = discriminator
+
+    @override
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"
+
+
+def maybe_transform(
+    data: object,
+    expected_type: object,
+) -> Any | None:
+    """Wrapper over `transform()` that allows `None` to be passed.
+
+    See `transform()` for more details.
+    """
+    if data is None:
+        return None
+    return transform(data, expected_type)
+
+
+# Wrapper over _transform_recursive providing fake types
+def transform(
+    data: _T,
+    expected_type: object,
+) -> _T:
+    """Transform dictionaries based off of type information from the given type, for example:
+
+    ```py
+    class Params(TypedDict, total=False):
+        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+    transformed = transform({"card_id": "<my card ID>"}, Params)
+    # {'cardID': '<my card ID>'}
+    ```
+
+    Any keys / data that does not have type information given will be included as is.
+
+    It should be noted that the transformations that this function does are not represented in the type system.
+    """
+    transformed = _transform_recursive(data, annotation=cast(type, expected_type))
+    return cast(_T, transformed)
+
+
+def _get_annotated_type(type_: type) -> type | None:
+    """If the given type is an `Annotated` type then it is returned, if not `None` is returned.
+
+    This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
+    """
+    if is_required_type(type_):
+        # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
+        type_ = get_args(type_)[0]
+
+    if is_annotated_type(type_):
+        return type_
+
+    return None
+
+
+def _maybe_transform_key(key: str, type_: type) -> str:
+    """Transform the given `data` based on the annotations provided in `type_`.
+
+    Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata.
+    """
+    annotated_type = _get_annotated_type(type_)
+    if annotated_type is None:
+        # no `Annotated` definition for this type, no transformation needed
+        return key
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
+            return annotation.alias
+
+    return key
+
+
+def _transform_recursive(
+    data: object,
+    *,
+    annotation: type,
+    inner_type: type | None = None,
+) -> object:
+    """Transform the given data against the expected type.
+
+    Args:
+        annotation: The direct type annotation given to the particular piece of data.
+            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+            the list can be transformed using the metadata from the container type.
+
+            Defaults to the same value as the `annotation` argument.
+    """
+    if inner_type is None:
+        inner_type = annotation
+
+    stripped_type = strip_annotated_type(inner_type)
+    if is_typeddict(stripped_type) and is_mapping(data):
+        return _transform_typeddict(data, stripped_type)
+
+    if (
+        # List[T]
+        (is_list_type(stripped_type) and is_list(data))
+        # Iterable[T]
+        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+    ):
+        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+        # intended as an iterable, so we don't transform it.
+        if isinstance(data, dict):
+            return cast(object, data)
+
+        inner_type = extract_type_arg(stripped_type, 0)
+        return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+    if is_union_type(stripped_type):
+        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+        #
+        # TODO: there may be edge cases where the same normalized field name will transform to two different names
+        # in different subtypes.
+        for subtype in get_args(stripped_type):
+            data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
+        return data
+
+    if isinstance(data, pydantic.BaseModel):
+        return model_dump(data, exclude_unset=True, mode="json")
+
+    annotated_type = _get_annotated_type(annotation)
+    if annotated_type is None:
+        return data
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+            return _format_data(data, annotation.format, annotation.format_template)
+
+    return data
+
+
+def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+    if isinstance(data, (date, datetime)):
+        if format_ == "iso8601":
+            return data.isoformat()
+
+        if format_ == "custom" and format_template is not None:
+            return data.strftime(format_template)
+
+    if format_ == "base64" and is_base64_file_input(data):
+        binary: str | bytes | None = None
+
+        if isinstance(data, pathlib.Path):
+            binary = data.read_bytes()
+        elif isinstance(data, io.IOBase):
+            binary = data.read()
+
+            if isinstance(binary, str):  # type: ignore[unreachable]
+                binary = binary.encode()
+
+        if not isinstance(binary, bytes):
+            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+        return base64.b64encode(binary).decode("ascii")
+
+    return data
+
+
+def _transform_typeddict(
+    data: Mapping[str, object],
+    expected_type: type,
+) -> Mapping[str, object]:
+    result: dict[str, object] = {}
+    annotations = get_type_hints(expected_type, include_extras=True)
+    for key, value in data.items():
+        type_ = annotations.get(key)
+        if type_ is None:
+            # we do not have a type annotation for this field, leave it as is
+            result[key] = value
+        else:
+            result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
+    return result
+
+
+async def async_maybe_transform(
+    data: object,
+    expected_type: object,
+) -> Any | None:
+    """Wrapper over `async_transform()` that allows `None` to be passed.
+
+    See `async_transform()` for more details.
+    """
+    if data is None:
+        return None
+    return await async_transform(data, expected_type)
+
+
+async def async_transform(
+    data: _T,
+    expected_type: object,
+) -> _T:
+    """Transform dictionaries based off of type information from the given type, for example:
+
+    ```py
+    class Params(TypedDict, total=False):
+        card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+    transformed = transform({"card_id": "<my card ID>"}, Params)
+    # {'cardID': '<my card ID>'}
+    ```
+
+    Any keys / data that does not have type information given will be included as is.
+
+    It should be noted that the transformations that this function does are not represented in the type system.
+    """
+    transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
+    return cast(_T, transformed)
+
+
+async def _async_transform_recursive(
+    data: object,
+    *,
+    annotation: type,
+    inner_type: type | None = None,
+) -> object:
+    """Transform the given data against the expected type.
+
+    Args:
+        annotation: The direct type annotation given to the particular piece of data.
+            This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+        inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+            is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+            the list can be transformed using the metadata from the container type.
+
+            Defaults to the same value as the `annotation` argument.
+    """
+    if inner_type is None:
+        inner_type = annotation
+
+    stripped_type = strip_annotated_type(inner_type)
+    if is_typeddict(stripped_type) and is_mapping(data):
+        return await _async_transform_typeddict(data, stripped_type)
+
+    if (
+        # List[T]
+        (is_list_type(stripped_type) and is_list(data))
+        # Iterable[T]
+        or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+    ):
+        # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+        # intended as an iterable, so we don't transform it.
+        if isinstance(data, dict):
+            return cast(object, data)
+
+        inner_type = extract_type_arg(stripped_type, 0)
+        return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+    if is_union_type(stripped_type):
+        # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+        #
+        # TODO: there may be edge cases where the same normalized field name will transform to two different names
+        # in different subtypes.
+        for subtype in get_args(stripped_type):
+            data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
+        return data
+
+    if isinstance(data, pydantic.BaseModel):
+        return model_dump(data, exclude_unset=True, mode="json")
+
+    annotated_type = _get_annotated_type(annotation)
+    if annotated_type is None:
+        return data
+
+    # ignore the first argument as it is the actual type
+    annotations = get_args(annotated_type)[1:]
+    for annotation in annotations:
+        if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+            return await _async_format_data(data, annotation.format, annotation.format_template)
+
+    return data
+
+
+async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+    if isinstance(data, (date, datetime)):
+        if format_ == "iso8601":
+            return data.isoformat()
+
+        if format_ == "custom" and format_template is not None:
+            return data.strftime(format_template)
+
+    if format_ == "base64" and is_base64_file_input(data):
+        binary: str | bytes | None = None
+
+        if isinstance(data, pathlib.Path):
+            binary = await anyio.Path(data).read_bytes()
+        elif isinstance(data, io.IOBase):
+            binary = data.read()
+
+            if isinstance(binary, str):  # type: ignore[unreachable]
+                binary = binary.encode()
+
+        if not isinstance(binary, bytes):
+            raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+        return base64.b64encode(binary).decode("ascii")
+
+    return data
+
+
+async def _async_transform_typeddict(
+    data: Mapping[str, object],
+    expected_type: type,
+) -> Mapping[str, object]:
+    result: dict[str, object] = {}
+    annotations = get_type_hints(expected_type, include_extras=True)
+    for key, value in data.items():
+        type_ = annotations.get(key)
+        if type_ is None:
+            # we do not have a type annotation for this field, leave it as is
+            result[key] = value
+        else:
+            result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
+    return result
diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py
new file mode 100644
index 00000000..278749b1
--- /dev/null
+++ b/src/codex/_utils/_typing.py
@@ -0,0 +1,149 @@
+from __future__ import annotations
+
+import sys
+import typing
+import typing_extensions
+from typing import Any, TypeVar, Iterable, cast
+from collections import abc as _c_abc
+from typing_extensions import (
+    TypeIs,
+    Required,
+    Annotated,
+    get_args,
+    get_origin,
+)
+
+from .._types import InheritsGeneric
+from .._compat import is_union as _is_union
+
+
+def is_annotated_type(typ: type) -> bool:
+    return get_origin(typ) == Annotated
+
+
+def is_list_type(typ: type) -> bool:
+    return (get_origin(typ) or typ) == list
+
+
+def is_iterable_type(typ: type) -> bool:
+    """If the given type is `typing.Iterable[T]`"""
+    origin = get_origin(typ) or typ
+    return origin == Iterable or origin == _c_abc.Iterable
+
+
+def is_union_type(typ: type) -> bool:
+    return _is_union(get_origin(typ))
+
+
+def is_required_type(typ: type) -> bool:
+    return get_origin(typ) == Required
+
+
+def is_typevar(typ: type) -> bool:
+    # type ignore is required because type checkers
+    # think this expression will always return False
+    return type(typ) == TypeVar  # type: ignore
+
+
+_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
+if sys.version_info >= (3, 12):
+    _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)
+
+
+def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
+    """Return whether the provided argument is an instance of `TypeAliasType`.
+
+    ```python
+    type Int = int
+    is_type_alias_type(Int)
+    # > True
+    Str = TypeAliasType("Str", str)
+    is_type_alias_type(Str)
+    # > True
+    ```
+    """
+    return isinstance(tp, _TYPE_ALIAS_TYPES)
+
+
+# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+def strip_annotated_type(typ: type) -> type:
+    if is_required_type(typ) or is_annotated_type(typ):
+        return strip_annotated_type(cast(type, get_args(typ)[0]))
+
+    return typ
+
+
+def extract_type_arg(typ: type, index: int) -> type:
+    args = get_args(typ)
+    try:
+        return cast(type, args[index])
+    except IndexError as err:
+        raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err
+
+
+def extract_type_var_from_base(
+    typ: type,
+    *,
+    generic_bases: tuple[type, ...],
+    index: int,
+    failure_message: str | None = None,
+) -> type:
+    """Given a type like `Foo[T]`, returns the generic type variable `T`.
+
+    This also handles the case where a concrete subclass is given, e.g.
+    ```py
+    class MyResponse(Foo[bytes]):
+        ...
+
+    extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
+    ```
+
+    And where a generic subclass is given:
+    ```py
+    _T = TypeVar('_T')
+    class MyResponse(Foo[_T]):
+        ...
+
+    extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
+    ```
+    """
+    cls = cast(object, get_origin(typ) or typ)
+    if cls in generic_bases:
+        # we're given the class directly
+        return extract_type_arg(typ, index)
+
+    # if a subclass is given
+    # ---
+    # this is needed as __orig_bases__ is not present in the typeshed stubs
+    # because it is intended to be for internal use only, however there does
+    # not seem to be a way to resolve generic TypeVars for inherited subclasses
+    # without using it.
+    if isinstance(cls, InheritsGeneric):
+        target_base_class: Any | None = None
+        for base in cls.__orig_bases__:
+            if base.__origin__ in generic_bases:
+                target_base_class = base
+                break
+
+        if target_base_class is None:
+            raise RuntimeError(
+                "Could not find the generic base class;\n"
+                "This should never happen;\n"
+                f"Does {cls} inherit from one of {generic_bases} ?"
+            )
+
+        extracted = extract_type_arg(target_base_class, index)
+        if is_typevar(extracted):
+            # If the extracted type argument is itself a type variable
+            # then that means the subclass itself is generic, so we have
+            # to resolve the type argument from the class itself, not
+            # the base class.
+            #
+            # Note: if there is more than 1 type argument, the subclass could
+            # change the ordering of the type arguments, this is not currently
+            # supported.
+            return extract_type_arg(typ, index)
+
+        return extracted
+
+    raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")
diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py
new file mode 100644
index 00000000..e5811bba
--- /dev/null
+++ b/src/codex/_utils/_utils.py
@@ -0,0 +1,414 @@
+from __future__ import annotations
+
+import os
+import re
+import inspect
+import functools
+from typing import (
+    Any,
+    Tuple,
+    Mapping,
+    TypeVar,
+    Callable,
+    Iterable,
+    Sequence,
+    cast,
+    overload,
+)
+from pathlib import Path
+from datetime import date, datetime
+from typing_extensions import TypeGuard
+
+import sniffio
+
+from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike
+from .._compat import parse_date as parse_date, parse_datetime as parse_datetime
+
+_T = TypeVar("_T")
+_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
+_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
+_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
+CallableT = TypeVar("CallableT", bound=Callable[..., Any])
+
+
+def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
+    return [item for sublist in t for item in sublist]
+
+
+def extract_files(
+    # TODO: this needs to take Dict but variance issues.....
+    # create protocol type ?
+    query: Mapping[str, object],
+    *,
+    paths: Sequence[Sequence[str]],
+) -> list[tuple[str, FileTypes]]:
+    """Recursively extract files from the given dictionary based on specified paths.
+
+    A path may look like this ['foo', 'files', '<array>', 'data'].
+
+    Note: this mutates the given dictionary.
+    """
+    files: list[tuple[str, FileTypes]] = []
+    for path in paths:
+        files.extend(_extract_items(query, path, index=0, flattened_key=None))
+    return files
+
+
+def _extract_items(
+    obj: object,
+    path: Sequence[str],
+    *,
+    index: int,
+    flattened_key: str | None,
+) -> list[tuple[str, FileTypes]]:
+    try:
+        key = path[index]
+    except IndexError:
+        if isinstance(obj, NotGiven):
+            # no value was provided - we can safely ignore
+            return []
+
+        # cyclical import
+        from .._files import assert_is_file_content
+
+        # We have exhausted the path, return the entry we found.
+        assert_is_file_content(obj, key=flattened_key)
+        assert flattened_key is not None
+        return [(flattened_key, cast(FileTypes, obj))]
+
+    index += 1
+    if is_dict(obj):
+        try:
+            # We are at the last entry in the path so we must remove the field
+            if (len(path)) == index:
+                item = obj.pop(key)
+            else:
+                item = obj[key]
+        except KeyError:
+            # Key was not present in the dictionary, this is not indicative of an error
+            # as the given path may not point to a required field. We also do not want
+            # to enforce required fields as the API may differ from the spec in some cases.
+            return []
+        if flattened_key is None:
+            flattened_key = key
+        else:
+            flattened_key += f"[{key}]"
+        return _extract_items(
+            item,
+            path,
+            index=index,
+            flattened_key=flattened_key,
+        )
+    elif is_list(obj):
+        if key != "<array>":
+            return []
+
+        return flatten(
+            [
+                _extract_items(
+                    item,
+                    path,
+                    index=index,
+                    flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
+                )
+                for item in obj
+            ]
+        )
+
+    # Something unexpected was passed, just ignore it.
+    return []
+
+
+def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]:
+    return not isinstance(obj, NotGiven)
+
+
+# Type safe methods for narrowing types with TypeVars.
+# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
+# however this cause Pyright to rightfully report errors. As we know we don't
+# care about the contained types we can safely use `object` in it's place.
+#
+# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
+# `is_*` is for when you're dealing with an unknown input
+# `is_*_t` is for when you're narrowing a known union type to a specific subset
+
+
+def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
+    return isinstance(obj, tuple)
+
+
+def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
+    return isinstance(obj, tuple)
+
+
+def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
+    return isinstance(obj, Sequence)
+
+
+def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
+    return isinstance(obj, Sequence)
+
+
+def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
+    return isinstance(obj, Mapping)
+
+
+def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
+    return isinstance(obj, Mapping)
+
+
+def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
+    return isinstance(obj, dict)
+
+
+def is_list(obj: object) -> TypeGuard[list[object]]:
+    return isinstance(obj, list)
+
+
+def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
+    return isinstance(obj, Iterable)
+
+
+def deepcopy_minimal(item: _T) -> _T:
+    """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
+
+    - mappings, e.g. `dict`
+    - list
+
+    This is done for performance reasons.
+    """
+    if is_mapping(item):
+        return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
+    if is_list(item):
+        return cast(_T, [deepcopy_minimal(entry) for entry in item])
+    return item
+
+
+# copied from https://github.com/Rapptz/RoboDanny
+def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
+    size = len(seq)
+    if size == 0:
+        return ""
+
+    if size == 1:
+        return seq[0]
+
+    if size == 2:
+        return f"{seq[0]} {final} {seq[1]}"
+
+    return delim.join(seq[:-1]) + f" {final} {seq[-1]}"
+
+
+def quote(string: str) -> str:
+    """Add single quotation marks around the given string. Does *not* do any escaping."""
+    return f"'{string}'"
+
+
+def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
+    """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.
+
+    Useful for enforcing runtime validation of overloaded functions.
+
+    Example usage:
+    ```py
+    @overload
+    def foo(*, a: str) -> str: ...
+
+
+    @overload
+    def foo(*, b: bool) -> str: ...
+
+
+    # This enforces the same constraints that a static type checker would
+    # i.e. that either a or b must be passed to the function
+    @required_args(["a"], ["b"])
+    def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
+    ```
+    """
+
+    def inner(func: CallableT) -> CallableT:
+        params = inspect.signature(func).parameters
+        positional = [
+            name
+            for name, param in params.items()
+            if param.kind
+            in {
+                param.POSITIONAL_ONLY,
+                param.POSITIONAL_OR_KEYWORD,
+            }
+        ]
+
+        @functools.wraps(func)
+        def wrapper(*args: object, **kwargs: object) -> object:
+            given_params: set[str] = set()
+            for i, _ in enumerate(args):
+                try:
+                    given_params.add(positional[i])
+                except IndexError:
+                    raise TypeError(
+                        f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
+                    ) from None
+
+            for key in kwargs.keys():
+                given_params.add(key)
+
+            for variant in variants:
+                matches = all((param in given_params for param in variant))
+                if matches:
+                    break
+            else:  # no break
+                if len(variants) > 1:
+                    variations = human_join(
+                        ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
+                    )
+                    msg = f"Missing required arguments; Expected either {variations} arguments to be given"
+                else:
+                    assert len(variants) > 0
+
+                    # TODO: this error message is not deterministic
+                    missing = list(set(variants[0]) - given_params)
+                    if len(missing) > 1:
+                        msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
+                    else:
+                        msg = f"Missing required argument: {quote(missing[0])}"
+                raise TypeError(msg)
+            return func(*args, **kwargs)
+
+        return wrapper  # type: ignore
+
+    return inner
+
+
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+
+
+@overload
+def strip_not_given(obj: None) -> None: ...
+
+
+@overload
+def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...
+
+
+@overload
+def strip_not_given(obj: object) -> object: ...
+
+
+def strip_not_given(obj: object | None) -> object:
+    """Remove all top-level keys where their values are instances of `NotGiven`"""
+    if obj is None:
+        return None
+
+    if not is_mapping(obj):
+        return obj
+
+    return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}
+
+
+def coerce_integer(val: str) -> int:
+    return int(val, base=10)
+
+
+def coerce_float(val: str) -> float:
+    return float(val)
+
+
+def coerce_boolean(val: str) -> bool:
+    return val == "true" or val == "1" or val == "on"
+
+
+def maybe_coerce_integer(val: str | None) -> int | None:
+    if val is None:
+        return None
+    return coerce_integer(val)
+
+
+def maybe_coerce_float(val: str | None) -> float | None:
+    if val is None:
+        return None
+    return coerce_float(val)
+
+
+def maybe_coerce_boolean(val: str | None) -> bool | None:
+    if val is None:
+        return None
+    return coerce_boolean(val)
+
+
+def removeprefix(string: str, prefix: str) -> str:
+    """Remove a prefix from a string.
+
+    Backport of `str.removeprefix` for Python < 3.9
+    """
+    if string.startswith(prefix):
+        return string[len(prefix) :]
+    return string
+
+
+def removesuffix(string: str, suffix: str) -> str:
+    """Remove a suffix from a string.
+
+    Backport of `str.removesuffix` for Python < 3.9
+    """
+    if string.endswith(suffix):
+        return string[: -len(suffix)]
+    return string
+
+
+def file_from_path(path: str) -> FileTypes:
+    contents = Path(path).read_bytes()
+    file_name = os.path.basename(path)
+    return (file_name, contents)
+
+
+def get_required_header(headers: HeadersLike, header: str) -> str:
+    lower_header = header.lower()
+    if is_mapping_t(headers):
+        # mypy doesn't understand the type narrowing here
+        for k, v in headers.items():  # type: ignore
+            if k.lower() == lower_header and isinstance(v, str):
+                return v
+
+    # to deal with the case where the header looks like Stainless-Event-Id
+    intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())
+
+    for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
+        value = headers.get(normalized_header)
+        if value:
+            return value
+
+    raise ValueError(f"Could not find {header} header")
+
+
+def get_async_library() -> str:
+    try:
+        return sniffio.current_async_library()
+    except Exception:
+        return "false"
+
+
+def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
+    """A version of functools.lru_cache that retains the type signature
+    for the wrapped function arguments.
+    """
+    wrapper = functools.lru_cache(  # noqa: TID251
+        maxsize=maxsize,
+    )
+    return cast(Any, wrapper)  # type: ignore[no-any-return]
+
+
+def json_safe(data: object) -> object:
+    """Translates a mapping / sequence recursively in the same fashion
+    as `pydantic` v2's `model_dump(mode="json")`.
+    """
+    if is_mapping(data):
+        return {json_safe(key): json_safe(value) for key, value in data.items()}
+
+    if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
+        return [json_safe(item) for item in data]
+
+    if isinstance(data, (datetime, date)):
+        return data.isoformat()
+
+    return data
diff --git a/src/codex/_version.py b/src/codex/_version.py
new file mode 100644
index 00000000..7ee46487
--- /dev/null
+++ b/src/codex/_version.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+__title__ = "codex"
+__version__ = "0.0.1-alpha.0"
diff --git a/src/codex/lib/.keep b/src/codex/lib/.keep
new file mode 100644
index 00000000..5e2c99fd
--- /dev/null
+++ b/src/codex/lib/.keep
@@ -0,0 +1,4 @@
+File generated from our OpenAPI spec by Stainless.
+
+This directory can be used to store custom files to expand the SDK.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
\ No newline at end of file
diff --git a/src/codex/py.typed b/src/codex/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/src/codex/resources/__init__.py b/src/codex/resources/__init__.py
new file mode 100644
index 00000000..b96b725a
--- /dev/null
+++ b/src/codex/resources/__init__.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .users import (
+    UsersResource,
+    AsyncUsersResource,
+    UsersResourceWithRawResponse,
+    AsyncUsersResourceWithRawResponse,
+    UsersResourceWithStreamingResponse,
+    AsyncUsersResourceWithStreamingResponse,
+)
+from .health import (
+    HealthResource,
+    AsyncHealthResource,
+    HealthResourceWithRawResponse,
+    AsyncHealthResourceWithRawResponse,
+    HealthResourceWithStreamingResponse,
+    AsyncHealthResourceWithStreamingResponse,
+)
+from .projects import (
+    ProjectsResource,
+    AsyncProjectsResource,
+    ProjectsResourceWithRawResponse,
+    AsyncProjectsResourceWithRawResponse,
+    ProjectsResourceWithStreamingResponse,
+    AsyncProjectsResourceWithStreamingResponse,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "HealthResource",
+    "AsyncHealthResource",
+    "HealthResourceWithRawResponse",
+    "AsyncHealthResourceWithRawResponse",
+    "HealthResourceWithStreamingResponse",
+    "AsyncHealthResourceWithStreamingResponse",
+    "OrganizationsResource",
+    "AsyncOrganizationsResource",
+    "OrganizationsResourceWithRawResponse",
+    "AsyncOrganizationsResourceWithRawResponse",
+    "OrganizationsResourceWithStreamingResponse",
+    "AsyncOrganizationsResourceWithStreamingResponse",
+    "UsersResource",
+    "AsyncUsersResource",
+    "UsersResourceWithRawResponse",
+    "AsyncUsersResourceWithRawResponse",
+    "UsersResourceWithStreamingResponse",
+    "AsyncUsersResourceWithStreamingResponse",
+    "ProjectsResource",
+    "AsyncProjectsResource",
+    "ProjectsResourceWithRawResponse",
+    "AsyncProjectsResourceWithRawResponse",
+    "ProjectsResourceWithStreamingResponse",
+    "AsyncProjectsResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py
new file mode 100644
index 00000000..29c374e6
--- /dev/null
+++ b/src/codex/resources/health.py
@@ -0,0 +1,235 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.health_check_response import HealthCheckResponse
+
+__all__ = ["HealthResource", "AsyncHealthResource"]
+
+
+class HealthResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> HealthResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return HealthResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> HealthResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return HealthResourceWithStreamingResponse(self)
+
+    def check(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the health of the application."""
+        return self._get(
+            "/api/health/",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+    def db(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the database connection."""
+        return self._get(
+            "/api/health/db",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+    def weaviate(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the weaviate connection."""
+        return self._get(
+            "/api/health/weaviate",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+
+class AsyncHealthResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncHealthResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncHealthResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncHealthResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncHealthResourceWithStreamingResponse(self)
+
+    async def check(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the health of the application."""
+        return await self._get(
+            "/api/health/",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+    async def db(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the database connection."""
+        return await self._get(
+            "/api/health/db",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+    async def weaviate(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> HealthCheckResponse:
+        """Check the weaviate connection."""
+        return await self._get(
+            "/api/health/weaviate",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=HealthCheckResponse,
+        )
+
+
+class HealthResourceWithRawResponse:
+    def __init__(self, health: HealthResource) -> None:
+        self._health = health
+
+        self.check = to_raw_response_wrapper(
+            health.check,
+        )
+        self.db = to_raw_response_wrapper(
+            health.db,
+        )
+        self.weaviate = to_raw_response_wrapper(
+            health.weaviate,
+        )
+
+
+class AsyncHealthResourceWithRawResponse:
+    def __init__(self, health: AsyncHealthResource) -> None:
+        self._health = health
+
+        self.check = async_to_raw_response_wrapper(
+            health.check,
+        )
+        self.db = async_to_raw_response_wrapper(
+            health.db,
+        )
+        self.weaviate = async_to_raw_response_wrapper(
+            health.weaviate,
+        )
+
+
+class HealthResourceWithStreamingResponse:
+    def __init__(self, health: HealthResource) -> None:
+        self._health = health
+
+        self.check = to_streamed_response_wrapper(
+            health.check,
+        )
+        self.db = to_streamed_response_wrapper(
+            health.db,
+        )
+        self.weaviate = to_streamed_response_wrapper(
+            health.weaviate,
+        )
+
+
+class AsyncHealthResourceWithStreamingResponse:
+    def __init__(self, health: AsyncHealthResource) -> None:
+        self._health = health
+
+        self.check = async_to_streamed_response_wrapper(
+            health.check,
+        )
+        self.db = async_to_streamed_response_wrapper(
+            health.db,
+        )
+        self.weaviate = async_to_streamed_response_wrapper(
+            health.weaviate,
+        )
diff --git a/src/codex/resources/organizations/__init__.py b/src/codex/resources/organizations/__init__.py
new file mode 100644
index 00000000..9dee3136
--- /dev/null
+++ b/src/codex/resources/organizations/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .billing import (
+    BillingResource,
+    AsyncBillingResource,
+    BillingResourceWithRawResponse,
+    AsyncBillingResourceWithRawResponse,
+    BillingResourceWithStreamingResponse,
+    AsyncBillingResourceWithStreamingResponse,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "BillingResource",
+    "AsyncBillingResource",
+    "BillingResourceWithRawResponse",
+    "AsyncBillingResourceWithRawResponse",
+    "BillingResourceWithStreamingResponse",
+    "AsyncBillingResourceWithStreamingResponse",
+    "OrganizationsResource",
+    "AsyncOrganizationsResource",
+    "OrganizationsResourceWithRawResponse",
+    "AsyncOrganizationsResourceWithRawResponse",
+    "OrganizationsResourceWithStreamingResponse",
+    "AsyncOrganizationsResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py
new file mode 100644
index 00000000..fb234032
--- /dev/null
+++ b/src/codex/resources/organizations/billing.py
@@ -0,0 +1,242 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema
+from ...types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema
+
+__all__ = ["BillingResource", "AsyncBillingResource"]
+
+
+class BillingResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> BillingResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return BillingResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> BillingResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return BillingResourceWithStreamingResponse(self)
+
+    def invoices(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingInvoicesSchema:
+        """
+        Get invoices iFrame URL for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/billing/invoices",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingInvoicesSchema,
+        )
+
+    def usage(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingUsageSchema:
+        """
+        Get usage iFrame URL for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/billing/usage",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingUsageSchema,
+        )
+
+
+class AsyncBillingResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncBillingResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncBillingResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncBillingResourceWithStreamingResponse(self)
+
+    async def invoices(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingInvoicesSchema:
+        """
+        Get invoices iFrame URL for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/billing/invoices",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingInvoicesSchema,
+        )
+
+    async def usage(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingUsageSchema:
+        """
+        Get usage iFrame URL for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/billing/usage",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingUsageSchema,
+        )
+
+
+class BillingResourceWithRawResponse:
+    def __init__(self, billing: BillingResource) -> None:
+        self._billing = billing
+
+        self.invoices = to_raw_response_wrapper(
+            billing.invoices,
+        )
+        self.usage = to_raw_response_wrapper(
+            billing.usage,
+        )
+
+
+class AsyncBillingResourceWithRawResponse:
+    def __init__(self, billing: AsyncBillingResource) -> None:
+        self._billing = billing
+
+        self.invoices = async_to_raw_response_wrapper(
+            billing.invoices,
+        )
+        self.usage = async_to_raw_response_wrapper(
+            billing.usage,
+        )
+
+
+class BillingResourceWithStreamingResponse:
+    def __init__(self, billing: BillingResource) -> None:
+        self._billing = billing
+
+        self.invoices = to_streamed_response_wrapper(
+            billing.invoices,
+        )
+        self.usage = to_streamed_response_wrapper(
+            billing.usage,
+        )
+
+
+class AsyncBillingResourceWithStreamingResponse:
+    def __init__(self, billing: AsyncBillingResource) -> None:
+        self._billing = billing
+
+        self.invoices = async_to_streamed_response_wrapper(
+            billing.invoices,
+        )
+        self.usage = async_to_streamed_response_wrapper(
+            billing.usage,
+        )
diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py
new file mode 100644
index 00000000..3aad77e0
--- /dev/null
+++ b/src/codex/resources/organizations/organizations.py
@@ -0,0 +1,195 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .billing import (
+    BillingResource,
+    AsyncBillingResource,
+    BillingResourceWithRawResponse,
+    AsyncBillingResourceWithRawResponse,
+    BillingResourceWithStreamingResponse,
+    AsyncBillingResourceWithStreamingResponse,
+)
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.organization_schema_public import OrganizationSchemaPublic
+
+__all__ = ["OrganizationsResource", "AsyncOrganizationsResource"]
+
+
+class OrganizationsResource(SyncAPIResource):
+    @cached_property
+    def billing(self) -> BillingResource:
+        return BillingResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return OrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return OrganizationsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationSchemaPublic:
+        """
+        Get a single organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationSchemaPublic,
+        )
+
+
+class AsyncOrganizationsResource(AsyncAPIResource):
+    @cached_property
+    def billing(self) -> AsyncBillingResource:
+        return AsyncBillingResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncOrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncOrganizationsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationSchemaPublic:
+        """
+        Get a single organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationSchemaPublic,
+        )
+
+
+class OrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.retrieve = to_raw_response_wrapper(
+            organizations.retrieve,
+        )
+
+    @cached_property
+    def billing(self) -> BillingResourceWithRawResponse:
+        return BillingResourceWithRawResponse(self._organizations.billing)
+
+
+class AsyncOrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.retrieve = async_to_raw_response_wrapper(
+            organizations.retrieve,
+        )
+
+    @cached_property
+    def billing(self) -> AsyncBillingResourceWithRawResponse:
+        return AsyncBillingResourceWithRawResponse(self._organizations.billing)
+
+
+class OrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.retrieve = to_streamed_response_wrapper(
+            organizations.retrieve,
+        )
+
+    @cached_property
+    def billing(self) -> BillingResourceWithStreamingResponse:
+        return BillingResourceWithStreamingResponse(self._organizations.billing)
+
+
+class AsyncOrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            organizations.retrieve,
+        )
+
+    @cached_property
+    def billing(self) -> AsyncBillingResourceWithStreamingResponse:
+        return AsyncBillingResourceWithStreamingResponse(self._organizations.billing)
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
new file mode 100644
index 00000000..5250b0b3
--- /dev/null
+++ b/src/codex/resources/projects/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .projects import (
+    ProjectsResource,
+    AsyncProjectsResource,
+    ProjectsResourceWithRawResponse,
+    AsyncProjectsResourceWithRawResponse,
+    ProjectsResourceWithStreamingResponse,
+    AsyncProjectsResourceWithStreamingResponse,
+)
+from .knowledge import (
+    KnowledgeResource,
+    AsyncKnowledgeResource,
+    KnowledgeResourceWithRawResponse,
+    AsyncKnowledgeResourceWithRawResponse,
+    KnowledgeResourceWithStreamingResponse,
+    AsyncKnowledgeResourceWithStreamingResponse,
+)
+from .access_keys import (
+    AccessKeysResource,
+    AsyncAccessKeysResource,
+    AccessKeysResourceWithRawResponse,
+    AsyncAccessKeysResourceWithRawResponse,
+    AccessKeysResourceWithStreamingResponse,
+    AsyncAccessKeysResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "AccessKeysResource",
+    "AsyncAccessKeysResource",
+    "AccessKeysResourceWithRawResponse",
+    "AsyncAccessKeysResourceWithRawResponse",
+    "AccessKeysResourceWithStreamingResponse",
+    "AsyncAccessKeysResourceWithStreamingResponse",
+    "KnowledgeResource",
+    "AsyncKnowledgeResource",
+    "KnowledgeResourceWithRawResponse",
+    "AsyncKnowledgeResourceWithRawResponse",
+    "KnowledgeResourceWithStreamingResponse",
+    "AsyncKnowledgeResourceWithStreamingResponse",
+    "ProjectsResource",
+    "AsyncProjectsResource",
+    "ProjectsResourceWithRawResponse",
+    "AsyncProjectsResourceWithRawResponse",
+    "ProjectsResourceWithStreamingResponse",
+    "AsyncProjectsResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
new file mode 100644
index 00000000..7fc21ae9
--- /dev/null
+++ b/src/codex/resources/projects/access_keys.py
@@ -0,0 +1,594 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
+from ..._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import access_key_create_params, access_key_update_params
+from ...types.projects.access_key_schema import AccessKeySchema
+from ...types.projects.access_key_list_response import AccessKeyListResponse
+
+__all__ = ["AccessKeysResource", "AsyncAccessKeysResource"]
+
+
+class AccessKeysResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AccessKeysResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AccessKeysResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AccessKeysResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        project_id: int,
+        *,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Create a new access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/access_keys/",
+            body=maybe_transform(
+                {
+                    "name": name,
+                    "description": description,
+                    "expires_at": expires_at,
+                },
+                access_key_create_params.AccessKeyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    def retrieve(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Get a single access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    def update(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Update an existing access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._put(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            body=maybe_transform(
+                {
+                    "name": name,
+                    "description": description,
+                    "expires_at": expires_at,
+                },
+                access_key_update_params.AccessKeyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    def list(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeyListResponse:
+        """
+        List all access keys for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}/access_keys/",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeyListResponse,
+        )
+
+    def delete(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete an existing access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._delete(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    def revoke(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Revoke an access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._post(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+
+class AsyncAccessKeysResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncAccessKeysResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncAccessKeysResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        project_id: int,
+        *,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Create a new access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/access_keys/",
+            body=await async_maybe_transform(
+                {
+                    "name": name,
+                    "description": description,
+                    "expires_at": expires_at,
+                },
+                access_key_create_params.AccessKeyCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    async def retrieve(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Get a single access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    async def update(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeySchema:
+        """
+        Update an existing access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._put(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            body=await async_maybe_transform(
+                {
+                    "name": name,
+                    "description": description,
+                    "expires_at": expires_at,
+                },
+                access_key_update_params.AccessKeyUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeySchema,
+        )
+
+    async def list(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeyListResponse:
+        """
+        List all access keys for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}/access_keys/",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=AccessKeyListResponse,
+        )
+
+    async def delete(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete an existing access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._delete(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    async def revoke(
+        self,
+        access_key_id: int,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Revoke an access key.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._post(
+            f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+
+class AccessKeysResourceWithRawResponse:
+    def __init__(self, access_keys: AccessKeysResource) -> None:
+        self._access_keys = access_keys
+
+        self.create = to_raw_response_wrapper(
+            access_keys.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            access_keys.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            access_keys.update,
+        )
+        self.list = to_raw_response_wrapper(
+            access_keys.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            access_keys.delete,
+        )
+        self.revoke = to_raw_response_wrapper(
+            access_keys.revoke,
+        )
+
+
+class AsyncAccessKeysResourceWithRawResponse:
+    def __init__(self, access_keys: AsyncAccessKeysResource) -> None:
+        self._access_keys = access_keys
+
+        self.create = async_to_raw_response_wrapper(
+            access_keys.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            access_keys.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            access_keys.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            access_keys.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            access_keys.delete,
+        )
+        self.revoke = async_to_raw_response_wrapper(
+            access_keys.revoke,
+        )
+
+
+class AccessKeysResourceWithStreamingResponse:
+    def __init__(self, access_keys: AccessKeysResource) -> None:
+        self._access_keys = access_keys
+
+        self.create = to_streamed_response_wrapper(
+            access_keys.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            access_keys.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            access_keys.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            access_keys.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            access_keys.delete,
+        )
+        self.revoke = to_streamed_response_wrapper(
+            access_keys.revoke,
+        )
+
+
+class AsyncAccessKeysResourceWithStreamingResponse:
+    def __init__(self, access_keys: AsyncAccessKeysResource) -> None:
+        self._access_keys = access_keys
+
+        self.create = async_to_streamed_response_wrapper(
+            access_keys.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            access_keys.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            access_keys.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            access_keys.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            access_keys.delete,
+        )
+        self.revoke = async_to_streamed_response_wrapper(
+            access_keys.revoke,
+        )
diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py
new file mode 100644
index 00000000..6005f9cc
--- /dev/null
+++ b/src/codex/resources/projects/knowledge.py
@@ -0,0 +1,740 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
+from ..._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import (
+    knowledge_list_params,
+    knowledge_query_params,
+    knowledge_create_params,
+    knowledge_update_params,
+    knowledge_add_question_params,
+)
+from ...types.projects.entry import Entry
+from ...types.projects.list_knowledge_response import ListKnowledgeResponse
+
+__all__ = ["KnowledgeResource", "AsyncKnowledgeResource"]
+
+
+class KnowledgeResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> KnowledgeResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return KnowledgeResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return KnowledgeResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Create a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/knowledge/",
+            body=maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                },
+                knowledge_create_params.KnowledgeCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def retrieve(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Get a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def update(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        question: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Update a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._put(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            body=maybe_transform(
+                {
+                    "answer": answer,
+                    "question": question,
+                },
+                knowledge_update_params.KnowledgeUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def list(
+        self,
+        project_id: int,
+        *,
+        answered_only: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        unanswered_only: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ListKnowledgeResponse:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}/knowledge/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "answered_only": answered_only,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "unanswered_only": unanswered_only,
+                    },
+                    knowledge_list_params.KnowledgeListParams,
+                ),
+            ),
+            cast_to=ListKnowledgeResponse,
+        )
+
+    def delete(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._delete(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    def add_question(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Add a question to a project.
+
+        Returns: 201 Created if a new question was added 200 OK if the question already
+        existed
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/knowledge/add_question",
+            body=maybe_transform({"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def query(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[Entry]:
+        """
+        Query knowledge for a project.
+
+        Returns the matching entry if found and answered, otherwise returns None. This
+        allows the client to distinguish between: (1) no matching question found
+        (returns None), and (2) matching question found but not yet answered (returns
+        Entry with answer=None).
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/knowledge/query",
+            body=maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+
+class AsyncKnowledgeResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncKnowledgeResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncKnowledgeResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Create a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/knowledge/",
+            body=await async_maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                },
+                knowledge_create_params.KnowledgeCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def retrieve(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Get a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def update(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        question: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Update a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._put(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            body=await async_maybe_transform(
+                {
+                    "answer": answer,
+                    "question": question,
+                },
+                knowledge_update_params.KnowledgeUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def list(
+        self,
+        project_id: int,
+        *,
+        answered_only: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        unanswered_only: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ListKnowledgeResponse:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}/knowledge/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "answered_only": answered_only,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "unanswered_only": unanswered_only,
+                    },
+                    knowledge_list_params.KnowledgeListParams,
+                ),
+            ),
+            cast_to=ListKnowledgeResponse,
+        )
+
+    async def delete(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._delete(
+            f"/api/projects/{project_id}/knowledge/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    async def add_question(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Add a question to a project.
+
+        Returns: 201 Created if a new question was added 200 OK if the question already
+        existed
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/knowledge/add_question",
+            body=await async_maybe_transform(
+                {"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def query(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[Entry]:
+        """
+        Query knowledge for a project.
+
+        Returns the matching entry if found and answered, otherwise returns None. This
+        allows the client to distinguish between: (1) no matching question found
+        (returns None), and (2) matching question found but not yet answered (returns
+        Entry with answer=None).
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/knowledge/query",
+            body=await async_maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+
+class KnowledgeResourceWithRawResponse:
+    def __init__(self, knowledge: KnowledgeResource) -> None:
+        self._knowledge = knowledge
+
+        self.create = to_raw_response_wrapper(
+            knowledge.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            knowledge.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            knowledge.update,
+        )
+        self.list = to_raw_response_wrapper(
+            knowledge.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            knowledge.delete,
+        )
+        self.add_question = to_raw_response_wrapper(
+            knowledge.add_question,
+        )
+        self.query = to_raw_response_wrapper(
+            knowledge.query,
+        )
+
+
+class AsyncKnowledgeResourceWithRawResponse:
+    def __init__(self, knowledge: AsyncKnowledgeResource) -> None:
+        self._knowledge = knowledge
+
+        self.create = async_to_raw_response_wrapper(
+            knowledge.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            knowledge.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            knowledge.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            knowledge.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            knowledge.delete,
+        )
+        self.add_question = async_to_raw_response_wrapper(
+            knowledge.add_question,
+        )
+        self.query = async_to_raw_response_wrapper(
+            knowledge.query,
+        )
+
+
+class KnowledgeResourceWithStreamingResponse:
+    def __init__(self, knowledge: KnowledgeResource) -> None:
+        self._knowledge = knowledge
+
+        self.create = to_streamed_response_wrapper(
+            knowledge.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            knowledge.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            knowledge.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            knowledge.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            knowledge.delete,
+        )
+        self.add_question = to_streamed_response_wrapper(
+            knowledge.add_question,
+        )
+        self.query = to_streamed_response_wrapper(
+            knowledge.query,
+        )
+
+
+class AsyncKnowledgeResourceWithStreamingResponse:
+    def __init__(self, knowledge: AsyncKnowledgeResource) -> None:
+        self._knowledge = knowledge
+
+        self.create = async_to_streamed_response_wrapper(
+            knowledge.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            knowledge.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            knowledge.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            knowledge.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            knowledge.delete,
+        )
+        self.add_question = async_to_streamed_response_wrapper(
+            knowledge.add_question,
+        )
+        self.query = async_to_streamed_response_wrapper(
+            knowledge.query,
+        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
new file mode 100644
index 00000000..20e95366
--- /dev/null
+++ b/src/codex/resources/projects/projects.py
@@ -0,0 +1,659 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import project_list_params, project_create_params, project_update_params
+from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
+from ..._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
+from ..._compat import cached_property
+from .knowledge import (
+    KnowledgeResource,
+    AsyncKnowledgeResource,
+    KnowledgeResourceWithRawResponse,
+    AsyncKnowledgeResourceWithRawResponse,
+    KnowledgeResourceWithStreamingResponse,
+    AsyncKnowledgeResourceWithStreamingResponse,
+)
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .access_keys import (
+    AccessKeysResource,
+    AsyncAccessKeysResource,
+    AccessKeysResourceWithRawResponse,
+    AsyncAccessKeysResourceWithRawResponse,
+    AccessKeysResourceWithStreamingResponse,
+    AsyncAccessKeysResourceWithStreamingResponse,
+)
+from ..._base_client import make_request_options
+from ...types.project_list_response import ProjectListResponse
+from ...types.project_return_schema import ProjectReturnSchema
+
+__all__ = ["ProjectsResource", "AsyncProjectsResource"]
+
+
+class ProjectsResource(SyncAPIResource):
+    @cached_property
+    def access_keys(self) -> AccessKeysResource:
+        return AccessKeysResource(self._client)
+
+    @cached_property
+    def knowledge(self) -> KnowledgeResource:
+        return KnowledgeResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> ProjectsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return ProjectsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return ProjectsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        *,
+        config: project_create_params.Config,
+        name: str,
+        organization_id: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Create a new project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/api/projects/",
+            body=maybe_transform(
+                {
+                    "config": config,
+                    "name": name,
+                    "organization_id": organization_id,
+                    "description": description,
+                },
+                project_create_params.ProjectCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    def retrieve(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Get a single project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    def update(
+        self,
+        project_id: int,
+        *,
+        config: project_update_params.Config,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._put(
+            f"/api/projects/{project_id}",
+            body=maybe_transform(
+                {
+                    "config": config,
+                    "name": name,
+                    "description": description,
+                },
+                project_update_params.ProjectUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    def list(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectListResponse:
+        """
+        List projects for organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            "/api/projects/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform({"organization_id": organization_id}, project_list_params.ProjectListParams),
+            ),
+            cast_to=ProjectListResponse,
+        )
+
+    def delete(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._delete(
+            f"/api/projects/{project_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    def export(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Export all data for a project as a JSON file.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}/export",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class AsyncProjectsResource(AsyncAPIResource):
+    @cached_property
+    def access_keys(self) -> AsyncAccessKeysResource:
+        return AsyncAccessKeysResource(self._client)
+
+    @cached_property
+    def knowledge(self) -> AsyncKnowledgeResource:
+        return AsyncKnowledgeResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncProjectsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncProjectsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        *,
+        config: project_create_params.Config,
+        name: str,
+        organization_id: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Create a new project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/api/projects/",
+            body=await async_maybe_transform(
+                {
+                    "config": config,
+                    "name": name,
+                    "organization_id": organization_id,
+                    "description": description,
+                },
+                project_create_params.ProjectCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    async def retrieve(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Get a single project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    async def update(
+        self,
+        project_id: int,
+        *,
+        config: project_update_params.Config,
+        name: str,
+        description: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._put(
+            f"/api/projects/{project_id}",
+            body=await async_maybe_transform(
+                {
+                    "config": config,
+                    "name": name,
+                    "description": description,
+                },
+                project_update_params.ProjectUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    async def list(
+        self,
+        *,
+        organization_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectListResponse:
+        """
+        List projects for organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            "/api/projects/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {"organization_id": organization_id}, project_list_params.ProjectListParams
+                ),
+            ),
+            cast_to=ProjectListResponse,
+        )
+
+    async def delete(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._delete(
+            f"/api/projects/{project_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    async def export(
+        self,
+        project_id: int,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Export all data for a project as a JSON file.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}/export",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
+
+class ProjectsResourceWithRawResponse:
+    def __init__(self, projects: ProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = to_raw_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            projects.update,
+        )
+        self.list = to_raw_response_wrapper(
+            projects.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            projects.delete,
+        )
+        self.export = to_raw_response_wrapper(
+            projects.export,
+        )
+
+    @cached_property
+    def access_keys(self) -> AccessKeysResourceWithRawResponse:
+        return AccessKeysResourceWithRawResponse(self._projects.access_keys)
+
+    @cached_property
+    def knowledge(self) -> KnowledgeResourceWithRawResponse:
+        return KnowledgeResourceWithRawResponse(self._projects.knowledge)
+
+
+class AsyncProjectsResourceWithRawResponse:
+    def __init__(self, projects: AsyncProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = async_to_raw_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            projects.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            projects.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            projects.delete,
+        )
+        self.export = async_to_raw_response_wrapper(
+            projects.export,
+        )
+
+    @cached_property
+    def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
+        return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys)
+
+    @cached_property
+    def knowledge(self) -> AsyncKnowledgeResourceWithRawResponse:
+        return AsyncKnowledgeResourceWithRawResponse(self._projects.knowledge)
+
+
+class ProjectsResourceWithStreamingResponse:
+    def __init__(self, projects: ProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = to_streamed_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            projects.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            projects.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            projects.delete,
+        )
+        self.export = to_streamed_response_wrapper(
+            projects.export,
+        )
+
+    @cached_property
+    def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
+        return AccessKeysResourceWithStreamingResponse(self._projects.access_keys)
+
+    @cached_property
+    def knowledge(self) -> KnowledgeResourceWithStreamingResponse:
+        return KnowledgeResourceWithStreamingResponse(self._projects.knowledge)
+
+
+class AsyncProjectsResourceWithStreamingResponse:
+    def __init__(self, projects: AsyncProjectsResource) -> None:
+        self._projects = projects
+
+        self.create = async_to_streamed_response_wrapper(
+            projects.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            projects.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            projects.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            projects.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            projects.delete,
+        )
+        self.export = async_to_streamed_response_wrapper(
+            projects.export,
+        )
+
+    @cached_property
+    def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
+        return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys)
+
+    @cached_property
+    def knowledge(self) -> AsyncKnowledgeResourceWithStreamingResponse:
+        return AsyncKnowledgeResourceWithStreamingResponse(self._projects.knowledge)
diff --git a/src/codex/resources/users/__init__.py b/src/codex/resources/users/__init__.py
new file mode 100644
index 00000000..18ed37eb
--- /dev/null
+++ b/src/codex/resources/users/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .users import (
+    UsersResource,
+    AsyncUsersResource,
+    UsersResourceWithRawResponse,
+    AsyncUsersResourceWithRawResponse,
+    UsersResourceWithStreamingResponse,
+    AsyncUsersResourceWithStreamingResponse,
+)
+from .myself import (
+    MyselfResource,
+    AsyncMyselfResource,
+    MyselfResourceWithRawResponse,
+    AsyncMyselfResourceWithRawResponse,
+    MyselfResourceWithStreamingResponse,
+    AsyncMyselfResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "MyselfResource",
+    "AsyncMyselfResource",
+    "MyselfResourceWithRawResponse",
+    "AsyncMyselfResourceWithRawResponse",
+    "MyselfResourceWithStreamingResponse",
+    "AsyncMyselfResourceWithStreamingResponse",
+    "UsersResource",
+    "AsyncUsersResource",
+    "UsersResourceWithRawResponse",
+    "AsyncUsersResourceWithRawResponse",
+    "UsersResourceWithStreamingResponse",
+    "AsyncUsersResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/users/myself/__init__.py b/src/codex/resources/users/myself/__init__.py
new file mode 100644
index 00000000..62070efa
--- /dev/null
+++ b/src/codex/resources/users/myself/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .myself import (
+    MyselfResource,
+    AsyncMyselfResource,
+    MyselfResourceWithRawResponse,
+    AsyncMyselfResourceWithRawResponse,
+    MyselfResourceWithStreamingResponse,
+    AsyncMyselfResourceWithStreamingResponse,
+)
+from .api_key import (
+    APIKeyResource,
+    AsyncAPIKeyResource,
+    APIKeyResourceWithRawResponse,
+    AsyncAPIKeyResourceWithRawResponse,
+    APIKeyResourceWithStreamingResponse,
+    AsyncAPIKeyResourceWithStreamingResponse,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "APIKeyResource",
+    "AsyncAPIKeyResource",
+    "APIKeyResourceWithRawResponse",
+    "AsyncAPIKeyResourceWithRawResponse",
+    "APIKeyResourceWithStreamingResponse",
+    "AsyncAPIKeyResourceWithStreamingResponse",
+    "OrganizationsResource",
+    "AsyncOrganizationsResource",
+    "OrganizationsResourceWithRawResponse",
+    "AsyncOrganizationsResourceWithRawResponse",
+    "OrganizationsResourceWithStreamingResponse",
+    "AsyncOrganizationsResourceWithStreamingResponse",
+    "MyselfResource",
+    "AsyncMyselfResource",
+    "MyselfResourceWithRawResponse",
+    "AsyncMyselfResourceWithRawResponse",
+    "MyselfResourceWithStreamingResponse",
+    "AsyncMyselfResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py
new file mode 100644
index 00000000..f4108375
--- /dev/null
+++ b/src/codex/resources/users/myself/api_key.py
@@ -0,0 +1,135 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.users.user_schema import UserSchema
+
+__all__ = ["APIKeyResource", "AsyncAPIKeyResource"]
+
+
+class APIKeyResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> APIKeyResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return APIKeyResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return APIKeyResourceWithStreamingResponse(self)
+
+    def refresh(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserSchema:
+        """Refresh the API key for an authenticated user"""
+        return self._post(
+            "/api/users/myself/api-key/refresh",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserSchema,
+        )
+
+
+class AsyncAPIKeyResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncAPIKeyResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncAPIKeyResourceWithStreamingResponse(self)
+
+    async def refresh(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserSchema:
+        """Refresh the API key for an authenticated user"""
+        return await self._post(
+            "/api/users/myself/api-key/refresh",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserSchema,
+        )
+
+
+class APIKeyResourceWithRawResponse:
+    def __init__(self, api_key: APIKeyResource) -> None:
+        self._api_key = api_key
+
+        self.refresh = to_raw_response_wrapper(
+            api_key.refresh,
+        )
+
+
+class AsyncAPIKeyResourceWithRawResponse:
+    def __init__(self, api_key: AsyncAPIKeyResource) -> None:
+        self._api_key = api_key
+
+        self.refresh = async_to_raw_response_wrapper(
+            api_key.refresh,
+        )
+
+
+class APIKeyResourceWithStreamingResponse:
+    def __init__(self, api_key: APIKeyResource) -> None:
+        self._api_key = api_key
+
+        self.refresh = to_streamed_response_wrapper(
+            api_key.refresh,
+        )
+
+
+class AsyncAPIKeyResourceWithStreamingResponse:
+    def __init__(self, api_key: AsyncAPIKeyResource) -> None:
+        self._api_key = api_key
+
+        self.refresh = async_to_streamed_response_wrapper(
+            api_key.refresh,
+        )
diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py
new file mode 100644
index 00000000..51d0b16b
--- /dev/null
+++ b/src/codex/resources/users/myself/myself.py
@@ -0,0 +1,199 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .api_key import (
+    APIKeyResource,
+    AsyncAPIKeyResource,
+    APIKeyResourceWithRawResponse,
+    AsyncAPIKeyResourceWithRawResponse,
+    APIKeyResourceWithStreamingResponse,
+    AsyncAPIKeyResourceWithStreamingResponse,
+)
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .organizations import (
+    OrganizationsResource,
+    AsyncOrganizationsResource,
+    OrganizationsResourceWithRawResponse,
+    AsyncOrganizationsResourceWithRawResponse,
+    OrganizationsResourceWithStreamingResponse,
+    AsyncOrganizationsResourceWithStreamingResponse,
+)
+from ...._base_client import make_request_options
+from ....types.users.user_schema_public import UserSchemaPublic
+
+__all__ = ["MyselfResource", "AsyncMyselfResource"]
+
+
+class MyselfResource(SyncAPIResource):
+    @cached_property
+    def api_key(self) -> APIKeyResource:
+        return APIKeyResource(self._client)
+
+    @cached_property
+    def organizations(self) -> OrganizationsResource:
+        return OrganizationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> MyselfResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return MyselfResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> MyselfResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return MyselfResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserSchemaPublic:
+        """Get user info for frontend."""
+        return self._get(
+            "/api/users/myself",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserSchemaPublic,
+        )
+
+
+class AsyncMyselfResource(AsyncAPIResource):
+    @cached_property
+    def api_key(self) -> AsyncAPIKeyResource:
+        return AsyncAPIKeyResource(self._client)
+
+    @cached_property
+    def organizations(self) -> AsyncOrganizationsResource:
+        return AsyncOrganizationsResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncMyselfResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncMyselfResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncMyselfResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserSchemaPublic:
+        """Get user info for frontend."""
+        return await self._get(
+            "/api/users/myself",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserSchemaPublic,
+        )
+
+
+class MyselfResourceWithRawResponse:
+    def __init__(self, myself: MyselfResource) -> None:
+        self._myself = myself
+
+        self.retrieve = to_raw_response_wrapper(
+            myself.retrieve,
+        )
+
+    @cached_property
+    def api_key(self) -> APIKeyResourceWithRawResponse:
+        return APIKeyResourceWithRawResponse(self._myself.api_key)
+
+    @cached_property
+    def organizations(self) -> OrganizationsResourceWithRawResponse:
+        return OrganizationsResourceWithRawResponse(self._myself.organizations)
+
+
+class AsyncMyselfResourceWithRawResponse:
+    def __init__(self, myself: AsyncMyselfResource) -> None:
+        self._myself = myself
+
+        self.retrieve = async_to_raw_response_wrapper(
+            myself.retrieve,
+        )
+
+    @cached_property
+    def api_key(self) -> AsyncAPIKeyResourceWithRawResponse:
+        return AsyncAPIKeyResourceWithRawResponse(self._myself.api_key)
+
+    @cached_property
+    def organizations(self) -> AsyncOrganizationsResourceWithRawResponse:
+        return AsyncOrganizationsResourceWithRawResponse(self._myself.organizations)
+
+
+class MyselfResourceWithStreamingResponse:
+    def __init__(self, myself: MyselfResource) -> None:
+        self._myself = myself
+
+        self.retrieve = to_streamed_response_wrapper(
+            myself.retrieve,
+        )
+
+    @cached_property
+    def api_key(self) -> APIKeyResourceWithStreamingResponse:
+        return APIKeyResourceWithStreamingResponse(self._myself.api_key)
+
+    @cached_property
+    def organizations(self) -> OrganizationsResourceWithStreamingResponse:
+        return OrganizationsResourceWithStreamingResponse(self._myself.organizations)
+
+
+class AsyncMyselfResourceWithStreamingResponse:
+    def __init__(self, myself: AsyncMyselfResource) -> None:
+        self._myself = myself
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            myself.retrieve,
+        )
+
+    @cached_property
+    def api_key(self) -> AsyncAPIKeyResourceWithStreamingResponse:
+        return AsyncAPIKeyResourceWithStreamingResponse(self._myself.api_key)
+
+    @cached_property
+    def organizations(self) -> AsyncOrganizationsResourceWithStreamingResponse:
+        return AsyncOrganizationsResourceWithStreamingResponse(self._myself.organizations)
diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py
new file mode 100644
index 00000000..5c37dd69
--- /dev/null
+++ b/src/codex/resources/users/myself/organizations.py
@@ -0,0 +1,135 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.users.myself.user_organizations_schema import UserOrganizationsSchema
+
+__all__ = ["OrganizationsResource", "AsyncOrganizationsResource"]
+
+
+class OrganizationsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return OrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return OrganizationsResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserOrganizationsSchema:
+        """Get the organizations for an authenticated user"""
+        return self._get(
+            "/api/users/myself/organizations",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserOrganizationsSchema,
+        )
+
+
+class AsyncOrganizationsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncOrganizationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncOrganizationsResourceWithStreamingResponse(self)
+
+    async def list(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> UserOrganizationsSchema:
+        """Get the organizations for an authenticated user"""
+        return await self._get(
+            "/api/users/myself/organizations",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=UserOrganizationsSchema,
+        )
+
+
+class OrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.list = to_raw_response_wrapper(
+            organizations.list,
+        )
+
+
+class AsyncOrganizationsResourceWithRawResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.list = async_to_raw_response_wrapper(
+            organizations.list,
+        )
+
+
+class OrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: OrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.list = to_streamed_response_wrapper(
+            organizations.list,
+        )
+
+
+class AsyncOrganizationsResourceWithStreamingResponse:
+    def __init__(self, organizations: AsyncOrganizationsResource) -> None:
+        self._organizations = organizations
+
+        self.list = async_to_streamed_response_wrapper(
+            organizations.list,
+        )
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
new file mode 100644
index 00000000..4d229677
--- /dev/null
+++ b/src/codex/resources/users/users.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .myself.myself import (
+    MyselfResource,
+    AsyncMyselfResource,
+    MyselfResourceWithRawResponse,
+    AsyncMyselfResourceWithRawResponse,
+    MyselfResourceWithStreamingResponse,
+    AsyncMyselfResourceWithStreamingResponse,
+)
+
+__all__ = ["UsersResource", "AsyncUsersResource"]
+
+
+class UsersResource(SyncAPIResource):
+    @cached_property
+    def myself(self) -> MyselfResource:
+        return MyselfResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> UsersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return UsersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> UsersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return UsersResourceWithStreamingResponse(self)
+
+
+class AsyncUsersResource(AsyncAPIResource):
+    @cached_property
+    def myself(self) -> AsyncMyselfResource:
+        return AsyncMyselfResource(self._client)
+
+    @cached_property
+    def with_raw_response(self) -> AsyncUsersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncUsersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        """
+        return AsyncUsersResourceWithStreamingResponse(self)
+
+
+class UsersResourceWithRawResponse:
+    def __init__(self, users: UsersResource) -> None:
+        self._users = users
+
+    @cached_property
+    def myself(self) -> MyselfResourceWithRawResponse:
+        return MyselfResourceWithRawResponse(self._users.myself)
+
+
+class AsyncUsersResourceWithRawResponse:
+    def __init__(self, users: AsyncUsersResource) -> None:
+        self._users = users
+
+    @cached_property
+    def myself(self) -> AsyncMyselfResourceWithRawResponse:
+        return AsyncMyselfResourceWithRawResponse(self._users.myself)
+
+
+class UsersResourceWithStreamingResponse:
+    def __init__(self, users: UsersResource) -> None:
+        self._users = users
+
+    @cached_property
+    def myself(self) -> MyselfResourceWithStreamingResponse:
+        return MyselfResourceWithStreamingResponse(self._users.myself)
+
+
+class AsyncUsersResourceWithStreamingResponse:
+    def __init__(self, users: AsyncUsersResource) -> None:
+        self._users = users
+
+    @cached_property
+    def myself(self) -> AsyncMyselfResourceWithStreamingResponse:
+        return AsyncMyselfResourceWithStreamingResponse(self._users.myself)
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
new file mode 100644
index 00000000..9a02abc3
--- /dev/null
+++ b/src/codex/types/__init__.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .project_list_params import ProjectListParams as ProjectListParams
+from .health_check_response import HealthCheckResponse as HealthCheckResponse
+from .project_create_params import ProjectCreateParams as ProjectCreateParams
+from .project_list_response import ProjectListResponse as ProjectListResponse
+from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
+from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
diff --git a/src/codex/types/health_check_response.py b/src/codex/types/health_check_response.py
new file mode 100644
index 00000000..7a2655d3
--- /dev/null
+++ b/src/codex/types/health_check_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from .._models import BaseModel
+
+__all__ = ["HealthCheckResponse"]
+
+
+class HealthCheckResponse(BaseModel):
+    status: str
diff --git a/src/codex/types/organization_schema_public.py b/src/codex/types/organization_schema_public.py
new file mode 100644
index 00000000..ed245c50
--- /dev/null
+++ b/src/codex/types/organization_schema_public.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["OrganizationSchemaPublic"]
+
+
+class OrganizationSchemaPublic(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    name: str
+
+    updated_at: datetime
diff --git a/src/codex/types/organizations/__init__.py b/src/codex/types/organizations/__init__.py
new file mode 100644
index 00000000..6d9c59d1
--- /dev/null
+++ b/src/codex/types/organizations/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .organization_billing_usage_schema import OrganizationBillingUsageSchema as OrganizationBillingUsageSchema
+from .organization_billing_invoices_schema import OrganizationBillingInvoicesSchema as OrganizationBillingInvoicesSchema
diff --git a/src/codex/types/organizations/organization_billing_invoices_schema.py b/src/codex/types/organizations/organization_billing_invoices_schema.py
new file mode 100644
index 00000000..e588ded0
--- /dev/null
+++ b/src/codex/types/organizations/organization_billing_invoices_schema.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from ..._models import BaseModel
+
+__all__ = ["OrganizationBillingInvoicesSchema"]
+
+
+class OrganizationBillingInvoicesSchema(BaseModel):
+    invoices_iframe_url: str
diff --git a/src/codex/types/organizations/organization_billing_usage_schema.py b/src/codex/types/organizations/organization_billing_usage_schema.py
new file mode 100644
index 00000000..8369fb31
--- /dev/null
+++ b/src/codex/types/organizations/organization_billing_usage_schema.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from ..._models import BaseModel
+
+__all__ = ["OrganizationBillingUsageSchema"]
+
+
+class OrganizationBillingUsageSchema(BaseModel):
+    usage_iframe_url: str
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
new file mode 100644
index 00000000..80882b21
--- /dev/null
+++ b/src/codex/types/project_create_params.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ProjectCreateParams", "Config"]
+
+
+class ProjectCreateParams(TypedDict, total=False):
+    config: Required[Config]
+
+    name: Required[str]
+
+    organization_id: Required[str]
+
+    description: Optional[str]
+
+
+class Config(TypedDict, total=False):
+    max_distance: float
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
new file mode 100644
index 00000000..5ca07a82
--- /dev/null
+++ b/src/codex/types/project_list_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ProjectListParams"]
+
+
+class ProjectListParams(TypedDict, total=False):
+    organization_id: Required[str]
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
new file mode 100644
index 00000000..9667cf81
--- /dev/null
+++ b/src/codex/types/project_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .project_return_schema import ProjectReturnSchema
+
+__all__ = ["ProjectListResponse"]
+
+ProjectListResponse: TypeAlias = List[ProjectReturnSchema]
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
new file mode 100644
index 00000000..7f6b06a2
--- /dev/null
+++ b/src/codex/types/project_return_schema.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ProjectReturnSchema", "Config"]
+
+
+class Config(BaseModel):
+    max_distance: Optional[float] = None
+
+
+class ProjectReturnSchema(BaseModel):
+    id: int
+
+    config: Config
+
+    created_at: datetime
+
+    created_by_user_id: str
+
+    name: str
+
+    organization_id: str
+
+    updated_at: datetime
+
+    description: Optional[str] = None
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
new file mode 100644
index 00000000..46d747ee
--- /dev/null
+++ b/src/codex/types/project_update_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ProjectUpdateParams", "Config"]
+
+
+class ProjectUpdateParams(TypedDict, total=False):
+    config: Required[Config]
+
+    name: Required[str]
+
+    description: Optional[str]
+
+
+class Config(TypedDict, total=False):
+    max_distance: float
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
new file mode 100644
index 00000000..89e09d61
--- /dev/null
+++ b/src/codex/types/projects/__init__.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .entry import Entry as Entry
+from .access_key_schema import AccessKeySchema as AccessKeySchema
+from .knowledge_list_params import KnowledgeListParams as KnowledgeListParams
+from .knowledge_query_params import KnowledgeQueryParams as KnowledgeQueryParams
+from .knowledge_create_params import KnowledgeCreateParams as KnowledgeCreateParams
+from .knowledge_update_params import KnowledgeUpdateParams as KnowledgeUpdateParams
+from .list_knowledge_response import ListKnowledgeResponse as ListKnowledgeResponse
+from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
+from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
+from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
+from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams
diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py
new file mode 100644
index 00000000..1cbc202f
--- /dev/null
+++ b/src/codex/types/projects/access_key_create_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["AccessKeyCreateParams"]
+
+
+class AccessKeyCreateParams(TypedDict, total=False):
+    name: Required[str]
+
+    description: Optional[str]
+
+    expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
diff --git a/src/codex/types/projects/access_key_list_response.py b/src/codex/types/projects/access_key_list_response.py
new file mode 100644
index 00000000..02db3e05
--- /dev/null
+++ b/src/codex/types/projects/access_key_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .access_key_schema import AccessKeySchema
+
+__all__ = ["AccessKeyListResponse"]
+
+AccessKeyListResponse: TypeAlias = List[AccessKeySchema]
diff --git a/src/codex/types/projects/access_key_schema.py b/src/codex/types/projects/access_key_schema.py
new file mode 100644
index 00000000..3e023aae
--- /dev/null
+++ b/src/codex/types/projects/access_key_schema.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["AccessKeySchema"]
+
+
+class AccessKeySchema(BaseModel):
+    id: int
+
+    token: str
+
+    created_at: datetime
+
+    last_used_at: Optional[datetime] = None
+
+    name: str
+
+    project_id: int
+
+    status: Literal["active", "expired", "revoked"]
+
+    updated_at: datetime
+
+    description: Optional[str] = None
+
+    expires_at: Optional[datetime] = None
diff --git a/src/codex/types/projects/access_key_update_params.py b/src/codex/types/projects/access_key_update_params.py
new file mode 100644
index 00000000..2f40f335
--- /dev/null
+++ b/src/codex/types/projects/access_key_update_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["AccessKeyUpdateParams"]
+
+
+class AccessKeyUpdateParams(TypedDict, total=False):
+    project_id: Required[int]
+
+    name: Required[str]
+
+    description: Optional[str]
+
+    expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
new file mode 100644
index 00000000..bfa7d278
--- /dev/null
+++ b/src/codex/types/projects/entry.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["Entry"]
+
+
+class Entry(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    question: str
+
+    answer: Optional[str] = None
+
+    answered_at: Optional[datetime] = None
diff --git a/src/codex/types/projects/knowledge_add_question_params.py b/src/codex/types/projects/knowledge_add_question_params.py
new file mode 100644
index 00000000..97115554
--- /dev/null
+++ b/src/codex/types/projects/knowledge_add_question_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["KnowledgeAddQuestionParams"]
+
+
+class KnowledgeAddQuestionParams(TypedDict, total=False):
+    question: Required[str]
diff --git a/src/codex/types/projects/knowledge_create_params.py b/src/codex/types/projects/knowledge_create_params.py
new file mode 100644
index 00000000..9c544e6e
--- /dev/null
+++ b/src/codex/types/projects/knowledge_create_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["KnowledgeCreateParams"]
+
+
+class KnowledgeCreateParams(TypedDict, total=False):
+    question: Required[str]
+
+    answer: Optional[str]
diff --git a/src/codex/types/projects/knowledge_list_params.py b/src/codex/types/projects/knowledge_list_params.py
new file mode 100644
index 00000000..45aee13e
--- /dev/null
+++ b/src/codex/types/projects/knowledge_list_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["KnowledgeListParams"]
+
+
+class KnowledgeListParams(TypedDict, total=False):
+    answered_only: bool
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    sort: Literal["created_at", "answered_at"]
+
+    unanswered_only: bool
diff --git a/src/codex/types/projects/knowledge_query_params.py b/src/codex/types/projects/knowledge_query_params.py
new file mode 100644
index 00000000..2c8097af
--- /dev/null
+++ b/src/codex/types/projects/knowledge_query_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["KnowledgeQueryParams"]
+
+
+class KnowledgeQueryParams(TypedDict, total=False):
+    question: Required[str]
diff --git a/src/codex/types/projects/knowledge_update_params.py b/src/codex/types/projects/knowledge_update_params.py
new file mode 100644
index 00000000..b5e22269
--- /dev/null
+++ b/src/codex/types/projects/knowledge_update_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["KnowledgeUpdateParams"]
+
+
+class KnowledgeUpdateParams(TypedDict, total=False):
+    project_id: Required[int]
+
+    answer: Optional[str]
+
+    question: Optional[str]
diff --git a/src/codex/types/projects/list_knowledge_response.py b/src/codex/types/projects/list_knowledge_response.py
new file mode 100644
index 00000000..d32f4e38
--- /dev/null
+++ b/src/codex/types/projects/list_knowledge_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .entry import Entry
+from ..._models import BaseModel
+
+__all__ = ["ListKnowledgeResponse"]
+
+
+class ListKnowledgeResponse(BaseModel):
+    entries: List[Entry]
+
+    total_count: int
diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py
new file mode 100644
index 00000000..85decc6b
--- /dev/null
+++ b/src/codex/types/users/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .user_schema import UserSchema as UserSchema
+from .user_schema_public import UserSchemaPublic as UserSchemaPublic
diff --git a/src/codex/types/users/myself/__init__.py b/src/codex/types/users/myself/__init__.py
new file mode 100644
index 00000000..f263cd4f
--- /dev/null
+++ b/src/codex/types/users/myself/__init__.py
@@ -0,0 +1,5 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .user_organizations_schema import UserOrganizationsSchema as UserOrganizationsSchema
diff --git a/src/codex/types/users/myself/user_organizations_schema.py b/src/codex/types/users/myself/user_organizations_schema.py
new file mode 100644
index 00000000..60ffd5c6
--- /dev/null
+++ b/src/codex/types/users/myself/user_organizations_schema.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from ...._models import BaseModel
+
+__all__ = ["UserOrganizationsSchema", "Organization"]
+
+
+class Organization(BaseModel):
+    created_at: datetime
+
+    organization_id: str
+
+    updated_at: datetime
+
+    user_id: str
+
+
+class UserOrganizationsSchema(BaseModel):
+    organizations: List[Organization]
diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py
new file mode 100644
index 00000000..ec6ad541
--- /dev/null
+++ b/src/codex/types/users/user_schema.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["UserSchema"]
+
+
+class UserSchema(BaseModel):
+    id: str
+
+    api_key: str
+
+    api_key_timestamp: datetime
+
+    created_at: datetime
+
+    email: str
+
+    name: Optional[str] = None
+
+    updated_at: datetime
diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py
new file mode 100644
index 00000000..9becf37d
--- /dev/null
+++ b/src/codex/types/users/user_schema_public.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["UserSchemaPublic"]
+
+
+class UserSchemaPublic(BaseModel):
+    id: str
+
+    api_key: str
+
+    email: str
+
+    name: Optional[str] = None
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/organizations/__init__.py b/tests/api_resources/organizations/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/organizations/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
new file mode 100644
index 00000000..ccb7dc8a
--- /dev/null
+++ b/tests/api_resources/organizations/test_billing.py
@@ -0,0 +1,174 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBilling:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_invoices(self, client: Codex) -> None:
+        billing = client.organizations.billing.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+    @parametrize
+    def test_raw_response_invoices(self, client: Codex) -> None:
+        response = client.organizations.billing.with_raw_response.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        billing = response.parse()
+        assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+    @parametrize
+    def test_streaming_response_invoices(self, client: Codex) -> None:
+        with client.organizations.billing.with_streaming_response.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            billing = response.parse()
+            assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_invoices(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.billing.with_raw_response.invoices(
+                "",
+            )
+
+    @parametrize
+    def test_method_usage(self, client: Codex) -> None:
+        billing = client.organizations.billing.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+    @parametrize
+    def test_raw_response_usage(self, client: Codex) -> None:
+        response = client.organizations.billing.with_raw_response.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        billing = response.parse()
+        assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+    @parametrize
+    def test_streaming_response_usage(self, client: Codex) -> None:
+        with client.organizations.billing.with_streaming_response.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            billing = response.parse()
+            assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_usage(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.billing.with_raw_response.usage(
+                "",
+            )
+
+
+class TestAsyncBilling:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_invoices(self, async_client: AsyncCodex) -> None:
+        billing = await async_client.organizations.billing.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+    @parametrize
+    async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.billing.with_raw_response.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        billing = await response.parse()
+        assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.billing.with_streaming_response.invoices(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            billing = await response.parse()
+            assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.billing.with_raw_response.invoices(
+                "",
+            )
+
+    @parametrize
+    async def test_method_usage(self, async_client: AsyncCodex) -> None:
+        billing = await async_client.organizations.billing.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+    @parametrize
+    async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.billing.with_raw_response.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        billing = await response.parse()
+        assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.billing.with_streaming_response.usage(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            billing = await response.parse()
+            assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_usage(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.billing.with_raw_response.usage(
+                "",
+            )
diff --git a/tests/api_resources/projects/__init__.py b/tests/api_resources/projects/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/projects/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
new file mode 100644
index 00000000..4fa1636f
--- /dev/null
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -0,0 +1,476 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex._utils import parse_datetime
+from codex.types.projects import (
+    AccessKeySchema,
+    AccessKeyListResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAccessKeys:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.create(
+            project_id=0,
+            name="name",
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.create(
+            project_id=0,
+            name="name",
+            description="description",
+            expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.create(
+            project_id=0,
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.create(
+            project_id=0,
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.retrieve(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.retrieve(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.retrieve(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_update(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_method_update_with_all_params(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+            description="description",
+            expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_update(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_update(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.list(
+            0,
+        )
+        assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.list(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.list(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.delete(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert access_key is None
+
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.delete(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert access_key is None
+
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.delete(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert access_key is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_revoke(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.revoke(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert access_key is None
+
+    @parametrize
+    def test_raw_response_revoke(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.revoke(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert access_key is None
+
+    @parametrize
+    def test_streaming_response_revoke(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.revoke(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert access_key is None
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncAccessKeys:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.create(
+            project_id=0,
+            name="name",
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.create(
+            project_id=0,
+            name="name",
+            description="description",
+            expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.create(
+            project_id=0,
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.create(
+            project_id=0,
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.retrieve(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.retrieve(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.retrieve(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+            description="description",
+            expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+        )
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.update(
+            access_key_id=0,
+            project_id=0,
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert_matches_type(AccessKeySchema, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.list(
+            0,
+        )
+        assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.list(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.list(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.delete(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert access_key is None
+
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.delete(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert access_key is None
+
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.delete(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert access_key is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_revoke(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.revoke(
+            access_key_id=0,
+            project_id=0,
+        )
+        assert access_key is None
+
+    @parametrize
+    async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.revoke(
+            access_key_id=0,
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert access_key is None
+
+    @parametrize
+    async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.revoke(
+            access_key_id=0,
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert access_key is None
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
new file mode 100644
index 00000000..9052b62d
--- /dev/null
+++ b/tests/api_resources/projects/test_knowledge.py
@@ -0,0 +1,607 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.projects import (
+    Entry,
+    ListKnowledgeResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestKnowledge:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.create(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.create(
+            project_id=0,
+            question="question",
+            answer="answer",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.create(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.create(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.knowledge.with_raw_response.retrieve(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    def test_method_update(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_method_update_with_all_params(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+            answer="answer",
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_update(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_update(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_update(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.knowledge.with_raw_response.update(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.list(
+            project_id=0,
+        )
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.list(
+            project_id=0,
+            answered_only=True,
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            unanswered_only=True,
+        )
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.list(
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.list(
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert knowledge is None
+
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert knowledge is None
+
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert knowledge is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.knowledge.with_raw_response.delete(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    def test_method_add_question(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.add_question(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_add_question(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.add_question(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_add_question(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.add_question(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_query(self, client: Codex) -> None:
+        knowledge = client.projects.knowledge.query(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+    @parametrize
+    def test_raw_response_query(self, client: Codex) -> None:
+        response = client.projects.knowledge.with_raw_response.query(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = response.parse()
+        assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+    @parametrize
+    def test_streaming_response_query(self, client: Codex) -> None:
+        with client.projects.knowledge.with_streaming_response.query(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = response.parse()
+            assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncKnowledge:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.create(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.create(
+            project_id=0,
+            question="question",
+            answer="answer",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.create(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.create(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.knowledge.with_raw_response.retrieve(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+            answer="answer",
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.knowledge.with_raw_response.update(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.list(
+            project_id=0,
+        )
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.list(
+            project_id=0,
+            answered_only=True,
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            unanswered_only=True,
+        )
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.list(
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.list(
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert knowledge is None
+
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert knowledge is None
+
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert knowledge is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.knowledge.with_raw_response.delete(
+                entry_id="",
+                project_id=0,
+            )
+
+    @parametrize
+    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.add_question(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.add_question(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(Entry, knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.add_question(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(Entry, knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_query(self, async_client: AsyncCodex) -> None:
+        knowledge = await async_client.projects.knowledge.query(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+    @parametrize
+    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.knowledge.with_raw_response.query(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        knowledge = await response.parse()
+        assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.knowledge.with_streaming_response.query(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            knowledge = await response.parse()
+            assert_matches_type(Optional[Entry], knowledge, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
new file mode 100644
index 00000000..be4729ab
--- /dev/null
+++ b/tests/api_resources/test_health.py
@@ -0,0 +1,172 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import HealthCheckResponse
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestHealth:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_check(self, client: Codex) -> None:
+        health = client.health.check()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_raw_response_check(self, client: Codex) -> None:
+        response = client.health.with_raw_response.check()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_streaming_response_check(self, client: Codex) -> None:
+        with client.health.with_streaming_response.check() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_db(self, client: Codex) -> None:
+        health = client.health.db()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_raw_response_db(self, client: Codex) -> None:
+        response = client.health.with_raw_response.db()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_streaming_response_db(self, client: Codex) -> None:
+        with client.health.with_streaming_response.db() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_weaviate(self, client: Codex) -> None:
+        health = client.health.weaviate()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_raw_response_weaviate(self, client: Codex) -> None:
+        response = client.health.with_raw_response.weaviate()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    def test_streaming_response_weaviate(self, client: Codex) -> None:
+        with client.health.with_streaming_response.weaviate() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncHealth:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_check(self, async_client: AsyncCodex) -> None:
+        health = await async_client.health.check()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
+        response = await async_client.health.with_raw_response.check()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = await response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
+        async with async_client.health.with_streaming_response.check() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = await response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_db(self, async_client: AsyncCodex) -> None:
+        health = await async_client.health.db()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
+        response = await async_client.health.with_raw_response.db()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = await response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
+        async with async_client.health.with_streaming_response.db() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = await response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_weaviate(self, async_client: AsyncCodex) -> None:
+        health = await async_client.health.weaviate()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
+        response = await async_client.health.with_raw_response.weaviate()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        health = await response.parse()
+        assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None:
+        async with async_client.health.with_streaming_response.weaviate() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            health = await response.parse()
+            assert_matches_type(HealthCheckResponse, health, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
new file mode 100644
index 00000000..2d627c70
--- /dev/null
+++ b/tests/api_resources/test_organizations.py
@@ -0,0 +1,98 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import OrganizationSchemaPublic
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestOrganizations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        organization = client.organizations.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.organizations.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.organizations.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.with_raw_response.retrieve(
+                "",
+            )
+
+
+class TestAsyncOrganizations:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        organization = await async_client.organizations.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.with_raw_response.retrieve(
+                "",
+            )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
new file mode 100644
index 00000000..7e79d8e8
--- /dev/null
+++ b/tests/api_resources/test_projects.py
@@ -0,0 +1,461 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import (
+    ProjectListResponse,
+    ProjectReturnSchema,
+)
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProjects:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        project = client.projects.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        project = client.projects.create(
+            config={"max_distance": 0},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            description="description",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        project = client.projects.retrieve(
+            0,
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.retrieve(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.retrieve(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_update(self, client: Codex) -> None:
+        project = client.projects.update(
+            project_id=0,
+            config={},
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_method_update_with_all_params(self, client: Codex) -> None:
+        project = client.projects.update(
+            project_id=0,
+            config={"max_distance": 0},
+            name="name",
+            description="description",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_raw_response_update(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.update(
+            project_id=0,
+            config={},
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    def test_streaming_response_update(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.update(
+            project_id=0,
+            config={},
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        project = client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectListResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        project = client.projects.delete(
+            0,
+        )
+        assert project is None
+
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.delete(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert project is None
+
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.delete(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert project is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    def test_method_export(self, client: Codex) -> None:
+        project = client.projects.export(
+            0,
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @parametrize
+    def test_raw_response_export(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.export(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @parametrize
+    def test_streaming_response_export(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.export(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncProjects:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.create(
+            config={"max_distance": 0},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            description="description",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.retrieve(
+            0,
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.retrieve(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.retrieve(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.update(
+            project_id=0,
+            config={},
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.update(
+            project_id=0,
+            config={"max_distance": 0},
+            name="name",
+            description="description",
+        )
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.update(
+            project_id=0,
+            config={},
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.update(
+            project_id=0,
+            config={},
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectListResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.delete(
+            0,
+        )
+        assert project is None
+
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.delete(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert project is None
+
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.delete(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert project is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @parametrize
+    async def test_method_export(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.export(
+            0,
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @parametrize
+    async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.export(
+            0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.export(
+            0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/__init__.py b/tests/api_resources/users/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/users/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/users/myself/__init__.py b/tests/api_resources/users/myself/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/users/myself/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
new file mode 100644
index 00000000..55bba35f
--- /dev/null
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.users import UserSchema
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAPIKey:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_refresh(self, client: Codex) -> None:
+        api_key = client.users.myself.api_key.refresh()
+        assert_matches_type(UserSchema, api_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_refresh(self, client: Codex) -> None:
+        response = client.users.myself.api_key.with_raw_response.refresh()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        api_key = response.parse()
+        assert_matches_type(UserSchema, api_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_refresh(self, client: Codex) -> None:
+        with client.users.myself.api_key.with_streaming_response.refresh() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            api_key = response.parse()
+            assert_matches_type(UserSchema, api_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncAPIKey:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_refresh(self, async_client: AsyncCodex) -> None:
+        api_key = await async_client.users.myself.api_key.refresh()
+        assert_matches_type(UserSchema, api_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.myself.api_key.with_raw_response.refresh()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        api_key = await response.parse()
+        assert_matches_type(UserSchema, api_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            api_key = await response.parse()
+            assert_matches_type(UserSchema, api_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
new file mode 100644
index 00000000..da485882
--- /dev/null
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.users.myself import UserOrganizationsSchema
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestOrganizations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        organization = client.users.myself.organizations.list()
+        assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.users.myself.organizations.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.users.myself.organizations.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncOrganizations:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        organization = await async_client.users.myself.organizations.list()
+        assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.myself.organizations.with_raw_response.list()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.myself.organizations.with_streaming_response.list() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
new file mode 100644
index 00000000..a206d1f9
--- /dev/null
+++ b/tests/api_resources/users/test_myself.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.users import UserSchemaPublic
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestMyself:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        myself = client.users.myself.retrieve()
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.users.myself.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        myself = response.parse()
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.users.myself.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            myself = response.parse()
+            assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncMyself:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        myself = await async_client.users.myself.retrieve()
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.myself.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        myself = await response.parse()
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.myself.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            myself = await response.parse()
+            assert_matches_type(UserSchemaPublic, myself, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..da139dcc
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+import os
+import logging
+from typing import TYPE_CHECKING, Iterator, AsyncIterator
+
+import pytest
+from pytest_asyncio import is_async_test
+
+from codex import Codex, AsyncCodex
+
+if TYPE_CHECKING:
+    from _pytest.fixtures import FixtureRequest
+
+pytest.register_assert_rewrite("tests.utils")
+
+logging.getLogger("codex").setLevel(logging.DEBUG)
+
+
+# automatically add `pytest.mark.asyncio()` to all of our async tests
+# so we don't have to add that boilerplate everywhere
+def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
+    pytest_asyncio_tests = (item for item in items if is_async_test(item))
+    session_scope_marker = pytest.mark.asyncio(loop_scope="session")
+    for async_test in pytest_asyncio_tests:
+        async_test.add_marker(session_scope_marker, append=False)
+
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+bearer_token = "My Bearer Token"
+api_key = "My API Key"
+access_key = "My Access Key"
+
+
+@pytest.fixture(scope="session")
+def client(request: FixtureRequest) -> Iterator[Codex]:
+    strict = getattr(request, "param", True)
+    if not isinstance(strict, bool):
+        raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
+
+    with Codex(
+        base_url=base_url,
+        bearer_token=bearer_token,
+        api_key=api_key,
+        access_key=access_key,
+        _strict_response_validation=strict,
+    ) as client:
+        yield client
+
+
+@pytest.fixture(scope="session")
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
+    strict = getattr(request, "param", True)
+    if not isinstance(strict, bool):
+        raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
+
+    async with AsyncCodex(
+        base_url=base_url,
+        bearer_token=bearer_token,
+        api_key=api_key,
+        access_key=access_key,
+        _strict_response_validation=strict,
+    ) as client:
+        yield client
diff --git a/tests/sample_file.txt b/tests/sample_file.txt
new file mode 100644
index 00000000..af5626b4
--- /dev/null
+++ b/tests/sample_file.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/tests/test_client.py b/tests/test_client.py
new file mode 100644
index 00000000..d36040f6
--- /dev/null
+++ b/tests/test_client.py
@@ -0,0 +1,1873 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import gc
+import os
+import sys
+import json
+import asyncio
+import inspect
+import subprocess
+import tracemalloc
+from typing import Any, Union, cast
+from textwrap import dedent
+from unittest import mock
+from typing_extensions import Literal
+
+import httpx
+import pytest
+from respx import MockRouter
+from pydantic import ValidationError
+
+from codex import Codex, AsyncCodex, APIResponseValidationError
+from codex._types import Omit
+from codex._models import BaseModel, FinalRequestOptions
+from codex._constants import RAW_RESPONSE_HEADER
+from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
+from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options
+
+from .utils import update_env
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+bearer_token = "My Bearer Token"
+api_key = "My API Key"
+access_key = "My Access Key"
+
+
+def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
+    request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+    url = httpx.URL(request.url)
+    return dict(url.params)
+
+
+def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
+    return 0.1
+
+
+def _get_open_connections(client: Codex | AsyncCodex) -> int:
+    transport = client._client._transport
+    assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
+
+    pool = transport._pool
+    return len(pool._requests)
+
+
+class TestCodex:
+    client = Codex(
+        base_url=base_url,
+        bearer_token=bearer_token,
+        api_key=api_key,
+        access_key=access_key,
+        _strict_response_validation=True,
+    )
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_raw_response(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(
+            return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+        )
+
+        response = self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    def test_copy(self) -> None:
+        copied = self.client.copy()
+        assert id(copied) != id(self.client)
+
+        copied = self.client.copy(bearer_token="another My Bearer Token")
+        assert copied.bearer_token == "another My Bearer Token"
+        assert self.client.bearer_token == "My Bearer Token"
+
+        copied = self.client.copy(api_key="another My API Key")
+        assert copied.api_key == "another My API Key"
+        assert self.client.api_key == "My API Key"
+
+        copied = self.client.copy(access_key="another My Access Key")
+        assert copied.access_key == "another My Access Key"
+        assert self.client.access_key == "My Access Key"
+
+    def test_copy_default_options(self) -> None:
+        # options that have a default are overridden correctly
+        copied = self.client.copy(max_retries=7)
+        assert copied.max_retries == 7
+        assert self.client.max_retries == 2
+
+        copied2 = copied.copy(max_retries=6)
+        assert copied2.max_retries == 6
+        assert copied.max_retries == 7
+
+        # timeout
+        assert isinstance(self.client.timeout, httpx.Timeout)
+        copied = self.client.copy(timeout=None)
+        assert copied.timeout is None
+        assert isinstance(self.client.timeout, httpx.Timeout)
+
+    def test_copy_default_headers(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        assert client.default_headers["X-Foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert copied.default_headers["X-Foo"] == "bar"
+
+        # merges already given headers
+        copied = client.copy(default_headers={"X-Bar": "stainless"})
+        assert copied.default_headers["X-Foo"] == "bar"
+        assert copied.default_headers["X-Bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_headers={"X-Foo": "stainless"})
+        assert copied.default_headers["X-Foo"] == "stainless"
+
+        # set_default_headers
+
+        # completely overrides already set values
+        copied = client.copy(set_default_headers={})
+        assert copied.default_headers.get("X-Foo") is None
+
+        copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+        assert copied.default_headers["X-Bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+
+    def test_copy_default_query(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_query={"foo": "bar"},
+        )
+        assert _get_params(client)["foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert _get_params(copied)["foo"] == "bar"
+
+        # merges already given params
+        copied = client.copy(default_query={"bar": "stainless"})
+        params = _get_params(copied)
+        assert params["foo"] == "bar"
+        assert params["bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_query={"foo": "stainless"})
+        assert _get_params(copied)["foo"] == "stainless"
+
+        # set_default_query
+
+        # completely overrides already set values
+        copied = client.copy(set_default_query={})
+        assert _get_params(copied) == {}
+
+        copied = client.copy(set_default_query={"bar": "Robert"})
+        assert _get_params(copied)["bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            # TODO: update
+            match="`default_query` and `set_default_query` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+    def test_copy_signature(self) -> None:
+        # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+        init_signature = inspect.signature(
+            # mypy doesn't like that we access the `__init__` property.
+            self.client.__init__,  # type: ignore[misc]
+        )
+        copy_signature = inspect.signature(self.client.copy)
+        exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+        for name in init_signature.parameters.keys():
+            if name in exclude_params:
+                continue
+
+            copy_param = copy_signature.parameters.get(name)
+            assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+    def test_copy_build_request(self) -> None:
+        options = FinalRequestOptions(method="get", url="/foo")
+
+        def build_request(options: FinalRequestOptions) -> None:
+            client = self.client.copy()
+            client._build_request(options)
+
+        # ensure that the machinery is warmed up before tracing starts.
+        build_request(options)
+        gc.collect()
+
+        tracemalloc.start(1000)
+
+        snapshot_before = tracemalloc.take_snapshot()
+
+        ITERATIONS = 10
+        for _ in range(ITERATIONS):
+            build_request(options)
+
+        gc.collect()
+        snapshot_after = tracemalloc.take_snapshot()
+
+        tracemalloc.stop()
+
+        def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+            if diff.count == 0:
+                # Avoid false positives by considering only leaks (i.e. allocations that persist).
+                return
+
+            if diff.count % ITERATIONS != 0:
+                # Avoid false positives by considering only leaks that appear per iteration.
+                return
+
+            for frame in diff.traceback:
+                if any(
+                    frame.filename.endswith(fragment)
+                    for fragment in [
+                        # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+                        #
+                        # removing the decorator fixes the leak for reasons we don't understand.
+                        "codex/_legacy_response.py",
+                        "codex/_response.py",
+                        # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+                        "codex/_compat.py",
+                        # Standard library leaks we don't care about.
+                        "/logging/__init__.py",
+                    ]
+                ):
+                    return
+
+            leaks.append(diff)
+
+        leaks: list[tracemalloc.StatisticDiff] = []
+        for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+            add_leak(leaks, diff)
+        if leaks:
+            for leak in leaks:
+                print("MEMORY LEAK:", leak)
+                for frame in leak.traceback:
+                    print(frame)
+            raise AssertionError()
+
+    def test_request_timeout(self) -> None:
+        request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == DEFAULT_TIMEOUT
+
+        request = self.client._build_request(
+            FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
+        )
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(100.0)
+
+    def test_client_timeout_option(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            timeout=httpx.Timeout(0),
+        )
+
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(0)
+
+    def test_http_client_timeout_option(self) -> None:
+        # custom timeout given to the httpx client should be used
+        with httpx.Client(timeout=None) as http_client:
+            client = Codex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == httpx.Timeout(None)
+
+        # no timeout given to the httpx client should not use the httpx default
+        with httpx.Client() as http_client:
+            client = Codex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT
+
+        # explicitly passing the default timeout currently results in it being ignored
+        with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+            client = Codex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT  # our default
+
+    async def test_invalid_http_client(self) -> None:
+        with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+            async with httpx.AsyncClient() as http_client:
+                Codex(
+                    base_url=base_url,
+                    bearer_token=bearer_token,
+                    api_key=api_key,
+                    access_key=access_key,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
+
+    def test_default_headers_option(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "bar"
+        assert request.headers.get("x-stainless-lang") == "python"
+
+        client2 = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={
+                "X-Foo": "stainless",
+                "X-Stainless-Lang": "my-overriding-header",
+            },
+        )
+        request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "stainless"
+        assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+    def test_default_query_option(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"query_param": "bar"}
+
+        request = client._build_request(
+            FinalRequestOptions(
+                method="get",
+                url="/foo",
+                params={"foo": "baz", "query_param": "overridden"},
+            )
+        )
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+    def test_request_extra_json(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": False}
+
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"baz": False}
+
+        # `extra_json` takes priority over `json_data` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar", "baz": True},
+                extra_json={"baz": None},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": None}
+
+    def test_request_extra_headers(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(extra_headers={"X-Foo": "Foo"}),
+            ),
+        )
+        assert request.headers.get("X-Foo") == "Foo"
+
+        # `extra_headers` takes priority over `default_headers` when keys clash
+        request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_headers={"X-Bar": "false"},
+                ),
+            ),
+        )
+        assert request.headers.get("X-Bar") == "false"
+
+    def test_request_extra_query(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_query={"my_query_param": "Foo"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"my_query_param": "Foo"}
+
+        # if both `query` and `extra_query` are given, they are merged
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"bar": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"bar": "1", "foo": "2"}
+
+        # `extra_query` takes priority over `query` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"foo": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"foo": "2"}
+
+    def test_multipart_repeating_array(self, client: Codex) -> None:
+        request = client._build_request(
+            FinalRequestOptions.construct(
+                method="get",
+                url="/foo",
+                headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+                json_data={"array": ["foo", "bar"]},
+                files=[("foo.txt", b"hello world")],
+            )
+        )
+
+        assert request.read().split(b"\r\n") == [
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"foo",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"bar",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+            b"Content-Type: application/octet-stream",
+            b"",
+            b"hello world",
+            b"--6b7ba517decee4a450543ea6ae821c82--",
+            b"",
+        ]
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+        class Model1(BaseModel):
+            name: str
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+        """Union of objects with the same field name using a different type"""
+
+        class Model1(BaseModel):
+            foo: int
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+        response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model1)
+        assert response.foo == 1
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+        """
+        Response that sets Content-Type to something other than application/json but returns json data
+        """
+
+        class Model(BaseModel):
+            foo: int
+
+        respx_mock.get("/foo").mock(
+            return_value=httpx.Response(
+                200,
+                content=json.dumps({"foo": 2}),
+                headers={"Content-Type": "application/text"},
+            )
+        )
+
+        response = self.client.get("/foo", cast_to=Model)
+        assert isinstance(response, Model)
+        assert response.foo == 2
+
+    def test_base_url_setter(self) -> None:
+        client = Codex(
+            base_url="https://example.com/from_init",
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        assert client.base_url == "https://example.com/from_init/"
+
+        client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
+
+        assert client.base_url == "https://example.com/from_setter/"
+
+    def test_base_url_env(self) -> None:
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = Codex(
+                bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True
+            )
+            assert client.base_url == "http://localhost:5000/from/env/"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_trailing_slash(self, client: Codex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_no_trailing_slash(self, client: Codex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            Codex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.Client(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_absolute_request_url(self, client: Codex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="https://myapi.com/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "https://myapi.com/foo"
+
+    def test_copied_client_does_not_close_http(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        assert not client.is_closed()
+
+        copied = client.copy()
+        assert copied is not client
+
+        del copied
+
+        assert not client.is_closed()
+
+    def test_client_context_manager(self) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        with client as c2:
+            assert c2 is client
+            assert not c2.is_closed()
+            assert not client.is_closed()
+        assert client.is_closed()
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
+
+        with pytest.raises(APIResponseValidationError) as exc:
+            self.client.get("/foo", cast_to=Model)
+
+        assert isinstance(exc.value.__cause__, ValidationError)
+
+    def test_client_max_retries_validation(self) -> None:
+        with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+            Codex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                max_retries=cast(Any, None),
+            )
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            name: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+
+        strict_client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+
+        with pytest.raises(APIResponseValidationError):
+            strict_client.get("/foo", cast_to=Model)
+
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=False,
+        )
+
+        response = client.get("/foo", cast_to=Model)
+        assert isinstance(response, str)  # type: ignore[unreachable]
+
+    @pytest.mark.parametrize(
+        "remaining_retries,retry_after,timeout",
+        [
+            [3, "20", 20],
+            [3, "0", 0.5],
+            [3, "-10", 0.5],
+            [3, "60", 60],
+            [3, "61", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+            [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+            [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+            [3, "99999999999999999999999999999999999", 0.5],
+            [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "", 0.5],
+            [2, "", 0.5 * 2.0],
+            [1, "", 0.5 * 4.0],
+            [-1100, "", 8],  # test large number potentially overflowing
+        ],
+    )
+    @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+    def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
+        client = Codex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+
+        headers = httpx.Headers({"retry-after": retry_after})
+        options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+        calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+        assert calculated == pytest.approx(timeout, 0.5 * 0.875)  # pyright: ignore[reportUnknownMemberType]
+
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
+
+        with pytest.raises(APITimeoutError):
+            self.client.post(
+                "/api/projects/",
+                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                cast_to=httpx.Response,
+                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
+            )
+
+        assert _get_open_connections(self.client) == 0
+
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500))
+
+        with pytest.raises(APIStatusError):
+            self.client.post(
+                "/api/projects/",
+                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                cast_to=httpx.Response,
+                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
+            )
+
+        assert _get_open_connections(self.client) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+    def test_retries_taken(
+        self,
+        client: Codex,
+        failures_before_success: int,
+        failure_mode: Literal["status", "exception"],
+        respx_mock: MockRouter,
+    ) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                if failure_mode == "exception":
+                    raise RuntimeError("oops")
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = client.projects.with_raw_response.create(
+            config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+        )
+
+        assert response.retries_taken == failures_before_success
+        assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_omit_retry_count_header(self, client: Codex, failures_before_success: int, respx_mock: MockRouter) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            extra_headers={"x-stainless-retry-count": Omit()},
+        )
+
+        assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    def test_overwrite_retry_count_header(
+        self, client: Codex, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            extra_headers={"x-stainless-retry-count": "42"},
+        )
+
+        assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+
+class TestAsyncCodex:
+    client = AsyncCodex(
+        base_url=base_url,
+        bearer_token=bearer_token,
+        api_key=api_key,
+        access_key=access_key,
+        _strict_response_validation=True,
+    )
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_raw_response(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/foo").mock(
+            return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+        )
+
+        response = await self.client.post("/foo", cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert isinstance(response, httpx.Response)
+        assert response.json() == {"foo": "bar"}
+
+    def test_copy(self) -> None:
+        copied = self.client.copy()
+        assert id(copied) != id(self.client)
+
+        copied = self.client.copy(bearer_token="another My Bearer Token")
+        assert copied.bearer_token == "another My Bearer Token"
+        assert self.client.bearer_token == "My Bearer Token"
+
+        copied = self.client.copy(api_key="another My API Key")
+        assert copied.api_key == "another My API Key"
+        assert self.client.api_key == "My API Key"
+
+        copied = self.client.copy(access_key="another My Access Key")
+        assert copied.access_key == "another My Access Key"
+        assert self.client.access_key == "My Access Key"
+
+    def test_copy_default_options(self) -> None:
+        # options that have a default are overridden correctly
+        copied = self.client.copy(max_retries=7)
+        assert copied.max_retries == 7
+        assert self.client.max_retries == 2
+
+        copied2 = copied.copy(max_retries=6)
+        assert copied2.max_retries == 6
+        assert copied.max_retries == 7
+
+        # timeout
+        assert isinstance(self.client.timeout, httpx.Timeout)
+        copied = self.client.copy(timeout=None)
+        assert copied.timeout is None
+        assert isinstance(self.client.timeout, httpx.Timeout)
+
+    def test_copy_default_headers(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        assert client.default_headers["X-Foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert copied.default_headers["X-Foo"] == "bar"
+
+        # merges already given headers
+        copied = client.copy(default_headers={"X-Bar": "stainless"})
+        assert copied.default_headers["X-Foo"] == "bar"
+        assert copied.default_headers["X-Bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_headers={"X-Foo": "stainless"})
+        assert copied.default_headers["X-Foo"] == "stainless"
+
+        # set_default_headers
+
+        # completely overrides already set values
+        copied = client.copy(set_default_headers={})
+        assert copied.default_headers.get("X-Foo") is None
+
+        copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+        assert copied.default_headers["X-Bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+
+    def test_copy_default_query(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_query={"foo": "bar"},
+        )
+        assert _get_params(client)["foo"] == "bar"
+
+        # does not override the already given value when not specified
+        copied = client.copy()
+        assert _get_params(copied)["foo"] == "bar"
+
+        # merges already given params
+        copied = client.copy(default_query={"bar": "stainless"})
+        params = _get_params(copied)
+        assert params["foo"] == "bar"
+        assert params["bar"] == "stainless"
+
+        # uses new values for any already given headers
+        copied = client.copy(default_query={"foo": "stainless"})
+        assert _get_params(copied)["foo"] == "stainless"
+
+        # set_default_query
+
+        # completely overrides already set values
+        copied = client.copy(set_default_query={})
+        assert _get_params(copied) == {}
+
+        copied = client.copy(set_default_query={"bar": "Robert"})
+        assert _get_params(copied)["bar"] == "Robert"
+
+        with pytest.raises(
+            ValueError,
+            # TODO: update
+            match="`default_query` and `set_default_query` arguments are mutually exclusive",
+        ):
+            client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+    def test_copy_signature(self) -> None:
+        # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+        init_signature = inspect.signature(
+            # mypy doesn't like that we access the `__init__` property.
+            self.client.__init__,  # type: ignore[misc]
+        )
+        copy_signature = inspect.signature(self.client.copy)
+        exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+        for name in init_signature.parameters.keys():
+            if name in exclude_params:
+                continue
+
+            copy_param = copy_signature.parameters.get(name)
+            assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+    def test_copy_build_request(self) -> None:
+        options = FinalRequestOptions(method="get", url="/foo")
+
+        def build_request(options: FinalRequestOptions) -> None:
+            client = self.client.copy()
+            client._build_request(options)
+
+        # ensure that the machinery is warmed up before tracing starts.
+        build_request(options)
+        gc.collect()
+
+        tracemalloc.start(1000)
+
+        snapshot_before = tracemalloc.take_snapshot()
+
+        ITERATIONS = 10
+        for _ in range(ITERATIONS):
+            build_request(options)
+
+        gc.collect()
+        snapshot_after = tracemalloc.take_snapshot()
+
+        tracemalloc.stop()
+
+        def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+            if diff.count == 0:
+                # Avoid false positives by considering only leaks (i.e. allocations that persist).
+                return
+
+            if diff.count % ITERATIONS != 0:
+                # Avoid false positives by considering only leaks that appear per iteration.
+                return
+
+            for frame in diff.traceback:
+                if any(
+                    frame.filename.endswith(fragment)
+                    for fragment in [
+                        # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+                        #
+                        # removing the decorator fixes the leak for reasons we don't understand.
+                        "codex/_legacy_response.py",
+                        "codex/_response.py",
+                        # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+                        "codex/_compat.py",
+                        # Standard library leaks we don't care about.
+                        "/logging/__init__.py",
+                    ]
+                ):
+                    return
+
+            leaks.append(diff)
+
+        leaks: list[tracemalloc.StatisticDiff] = []
+        for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+            add_leak(leaks, diff)
+        if leaks:
+            for leak in leaks:
+                print("MEMORY LEAK:", leak)
+                for frame in leak.traceback:
+                    print(frame)
+            raise AssertionError()
+
+    async def test_request_timeout(self) -> None:
+        request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == DEFAULT_TIMEOUT
+
+        request = self.client._build_request(
+            FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
+        )
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(100.0)
+
+    async def test_client_timeout_option(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            timeout=httpx.Timeout(0),
+        )
+
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+        assert timeout == httpx.Timeout(0)
+
+    async def test_http_client_timeout_option(self) -> None:
+        # custom timeout given to the httpx client should be used
+        async with httpx.AsyncClient(timeout=None) as http_client:
+            client = AsyncCodex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == httpx.Timeout(None)
+
+        # no timeout given to the httpx client should not use the httpx default
+        async with httpx.AsyncClient() as http_client:
+            client = AsyncCodex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT
+
+        # explicitly passing the default timeout currently results in it being ignored
+        async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+            client = AsyncCodex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=http_client,
+            )
+
+            request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+            timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
+            assert timeout == DEFAULT_TIMEOUT  # our default
+
+    def test_invalid_http_client(self) -> None:
+        with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+            with httpx.Client() as http_client:
+                AsyncCodex(
+                    base_url=base_url,
+                    bearer_token=bearer_token,
+                    api_key=api_key,
+                    access_key=access_key,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
+
+    def test_default_headers_option(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={"X-Foo": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "bar"
+        assert request.headers.get("x-stainless-lang") == "python"
+
+        client2 = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_headers={
+                "X-Foo": "stainless",
+                "X-Stainless-Lang": "my-overriding-header",
+            },
+        )
+        request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+        assert request.headers.get("x-foo") == "stainless"
+        assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+    def test_default_query_option(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
+        request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"query_param": "bar"}
+
+        request = client._build_request(
+            FinalRequestOptions(
+                method="get",
+                url="/foo",
+                params={"foo": "baz", "query_param": "overridden"},
+            )
+        )
+        url = httpx.URL(request.url)
+        assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+    def test_request_extra_json(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": False}
+
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                extra_json={"baz": False},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"baz": False}
+
+        # `extra_json` takes priority over `json_data` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar", "baz": True},
+                extra_json={"baz": None},
+            ),
+        )
+        data = json.loads(request.content.decode("utf-8"))
+        assert data == {"foo": "bar", "baz": None}
+
+    def test_request_extra_headers(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(extra_headers={"X-Foo": "Foo"}),
+            ),
+        )
+        assert request.headers.get("X-Foo") == "Foo"
+
+        # `extra_headers` takes priority over `default_headers` when keys clash
+        request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_headers={"X-Bar": "false"},
+                ),
+            ),
+        )
+        assert request.headers.get("X-Bar") == "false"
+
+    def test_request_extra_query(self) -> None:
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    extra_query={"my_query_param": "Foo"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"my_query_param": "Foo"}
+
+        # if both `query` and `extra_query` are given, they are merged
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"bar": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"bar": "1", "foo": "2"}
+
+        # `extra_query` takes priority over `query` when keys clash
+        request = self.client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                **make_request_options(
+                    query={"foo": "1"},
+                    extra_query={"foo": "2"},
+                ),
+            ),
+        )
+        params = dict(request.url.params)
+        assert params == {"foo": "2"}
+
+    def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None:
+        request = async_client._build_request(
+            FinalRequestOptions.construct(
+                method="get",
+                url="/foo",
+                headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+                json_data={"array": ["foo", "bar"]},
+                files=[("foo.txt", b"hello world")],
+            )
+        )
+
+        assert request.read().split(b"\r\n") == [
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"foo",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="array[]"',
+            b"",
+            b"bar",
+            b"--6b7ba517decee4a450543ea6ae821c82",
+            b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+            b"Content-Type: application/octet-stream",
+            b"",
+            b"hello world",
+            b"--6b7ba517decee4a450543ea6ae821c82--",
+            b"",
+        ]
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+        class Model1(BaseModel):
+            name: str
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+        """Union of objects with the same field name using a different type"""
+
+        class Model1(BaseModel):
+            foo: int
+
+        class Model2(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model2)
+        assert response.foo == "bar"
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+        response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+        assert isinstance(response, Model1)
+        assert response.foo == 1
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+        """
+        Response that sets Content-Type to something other than application/json but returns json data
+        """
+
+        class Model(BaseModel):
+            foo: int
+
+        respx_mock.get("/foo").mock(
+            return_value=httpx.Response(
+                200,
+                content=json.dumps({"foo": 2}),
+                headers={"Content-Type": "application/text"},
+            )
+        )
+
+        response = await self.client.get("/foo", cast_to=Model)
+        assert isinstance(response, Model)
+        assert response.foo == 2
+
+    def test_base_url_setter(self) -> None:
+        client = AsyncCodex(
+            base_url="https://example.com/from_init",
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        assert client.base_url == "https://example.com/from_init/"
+
+        client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
+
+        assert client.base_url == "https://example.com/from_setter/"
+
+    def test_base_url_env(self) -> None:
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = AsyncCodex(
+                bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True
+            )
+            assert client.base_url == "http://localhost:5000/from/env/"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "http://localhost:5000/custom/path/foo"
+
+    @pytest.mark.parametrize(
+        "client",
+        [
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+            ),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/",
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                http_client=httpx.AsyncClient(),
+            ),
+        ],
+        ids=["standard", "custom http client"],
+    )
+    def test_absolute_request_url(self, client: AsyncCodex) -> None:
+        request = client._build_request(
+            FinalRequestOptions(
+                method="post",
+                url="https://myapi.com/foo",
+                json_data={"foo": "bar"},
+            ),
+        )
+        assert request.url == "https://myapi.com/foo"
+
+    async def test_copied_client_does_not_close_http(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        assert not client.is_closed()
+
+        copied = client.copy()
+        assert copied is not client
+
+        del copied
+
+        await asyncio.sleep(0.2)
+        assert not client.is_closed()
+
+    async def test_client_context_manager(self) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+        async with client as c2:
+            assert c2 is client
+            assert not c2.is_closed()
+            assert not client.is_closed()
+        assert client.is_closed()
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            foo: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
+
+        with pytest.raises(APIResponseValidationError) as exc:
+            await self.client.get("/foo", cast_to=Model)
+
+        assert isinstance(exc.value.__cause__, ValidationError)
+
+    async def test_client_max_retries_validation(self) -> None:
+        with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+            AsyncCodex(
+                base_url=base_url,
+                bearer_token=bearer_token,
+                api_key=api_key,
+                access_key=access_key,
+                _strict_response_validation=True,
+                max_retries=cast(Any, None),
+            )
+
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+        class Model(BaseModel):
+            name: str
+
+        respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+
+        strict_client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+
+        with pytest.raises(APIResponseValidationError):
+            await strict_client.get("/foo", cast_to=Model)
+
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=False,
+        )
+
+        response = await client.get("/foo", cast_to=Model)
+        assert isinstance(response, str)  # type: ignore[unreachable]
+
+    @pytest.mark.parametrize(
+        "remaining_retries,retry_after,timeout",
+        [
+            [3, "20", 20],
+            [3, "0", 0.5],
+            [3, "-10", 0.5],
+            [3, "60", 60],
+            [3, "61", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+            [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+            [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+            [3, "99999999999999999999999999999999999", 0.5],
+            [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+            [3, "", 0.5],
+            [2, "", 0.5 * 2.0],
+            [1, "", 0.5 * 4.0],
+            [-1100, "", 8],  # test large number potentially overflowing
+        ],
+    )
+    @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+    @pytest.mark.asyncio
+    async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
+        client = AsyncCodex(
+            base_url=base_url,
+            bearer_token=bearer_token,
+            api_key=api_key,
+            access_key=access_key,
+            _strict_response_validation=True,
+        )
+
+        headers = httpx.Headers({"retry-after": retry_after})
+        options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+        calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+        assert calculated == pytest.approx(timeout, 0.5 * 0.875)  # pyright: ignore[reportUnknownMemberType]
+
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
+
+        with pytest.raises(APITimeoutError):
+            await self.client.post(
+                "/api/projects/",
+                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                cast_to=httpx.Response,
+                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
+            )
+
+        assert _get_open_connections(self.client) == 0
+
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+        respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500))
+
+        with pytest.raises(APIStatusError):
+            await self.client.post(
+                "/api/projects/",
+                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                cast_to=httpx.Response,
+                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
+            )
+
+        assert _get_open_connections(self.client) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+    async def test_retries_taken(
+        self,
+        async_client: AsyncCodex,
+        failures_before_success: int,
+        failure_mode: Literal["status", "exception"],
+        respx_mock: MockRouter,
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                if failure_mode == "exception":
+                    raise RuntimeError("oops")
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = await client.projects.with_raw_response.create(
+            config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+        )
+
+        assert response.retries_taken == failures_before_success
+        assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_omit_retry_count_header(
+        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = await client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            extra_headers={"x-stainless-retry-count": Omit()},
+        )
+
+        assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+    @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+    @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+    @pytest.mark.respx(base_url=base_url)
+    @pytest.mark.asyncio
+    async def test_overwrite_retry_count_header(
+        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
+        client = async_client.with_options(max_retries=4)
+
+        nb_retries = 0
+
+        def retry_handler(_request: httpx.Request) -> httpx.Response:
+            nonlocal nb_retries
+            if nb_retries < failures_before_success:
+                nb_retries += 1
+                return httpx.Response(500)
+            return httpx.Response(200)
+
+        respx_mock.post("/api/projects/").mock(side_effect=retry_handler)
+
+        response = await client.projects.with_raw_response.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            extra_headers={"x-stainless-retry-count": "42"},
+        )
+
+        assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+    def test_get_platform(self) -> None:
+        # A previous implementation of asyncify could leave threads unterminated when
+        # used with nest_asyncio.
+        #
+        # Since nest_asyncio.apply() is global and cannot be un-applied, this
+        # test is run in a separate process to avoid affecting other tests.
+        test_code = dedent("""
+        import asyncio
+        import nest_asyncio
+        import threading
+
+        from codex._utils import asyncify
+        from codex._base_client import get_platform 
+
+        async def test_main() -> None:
+            result = await asyncify(get_platform)()
+            print(result)
+            for thread in threading.enumerate():
+                print(thread.name)
+
+        nest_asyncio.apply()
+        asyncio.run(test_main())
+        """)
+        with subprocess.Popen(
+            [sys.executable, "-c", test_code],
+            text=True,
+        ) as process:
+            try:
+                process.wait(2)
+                if process.returncode:
+                    raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
+            except subprocess.TimeoutExpired as e:
+                process.kill()
+                raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e
diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py
new file mode 100644
index 00000000..114952b2
--- /dev/null
+++ b/tests/test_deepcopy.py
@@ -0,0 +1,58 @@
+from codex._utils import deepcopy_minimal
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+    assert obj1 == obj2
+    assert id(obj1) != id(obj2)
+
+
+def test_simple_dict() -> None:
+    obj1 = {"foo": "bar"}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+
+
+def test_nested_dict() -> None:
+    obj1 = {"foo": {"bar": True}}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1["foo"], obj2["foo"])
+
+
+def test_complex_nested_dict() -> None:
+    obj1 = {"foo": {"bar": [{"hello": "world"}]}}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1["foo"], obj2["foo"])
+    assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"])
+    assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0])
+
+
+def test_simple_list() -> None:
+    obj1 = ["a", "b", "c"]
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+
+
+def test_nested_list() -> None:
+    obj1 = ["a", [1, 2, 3]]
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert_different_identities(obj1[1], obj2[1])
+
+
+class MyObject: ...
+
+
+def test_ignores_other_types() -> None:
+    # custom classes
+    my_obj = MyObject()
+    obj1 = {"foo": my_obj}
+    obj2 = deepcopy_minimal(obj1)
+    assert_different_identities(obj1, obj2)
+    assert obj1["foo"] is my_obj
+
+    # tuples
+    obj3 = ("a", "b")
+    obj4 = deepcopy_minimal(obj3)
+    assert obj3 is obj4
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
new file mode 100644
index 00000000..9edb3f4e
--- /dev/null
+++ b/tests/test_extract_files.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from typing import Sequence
+
+import pytest
+
+from codex._types import FileTypes
+from codex._utils import extract_files
+
+
+def test_removes_files_from_input() -> None:
+    query = {"foo": "bar"}
+    assert extract_files(query, paths=[]) == []
+    assert query == {"foo": "bar"}
+
+    query2 = {"foo": b"Bar", "hello": "world"}
+    assert extract_files(query2, paths=[["foo"]]) == [("foo", b"Bar")]
+    assert query2 == {"hello": "world"}
+
+    query3 = {"foo": {"foo": {"bar": b"Bar"}}, "hello": "world"}
+    assert extract_files(query3, paths=[["foo", "foo", "bar"]]) == [("foo[foo][bar]", b"Bar")]
+    assert query3 == {"foo": {"foo": {}}, "hello": "world"}
+
+    query4 = {"foo": {"bar": b"Bar", "baz": "foo"}, "hello": "world"}
+    assert extract_files(query4, paths=[["foo", "bar"]]) == [("foo[bar]", b"Bar")]
+    assert query4 == {"hello": "world", "foo": {"baz": "foo"}}
+
+
+def test_multiple_files() -> None:
+    query = {"documents": [{"file": b"My first file"}, {"file": b"My second file"}]}
+    assert extract_files(query, paths=[["documents", "<array>", "file"]]) == [
+        ("documents[][file]", b"My first file"),
+        ("documents[][file]", b"My second file"),
+    ]
+    assert query == {"documents": [{}, {}]}
+
+
+@pytest.mark.parametrize(
+    "query,paths,expected",
+    [
+        [
+            {"foo": {"bar": "baz"}},
+            [["foo", "<array>", "bar"]],
+            [],
+        ],
+        [
+            {"foo": ["bar", "baz"]},
+            [["foo", "bar"]],
+            [],
+        ],
+        [
+            {"foo": {"bar": "baz"}},
+            [["foo", "foo"]],
+            [],
+        ],
+    ],
+    ids=["dict expecting array", "array expecting dict", "unknown keys"],
+)
+def test_ignores_incorrect_paths(
+    query: dict[str, object],
+    paths: Sequence[Sequence[str]],
+    expected: list[tuple[str, FileTypes]],
+) -> None:
+    assert extract_files(query, paths=paths) == expected
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 00000000..ca20744c
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,51 @@
+from pathlib import Path
+
+import anyio
+import pytest
+from dirty_equals import IsDict, IsList, IsBytes, IsTuple
+
+from codex._files import to_httpx_files, async_to_httpx_files
+
+readme_path = Path(__file__).parent.parent.joinpath("README.md")
+
+
+def test_pathlib_includes_file_name() -> None:
+    result = to_httpx_files({"file": readme_path})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+def test_tuple_input() -> None:
+    result = to_httpx_files([("file", readme_path)])
+    print(result)
+    assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+@pytest.mark.asyncio
+async def test_async_pathlib_includes_file_name() -> None:
+    result = await async_to_httpx_files({"file": readme_path})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_supports_anyio_path() -> None:
+    result = await async_to_httpx_files({"file": anyio.Path(readme_path)})
+    print(result)
+    assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_tuple_input() -> None:
+    result = await async_to_httpx_files([("file", readme_path)])
+    print(result)
+    assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+def test_string_not_allowed() -> None:
+    with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"):
+        to_httpx_files(
+            {
+                "file": "foo",  # type: ignore
+            }
+        )
diff --git a/tests/test_models.py b/tests/test_models.py
new file mode 100644
index 00000000..26df341f
--- /dev/null
+++ b/tests/test_models.py
@@ -0,0 +1,856 @@
+import json
+from typing import Any, Dict, List, Union, Optional, cast
+from datetime import datetime, timezone
+from typing_extensions import Literal, Annotated, TypeAliasType
+
+import pytest
+import pydantic
+from pydantic import Field
+
+from codex._utils import PropertyInfo
+from codex._compat import PYDANTIC_V2, parse_obj, model_dump, model_json
+from codex._models import BaseModel, construct_type
+
+
+class BasicModel(BaseModel):
+    foo: str
+
+
+@pytest.mark.parametrize("value", ["hello", 1], ids=["correct type", "mismatched"])
+def test_basic(value: object) -> None:
+    m = BasicModel.construct(foo=value)
+    assert m.foo == value
+
+
+def test_directly_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: BasicModel
+
+    m = NestedModel.construct(nested={"foo": "Foo!"})
+    assert m.nested.foo == "Foo!"
+
+    # mismatched types
+    m = NestedModel.construct(nested="hello!")
+    assert cast(Any, m.nested) == "hello!"
+
+
+def test_optional_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Optional[BasicModel]
+
+    m1 = NestedModel.construct(nested=None)
+    assert m1.nested is None
+
+    m2 = NestedModel.construct(nested={"foo": "bar"})
+    assert m2.nested is not None
+    assert m2.nested.foo == "bar"
+
+    # mismatched types
+    m3 = NestedModel.construct(nested={"foo"})
+    assert isinstance(cast(Any, m3.nested), set)
+    assert cast(Any, m3.nested) == {"foo"}
+
+
+def test_list_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: List[BasicModel]
+
+    m = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+    assert m.nested is not None
+    assert isinstance(m.nested, list)
+    assert len(m.nested) == 2
+    assert m.nested[0].foo == "bar"
+    assert m.nested[1].foo == "2"
+
+    # mismatched types
+    m = NestedModel.construct(nested=True)
+    assert cast(Any, m.nested) is True
+
+    m = NestedModel.construct(nested=[False])
+    assert cast(Any, m.nested) == [False]
+
+
+def test_optional_list_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Optional[List[BasicModel]]
+
+    m1 = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+    assert m1.nested is not None
+    assert isinstance(m1.nested, list)
+    assert len(m1.nested) == 2
+    assert m1.nested[0].foo == "bar"
+    assert m1.nested[1].foo == "2"
+
+    m2 = NestedModel.construct(nested=None)
+    assert m2.nested is None
+
+    # mismatched types
+    m3 = NestedModel.construct(nested={1})
+    assert cast(Any, m3.nested) == {1}
+
+    m4 = NestedModel.construct(nested=[False])
+    assert cast(Any, m4.nested) == [False]
+
+
+def test_list_optional_items_nested_model() -> None:
+    class NestedModel(BaseModel):
+        nested: List[Optional[BasicModel]]
+
+    m = NestedModel.construct(nested=[None, {"foo": "bar"}])
+    assert m.nested is not None
+    assert isinstance(m.nested, list)
+    assert len(m.nested) == 2
+    assert m.nested[0] is None
+    assert m.nested[1] is not None
+    assert m.nested[1].foo == "bar"
+
+    # mismatched types
+    m3 = NestedModel.construct(nested="foo")
+    assert cast(Any, m3.nested) == "foo"
+
+    m4 = NestedModel.construct(nested=[False])
+    assert cast(Any, m4.nested) == [False]
+
+
+def test_list_mismatched_type() -> None:
+    class NestedModel(BaseModel):
+        nested: List[str]
+
+    m = NestedModel.construct(nested=False)
+    assert cast(Any, m.nested) is False
+
+
+def test_raw_dictionary() -> None:
+    class NestedModel(BaseModel):
+        nested: Dict[str, str]
+
+    m = NestedModel.construct(nested={"hello": "world"})
+    assert m.nested == {"hello": "world"}
+
+    # mismatched types
+    m = NestedModel.construct(nested=False)
+    assert cast(Any, m.nested) is False
+
+
+def test_nested_dictionary_model() -> None:
+    class NestedModel(BaseModel):
+        nested: Dict[str, BasicModel]
+
+    m = NestedModel.construct(nested={"hello": {"foo": "bar"}})
+    assert isinstance(m.nested, dict)
+    assert m.nested["hello"].foo == "bar"
+
+    # mismatched types
+    m = NestedModel.construct(nested={"hello": False})
+    assert cast(Any, m.nested["hello"]) is False
+
+
+def test_unknown_fields() -> None:
+    m1 = BasicModel.construct(foo="foo", unknown=1)
+    assert m1.foo == "foo"
+    assert cast(Any, m1).unknown == 1
+
+    m2 = BasicModel.construct(foo="foo", unknown={"foo_bar": True})
+    assert m2.foo == "foo"
+    assert cast(Any, m2).unknown == {"foo_bar": True}
+
+    assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}}
+
+
+def test_strict_validation_unknown_fields() -> None:
+    class Model(BaseModel):
+        foo: str
+
+    model = parse_obj(Model, dict(foo="hello!", user="Robert"))
+    assert model.foo == "hello!"
+    assert cast(Any, model).user == "Robert"
+
+    assert model_dump(model) == {"foo": "hello!", "user": "Robert"}
+
+
+def test_aliases() -> None:
+    class Model(BaseModel):
+        my_field: int = Field(alias="myField")
+
+    m = Model.construct(myField=1)
+    assert m.my_field == 1
+
+    # mismatched types
+    m = Model.construct(myField={"hello": False})
+    assert cast(Any, m.my_field) == {"hello": False}
+
+
+def test_repr() -> None:
+    model = BasicModel(foo="bar")
+    assert str(model) == "BasicModel(foo='bar')"
+    assert repr(model) == "BasicModel(foo='bar')"
+
+
+def test_repr_nested_model() -> None:
+    class Child(BaseModel):
+        name: str
+        age: int
+
+    class Parent(BaseModel):
+        name: str
+        child: Child
+
+    model = Parent(name="Robert", child=Child(name="Foo", age=5))
+    assert str(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+    assert repr(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+
+
+def test_optional_list() -> None:
+    class Submodel(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: Optional[List[Submodel]]
+
+    m = Model.construct(items=None)
+    assert m.items is None
+
+    m = Model.construct(items=[])
+    assert m.items == []
+
+    m = Model.construct(items=[{"name": "Robert"}])
+    assert m.items is not None
+    assert len(m.items) == 1
+    assert m.items[0].name == "Robert"
+
+
+def test_nested_union_of_models() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Submodel2(BaseModel):
+        thing: str
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2]
+
+    m = Model.construct(foo={"thing": "hello"})
+    assert isinstance(m.foo, Submodel2)
+    assert m.foo.thing == "hello"
+
+
+def test_nested_union_of_mixed_types() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Literal[True], Literal["CARD_HOLDER"]]
+
+    m = Model.construct(foo=True)
+    assert m.foo is True
+
+    m = Model.construct(foo="CARD_HOLDER")
+    assert m.foo == "CARD_HOLDER"
+
+    m = Model.construct(foo={"bar": False})
+    assert isinstance(m.foo, Submodel1)
+    assert m.foo.bar is False
+
+
+def test_nested_union_multiple_variants() -> None:
+    class Submodel1(BaseModel):
+        bar: bool
+
+    class Submodel2(BaseModel):
+        thing: str
+
+    class Submodel3(BaseModel):
+        foo: int
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2, None, Submodel3]
+
+    m = Model.construct(foo={"thing": "hello"})
+    assert isinstance(m.foo, Submodel2)
+    assert m.foo.thing == "hello"
+
+    m = Model.construct(foo=None)
+    assert m.foo is None
+
+    m = Model.construct()
+    assert m.foo is None
+
+    m = Model.construct(foo={"foo": "1"})
+    assert isinstance(m.foo, Submodel3)
+    assert m.foo.foo == 1
+
+
+def test_nested_union_invalid_data() -> None:
+    class Submodel1(BaseModel):
+        level: int
+
+    class Submodel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        foo: Union[Submodel1, Submodel2]
+
+    m = Model.construct(foo=True)
+    assert cast(bool, m.foo) is True
+
+    m = Model.construct(foo={"name": 3})
+    if PYDANTIC_V2:
+        assert isinstance(m.foo, Submodel1)
+        assert m.foo.name == 3  # type: ignore
+    else:
+        assert isinstance(m.foo, Submodel2)
+        assert m.foo.name == "3"
+
+
+def test_list_of_unions() -> None:
+    class Submodel1(BaseModel):
+        level: int
+
+    class Submodel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: List[Union[Submodel1, Submodel2]]
+
+    m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], Submodel1)
+    assert m.items[0].level == 1
+    assert isinstance(m.items[1], Submodel2)
+    assert m.items[1].name == "Robert"
+
+    m = Model.construct(items=[{"level": -1}, 156])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], Submodel1)
+    assert m.items[0].level == -1
+    assert cast(Any, m.items[1]) == 156
+
+
+def test_union_of_lists() -> None:
+    class SubModel1(BaseModel):
+        level: int
+
+    class SubModel2(BaseModel):
+        name: str
+
+    class Model(BaseModel):
+        items: Union[List[SubModel1], List[SubModel2]]
+
+    # with one valid entry
+    m = Model.construct(items=[{"name": "Robert"}])
+    assert len(m.items) == 1
+    assert isinstance(m.items[0], SubModel2)
+    assert m.items[0].name == "Robert"
+
+    # with two entries pointing to different types
+    m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], SubModel1)
+    assert m.items[0].level == 1
+    assert isinstance(m.items[1], SubModel1)
+    assert cast(Any, m.items[1]).name == "Robert"
+
+    # with two entries pointing to *completely* different types
+    m = Model.construct(items=[{"level": -1}, 156])
+    assert len(m.items) == 2
+    assert isinstance(m.items[0], SubModel1)
+    assert m.items[0].level == -1
+    assert cast(Any, m.items[1]) == 156
+
+
+def test_dict_of_union() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        foo: str
+
+    class Model(BaseModel):
+        data: Dict[str, Union[SubModel1, SubModel2]]
+
+    m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+    assert len(list(m.data.keys())) == 2
+    assert isinstance(m.data["hello"], SubModel1)
+    assert m.data["hello"].name == "there"
+    assert isinstance(m.data["foo"], SubModel2)
+    assert m.data["foo"].foo == "bar"
+
+    # TODO: test mismatched type
+
+
+def test_double_nested_union() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        bar: str
+
+    class Model(BaseModel):
+        data: Dict[str, List[Union[SubModel1, SubModel2]]]
+
+    m = Model.construct(data={"foo": [{"bar": "baz"}, {"name": "Robert"}]})
+    assert len(m.data["foo"]) == 2
+
+    entry1 = m.data["foo"][0]
+    assert isinstance(entry1, SubModel2)
+    assert entry1.bar == "baz"
+
+    entry2 = m.data["foo"][1]
+    assert isinstance(entry2, SubModel1)
+    assert entry2.name == "Robert"
+
+    # TODO: test mismatched type
+
+
+def test_union_of_dict() -> None:
+    class SubModel1(BaseModel):
+        name: str
+
+    class SubModel2(BaseModel):
+        foo: str
+
+    class Model(BaseModel):
+        data: Union[Dict[str, SubModel1], Dict[str, SubModel2]]
+
+    m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+    assert len(list(m.data.keys())) == 2
+    assert isinstance(m.data["hello"], SubModel1)
+    assert m.data["hello"].name == "there"
+    assert isinstance(m.data["foo"], SubModel1)
+    assert cast(Any, m.data["foo"]).foo == "bar"
+
+
+def test_iso8601_datetime() -> None:
+    class Model(BaseModel):
+        created_at: datetime
+
+    expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc)
+
+    if PYDANTIC_V2:
+        expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}'
+    else:
+        expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}'
+
+    model = Model.construct(created_at="2019-12-27T18:11:19.117Z")
+    assert model.created_at == expected
+    assert model_json(model) == expected_json
+
+    model = parse_obj(Model, dict(created_at="2019-12-27T18:11:19.117Z"))
+    assert model.created_at == expected
+    assert model_json(model) == expected_json
+
+
+def test_does_not_coerce_int() -> None:
+    class Model(BaseModel):
+        bar: int
+
+    assert Model.construct(bar=1).bar == 1
+    assert Model.construct(bar=10.9).bar == 10.9
+    assert Model.construct(bar="19").bar == "19"  # type: ignore[comparison-overlap]
+    assert Model.construct(bar=False).bar is False
+
+
+def test_int_to_float_safe_conversion() -> None:
+    class Model(BaseModel):
+        float_field: float
+
+    m = Model.construct(float_field=10)
+    assert m.float_field == 10.0
+    assert isinstance(m.float_field, float)
+
+    m = Model.construct(float_field=10.12)
+    assert m.float_field == 10.12
+    assert isinstance(m.float_field, float)
+
+    # number too big
+    m = Model.construct(float_field=2**53 + 1)
+    assert m.float_field == 2**53 + 1
+    assert isinstance(m.float_field, int)
+
+
+def test_deprecated_alias() -> None:
+    class Model(BaseModel):
+        resource_id: str = Field(alias="model_id")
+
+        @property
+        def model_id(self) -> str:
+            return self.resource_id
+
+    m = Model.construct(model_id="id")
+    assert m.model_id == "id"
+    assert m.resource_id == "id"
+    assert m.resource_id is m.model_id
+
+    m = parse_obj(Model, {"model_id": "id"})
+    assert m.model_id == "id"
+    assert m.resource_id == "id"
+    assert m.resource_id is m.model_id
+
+
+def test_omitted_fields() -> None:
+    class Model(BaseModel):
+        resource_id: Optional[str] = None
+
+    m = Model.construct()
+    assert "resource_id" not in m.model_fields_set
+
+    m = Model.construct(resource_id=None)
+    assert "resource_id" in m.model_fields_set
+
+    m = Model.construct(resource_id="foo")
+    assert "resource_id" in m.model_fields_set
+
+
+def test_to_dict() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert m.to_dict() == {"FOO": "hello"}
+    assert m.to_dict(use_api_names=False) == {"foo": "hello"}
+
+    m2 = Model()
+    assert m2.to_dict() == {}
+    assert m2.to_dict(exclude_unset=False) == {"FOO": None}
+    assert m2.to_dict(exclude_unset=False, exclude_none=True) == {}
+    assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {}
+
+    m3 = Model(FOO=None)
+    assert m3.to_dict() == {"FOO": None}
+    assert m3.to_dict(exclude_none=True) == {}
+    assert m3.to_dict(exclude_defaults=True) == {}
+
+    class Model2(BaseModel):
+        created_at: datetime
+
+    time_str = "2024-03-21T11:39:01.275859"
+    m4 = Model2.construct(created_at=time_str)
+    assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)}
+    assert m4.to_dict(mode="json") == {"created_at": time_str}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.to_dict(warnings=False)
+
+
+def test_forwards_compat_model_dump_method() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert m.model_dump() == {"foo": "hello"}
+    assert m.model_dump(include={"bar"}) == {}
+    assert m.model_dump(exclude={"foo"}) == {}
+    assert m.model_dump(by_alias=True) == {"FOO": "hello"}
+
+    m2 = Model()
+    assert m2.model_dump() == {"foo": None}
+    assert m2.model_dump(exclude_unset=True) == {}
+    assert m2.model_dump(exclude_none=True) == {}
+    assert m2.model_dump(exclude_defaults=True) == {}
+
+    m3 = Model(FOO=None)
+    assert m3.model_dump() == {"foo": None}
+    assert m3.model_dump(exclude_none=True) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+            m.model_dump(round_trip=True)
+
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.model_dump(warnings=False)
+
+
+def test_compat_method_no_error_for_warnings() -> None:
+    class Model(BaseModel):
+        foo: Optional[str]
+
+    m = Model(foo="hello")
+    assert isinstance(model_dump(m, warnings=False), dict)
+
+
+def test_to_json() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert json.loads(m.to_json()) == {"FOO": "hello"}
+    assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"}
+
+    if PYDANTIC_V2:
+        assert m.to_json(indent=None) == '{"FOO":"hello"}'
+    else:
+        assert m.to_json(indent=None) == '{"FOO": "hello"}'
+
+    m2 = Model()
+    assert json.loads(m2.to_json()) == {}
+    assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None}
+    assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {}
+    assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {}
+
+    m3 = Model(FOO=None)
+    assert json.loads(m3.to_json()) == {"FOO": None}
+    assert json.loads(m3.to_json(exclude_none=True)) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.to_json(warnings=False)
+
+
+def test_forwards_compat_model_dump_json_method() -> None:
+    class Model(BaseModel):
+        foo: Optional[str] = Field(alias="FOO", default=None)
+
+    m = Model(FOO="hello")
+    assert json.loads(m.model_dump_json()) == {"foo": "hello"}
+    assert json.loads(m.model_dump_json(include={"bar"})) == {}
+    assert json.loads(m.model_dump_json(include={"foo"})) == {"foo": "hello"}
+    assert json.loads(m.model_dump_json(by_alias=True)) == {"FOO": "hello"}
+
+    assert m.model_dump_json(indent=2) == '{\n  "foo": "hello"\n}'
+
+    m2 = Model()
+    assert json.loads(m2.model_dump_json()) == {"foo": None}
+    assert json.loads(m2.model_dump_json(exclude_unset=True)) == {}
+    assert json.loads(m2.model_dump_json(exclude_none=True)) == {}
+    assert json.loads(m2.model_dump_json(exclude_defaults=True)) == {}
+
+    m3 = Model(FOO=None)
+    assert json.loads(m3.model_dump_json()) == {"foo": None}
+    assert json.loads(m3.model_dump_json(exclude_none=True)) == {}
+
+    if not PYDANTIC_V2:
+        with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+            m.model_dump_json(round_trip=True)
+
+        with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+            m.model_dump_json(warnings=False)
+
+
+def test_type_compat() -> None:
+    # our model type can be assigned to Pydantic's model type
+
+    def takes_pydantic(model: pydantic.BaseModel) -> None:  # noqa: ARG001
+        ...
+
+    class OurModel(BaseModel):
+        foo: Optional[str] = None
+
+    takes_pydantic(OurModel())
+
+
+def test_annotated_types() -> None:
+    class Model(BaseModel):
+        value: str
+
+    m = construct_type(
+        value={"value": "foo"},
+        type_=cast(Any, Annotated[Model, "random metadata"]),
+    )
+    assert isinstance(m, Model)
+    assert m.value == "foo"
+
+
+def test_discriminated_unions_invalid_data() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "a", "data": 100},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, A)
+    assert m.type == "a"
+    if PYDANTIC_V2:
+        assert m.data == 100  # type: ignore[comparison-overlap]
+    else:
+        # pydantic v1 automatically converts inputs to strings
+        # if the expected type is a str
+        assert m.data == "100"
+
+
+def test_discriminated_unions_unknown_variant() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "c", "data": None, "new_thing": "bar"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+
+    # just chooses the first variant
+    assert isinstance(m, A)
+    assert m.type == "c"  # type: ignore[comparison-overlap]
+    assert m.data == None  # type: ignore[unreachable]
+    assert m.new_thing == "bar"
+
+
+def test_discriminated_unions_invalid_data_nested_unions() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    class C(BaseModel):
+        type: Literal["c"]
+
+        data: bool
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "c", "data": "foo"},
+        type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, C)
+    assert m.type == "c"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_with_aliases_invalid_data() -> None:
+    class A(BaseModel):
+        foo_type: Literal["a"] = Field(alias="type")
+
+        data: str
+
+    class B(BaseModel):
+        foo_type: Literal["b"] = Field(alias="type")
+
+        data: int
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+    )
+    assert isinstance(m, B)
+    assert m.foo_type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    m = construct_type(
+        value={"type": "a", "data": 100},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+    )
+    assert isinstance(m, A)
+    assert m.foo_type == "a"
+    if PYDANTIC_V2:
+        assert m.data == 100  # type: ignore[comparison-overlap]
+    else:
+        # pydantic v1 automatically converts inputs to strings
+        # if the expected type is a str
+        assert m.data == "100"
+
+
+def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: bool
+
+    class B(BaseModel):
+        type: Literal["a"]
+
+        data: int
+
+    m = construct_type(
+        value={"type": "a", "data": "foo"},
+        type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+    )
+    assert isinstance(m, B)
+    assert m.type == "a"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_invalid_data_uses_cache() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: str
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: int
+
+    UnionType = cast(Any, Union[A, B])
+
+    assert not hasattr(UnionType, "__discriminator__")
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    discriminator = UnionType.__discriminator__
+    assert discriminator is not None
+
+    m = construct_type(
+        value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+    )
+    assert isinstance(m, B)
+    assert m.type == "b"
+    assert m.data == "foo"  # type: ignore[comparison-overlap]
+
+    # if the discriminator details object stays the same between invocations then
+    # we hit the cache
+    assert UnionType.__discriminator__ is discriminator
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
+def test_type_alias_type() -> None:
+    Alias = TypeAliasType("Alias", str)
+
+    class Model(BaseModel):
+        alias: Alias
+        union: Union[int, Alias]
+
+    m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model)
+    assert isinstance(m, Model)
+    assert isinstance(m.alias, str)
+    assert m.alias == "foo"
+    assert isinstance(m.union, str)
+    assert m.union == "bar"
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
+def test_field_named_cls() -> None:
+    class Model(BaseModel):
+        cls: str
+
+    m = construct_type(value={"cls": "foo"}, type_=Model)
+    assert isinstance(m, Model)
+    assert isinstance(m.cls, str)
diff --git a/tests/test_qs.py b/tests/test_qs.py
new file mode 100644
index 00000000..e1c9b4b3
--- /dev/null
+++ b/tests/test_qs.py
@@ -0,0 +1,78 @@
+from typing import Any, cast
+from functools import partial
+from urllib.parse import unquote
+
+import pytest
+
+from codex._qs import Querystring, stringify
+
+
+def test_empty() -> None:
+    assert stringify({}) == ""
+    assert stringify({"a": {}}) == ""
+    assert stringify({"a": {"b": {"c": {}}}}) == ""
+
+
+def test_basic() -> None:
+    assert stringify({"a": 1}) == "a=1"
+    assert stringify({"a": "b"}) == "a=b"
+    assert stringify({"a": True}) == "a=true"
+    assert stringify({"a": False}) == "a=false"
+    assert stringify({"a": 1.23456}) == "a=1.23456"
+    assert stringify({"a": None}) == ""
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_nested_dotted(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(nested_format="dots").stringify
+    else:
+        serialise = partial(stringify, nested_format="dots")
+
+    assert unquote(serialise({"a": {"b": "c"}})) == "a.b=c"
+    assert unquote(serialise({"a": {"b": "c", "d": "e", "f": "g"}})) == "a.b=c&a.d=e&a.f=g"
+    assert unquote(serialise({"a": {"b": {"c": {"d": "e"}}}})) == "a.b.c.d=e"
+    assert unquote(serialise({"a": {"b": True}})) == "a.b=true"
+
+
+def test_nested_brackets() -> None:
+    assert unquote(stringify({"a": {"b": "c"}})) == "a[b]=c"
+    assert unquote(stringify({"a": {"b": "c", "d": "e", "f": "g"}})) == "a[b]=c&a[d]=e&a[f]=g"
+    assert unquote(stringify({"a": {"b": {"c": {"d": "e"}}}})) == "a[b][c][d]=e"
+    assert unquote(stringify({"a": {"b": True}})) == "a[b]=true"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_comma(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(array_format="comma").stringify
+    else:
+        serialise = partial(stringify, array_format="comma")
+
+    assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar"
+    assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false"
+    assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true"
+
+
+def test_array_repeat() -> None:
+    assert unquote(stringify({"in": ["foo", "bar"]})) == "in=foo&in=bar"
+    assert unquote(stringify({"a": {"b": [True, False]}})) == "a[b]=true&a[b]=false"
+    assert unquote(stringify({"a": {"b": [True, False, None, True]}})) == "a[b]=true&a[b]=false&a[b]=true"
+    assert unquote(stringify({"in": ["foo", {"b": {"c": ["d", "e"]}}]})) == "in=foo&in[b][c]=d&in[b][c]=e"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_brackets(method: str) -> None:
+    if method == "class":
+        serialise = Querystring(array_format="brackets").stringify
+    else:
+        serialise = partial(stringify, array_format="brackets")
+
+    assert unquote(serialise({"in": ["foo", "bar"]})) == "in[]=foo&in[]=bar"
+    assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][]=true&a[b][]=false"
+    assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true"
+
+
+def test_unknown_array_format() -> None:
+    with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"):
+        stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo"))
diff --git a/tests/test_required_args.py b/tests/test_required_args.py
new file mode 100644
index 00000000..8a160534
--- /dev/null
+++ b/tests/test_required_args.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import pytest
+
+from codex._utils import required_args
+
+
+def test_too_many_positional_params() -> None:
+    @required_args(["a"])
+    def foo(a: str | None = None) -> str | None:
+        return a
+
+    with pytest.raises(TypeError, match=r"foo\(\) takes 1 argument\(s\) but 2 were given"):
+        foo("a", "b")  # type: ignore
+
+
+def test_positional_param() -> None:
+    @required_args(["a"])
+    def foo(a: str | None = None) -> str | None:
+        return a
+
+    assert foo("a") == "a"
+    assert foo(None) is None
+    assert foo(a="b") == "b"
+
+    with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+        foo()
+
+
+def test_keyword_only_param() -> None:
+    @required_args(["a"])
+    def foo(*, a: str | None = None) -> str | None:
+        return a
+
+    assert foo(a="a") == "a"
+    assert foo(a=None) is None
+    assert foo(a="b") == "b"
+
+    with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+        foo()
+
+
+def test_multiple_params() -> None:
+    @required_args(["a", "b", "c"])
+    def foo(a: str = "", *, b: str = "", c: str = "") -> str | None:
+        return f"{a} {b} {c}"
+
+    assert foo(a="a", b="b", c="c") == "a b c"
+
+    error_message = r"Missing required arguments.*"
+
+    with pytest.raises(TypeError, match=error_message):
+        foo()
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(a="a")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(b="b")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(c="c")
+
+    with pytest.raises(TypeError, match=r"Missing required argument: 'a'"):
+        foo(b="a", c="c")
+
+    with pytest.raises(TypeError, match=r"Missing required argument: 'b'"):
+        foo("a", c="c")
+
+
+def test_multiple_variants() -> None:
+    @required_args(["a"], ["b"])
+    def foo(*, a: str | None = None, b: str | None = None) -> str | None:
+        return a if a is not None else b
+
+    assert foo(a="foo") == "foo"
+    assert foo(b="bar") == "bar"
+    assert foo(a=None) is None
+    assert foo(b=None) is None
+
+    # TODO: this error message could probably be improved
+    with pytest.raises(
+        TypeError,
+        match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given",
+    ):
+        foo()
+
+
+def test_multiple_params_multiple_variants() -> None:
+    @required_args(["a", "b"], ["c"])
+    def foo(*, a: str | None = None, b: str | None = None, c: str | None = None) -> str | None:
+        if a is not None:
+            return a
+        if b is not None:
+            return b
+        return c
+
+    error_message = r"Missing required arguments; Expected either \('a' and 'b'\) or \('c'\) arguments to be given"
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(a="foo")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo(b="bar")
+
+    with pytest.raises(TypeError, match=error_message):
+        foo()
+
+    assert foo(a=None, b="bar") == "bar"
+    assert foo(c=None) is None
+    assert foo(c="foo") == "foo"
diff --git a/tests/test_response.py b/tests/test_response.py
new file mode 100644
index 00000000..224eb22e
--- /dev/null
+++ b/tests/test_response.py
@@ -0,0 +1,277 @@
+import json
+from typing import Any, List, Union, cast
+from typing_extensions import Annotated
+
+import httpx
+import pytest
+import pydantic
+
+from codex import Codex, BaseModel, AsyncCodex
+from codex._response import (
+    APIResponse,
+    BaseAPIResponse,
+    AsyncAPIResponse,
+    BinaryAPIResponse,
+    AsyncBinaryAPIResponse,
+    extract_response_type,
+)
+from codex._streaming import Stream
+from codex._base_client import FinalRequestOptions
+
+
+class ConcreteBaseAPIResponse(APIResponse[bytes]): ...
+
+
+class ConcreteAPIResponse(APIResponse[List[str]]): ...
+
+
+class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ...
+
+
+def test_extract_response_type_direct_classes() -> None:
+    assert extract_response_type(BaseAPIResponse[str]) == str
+    assert extract_response_type(APIResponse[str]) == str
+    assert extract_response_type(AsyncAPIResponse[str]) == str
+
+
+def test_extract_response_type_direct_class_missing_type_arg() -> None:
+    with pytest.raises(
+        RuntimeError,
+        match="Expected type <class 'codex._response.AsyncAPIResponse'> to have a type argument at index 0 but it did not",
+    ):
+        extract_response_type(AsyncAPIResponse)
+
+
+def test_extract_response_type_concrete_subclasses() -> None:
+    assert extract_response_type(ConcreteBaseAPIResponse) == bytes
+    assert extract_response_type(ConcreteAPIResponse) == List[str]
+    assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response
+
+
+def test_extract_response_type_binary_response() -> None:
+    assert extract_response_type(BinaryAPIResponse) == bytes
+    assert extract_response_type(AsyncBinaryAPIResponse) == bytes
+
+
+class PydanticModel(pydantic.BaseModel): ...
+
+
+def test_response_parse_mismatched_basemodel(client: Codex) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    with pytest.raises(
+        TypeError,
+        match="Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`",
+    ):
+        response.parse(to=PydanticModel)
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    with pytest.raises(
+        TypeError,
+        match="Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`",
+    ):
+        await response.parse(to=PydanticModel)
+
+
+def test_response_parse_custom_stream(client: Codex) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=client,
+        stream=True,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    stream = response.parse(to=Stream[int])
+    assert stream._cast_to == int
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo"),
+        client=async_client,
+        stream=True,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    stream = await response.parse(to=Stream[int])
+    assert stream._cast_to == int
+
+
+class CustomModel(BaseModel):
+    foo: str
+    bar: int
+
+
+def test_response_parse_custom_model(client: Codex) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(to=CustomModel)
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(to=CustomModel)
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+def test_response_parse_annotated_type(client: Codex) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(
+        to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+    )
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(
+        to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+    )
+    assert obj.foo == "hello!"
+    assert obj.bar == 2
+
+
+@pytest.mark.parametrize(
+    "content, expected",
+    [
+        ("false", False),
+        ("true", True),
+        ("False", False),
+        ("True", True),
+        ("TrUe", True),
+        ("FalSe", False),
+    ],
+)
+def test_response_parse_bool(client: Codex, content: str, expected: bool) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=content),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    result = response.parse(to=bool)
+    assert result is expected
+
+
+@pytest.mark.parametrize(
+    "content, expected",
+    [
+        ("false", False),
+        ("true", True),
+        ("False", False),
+        ("True", True),
+        ("TrUe", True),
+        ("FalSe", False),
+    ],
+)
+async def test_async_response_parse_bool(client: AsyncCodex, content: str, expected: bool) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=content),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    result = await response.parse(to=bool)
+    assert result is expected
+
+
+class OtherModel(BaseModel):
+    a: str
+
+
+@pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
+def test_response_parse_expect_model_union_non_json_content(client: Codex) -> None:
+    response = APIResponse(
+        raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+        client=client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+    assert isinstance(obj, str)
+    assert obj == "foo"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("async_client", [False], indirect=True)  # loose validation
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCodex) -> None:
+    response = AsyncAPIResponse(
+        raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+        client=async_client,
+        stream=False,
+        stream_cls=None,
+        cast_to=str,
+        options=FinalRequestOptions.construct(method="get", url="/foo"),
+    )
+
+    obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+    assert isinstance(obj, str)
+    assert obj == "foo"
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
new file mode 100644
index 00000000..443b5aa5
--- /dev/null
+++ b/tests/test_streaming.py
@@ -0,0 +1,248 @@
+from __future__ import annotations
+
+from typing import Iterator, AsyncIterator
+
+import httpx
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex._streaming import Stream, AsyncStream, ServerSentEvent
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: completion\n"
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.data == ""
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"\n"
+        yield b"event: completion\n"
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.data == ""
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.data == ""
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b'data: {"foo":true}\n'
+        yield b"\n"
+        yield b"event: completion\n"
+        yield b'data: {"bar":false}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+
+    sse = await iter_next(iterator)
+    assert sse.event == "completion"
+    assert sse.json() == {"bar": False}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"data: {\n"
+        yield b'data: "foo":\n'
+        yield b"data: \n"
+        yield b"data:\n"
+        yield b"data: true}\n"
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+    assert sse.data == '{\n"foo":\n\n\ntrue}'
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b'data: {"foo": "my long\\n\\ncontent"}'
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": "my long\n\ncontent"}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+    def body() -> Iterator[bytes]:
+        yield b"event: ping\n"
+        yield b"data: {\n"
+        yield b'data: "foo":\n'
+        yield b"data: true}\n"
+        yield b"\n\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event == "ping"
+    assert sse.json() == {"foo": True}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_special_new_line_character(
+    sync: bool,
+    client: Codex,
+    async_client: AsyncCodex,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"content":" culpa"}\n'
+        yield b"\n"
+        yield b'data: {"content":" \xe2\x80\xa8"}\n'
+        yield b"\n"
+        yield b'data: {"content":"foo"}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": " culpa"}
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": "  "}
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": "foo"}
+
+    await assert_empty_iter(iterator)
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multi_byte_character_multiple_chunks(
+    sync: bool,
+    client: Codex,
+    async_client: AsyncCodex,
+) -> None:
+    def body() -> Iterator[bytes]:
+        yield b'data: {"content":"'
+        # bytes taken from the string 'известни' and arbitrarily split
+        # so that some multi-byte characters span multiple chunks
+        yield b"\xd0"
+        yield b"\xb8\xd0\xb7\xd0"
+        yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8"
+        yield b'"}\n'
+        yield b"\n"
+
+    iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+    sse = await iter_next(iterator)
+    assert sse.event is None
+    assert sse.json() == {"content": "известни"}
+
+
+async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]:
+    for chunk in iter:
+        yield chunk
+
+
+async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent:
+    if isinstance(iter, AsyncIterator):
+        return await iter.__anext__()
+
+    return next(iter)
+
+
+async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None:
+    with pytest.raises((StopAsyncIteration, RuntimeError)):
+        await iter_next(iter)
+
+
+def make_event_iterator(
+    content: Iterator[bytes],
+    *,
+    sync: bool,
+    client: Codex,
+    async_client: AsyncCodex,
+) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
+    if sync:
+        return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()
+
+    return AsyncStream(
+        cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content))
+    )._iter_events()
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 00000000..2e918885
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,425 @@
+from __future__ import annotations
+
+import io
+import pathlib
+from typing import Any, List, Union, TypeVar, Iterable, Optional, cast
+from datetime import date, datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+import pytest
+
+from codex._types import Base64FileInput
+from codex._utils import (
+    PropertyInfo,
+    transform as _transform,
+    parse_datetime,
+    async_transform as _async_transform,
+)
+from codex._compat import PYDANTIC_V2
+from codex._models import BaseModel
+
+_T = TypeVar("_T")
+
+SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt")
+
+
+async def transform(
+    data: _T,
+    expected_type: object,
+    use_async: bool,
+) -> _T:
+    if use_async:
+        return await _async_transform(data, expected_type=expected_type)
+
+    return _transform(data, expected_type=expected_type)
+
+
+parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"])
+
+
+class Foo1(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_top_level_alias(use_async: bool) -> None:
+    assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"}
+
+
+class Foo2(TypedDict):
+    bar: Bar2
+
+
+class Bar2(TypedDict):
+    this_thing: Annotated[int, PropertyInfo(alias="this__thing")]
+    baz: Annotated[Baz2, PropertyInfo(alias="Baz")]
+
+
+class Baz2(TypedDict):
+    my_baz: Annotated[str, PropertyInfo(alias="myBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_recursive_typeddict(use_async: bool) -> None:
+    assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}}
+    assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}}
+
+
+class Foo3(TypedDict):
+    things: List[Bar3]
+
+
+class Bar3(TypedDict):
+    my_field: Annotated[str, PropertyInfo(alias="myField")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_list_of_typeddict(use_async: bool) -> None:
+    result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async)
+    assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]}
+
+
+class Foo4(TypedDict):
+    foo: Union[Bar4, Baz4]
+
+
+class Bar4(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz4(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_typeddict(use_async: bool) -> None:
+    assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}}
+    assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}}
+    assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == {
+        "foo": {"fooBaz": "baz", "fooBar": "bar"}
+    }
+
+
+class Foo5(TypedDict):
+    foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")]
+
+
+class Bar5(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz5(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_list(use_async: bool) -> None:
+    assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}}
+    assert await transform(
+        {
+            "foo": [
+                {"foo_baz": "baz"},
+                {"foo_baz": "baz"},
+            ]
+        },
+        Foo5,
+        use_async,
+    ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]}
+
+
+class Foo6(TypedDict):
+    bar: Annotated[str, PropertyInfo(alias="Bar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_includes_unknown_keys(use_async: bool) -> None:
+    assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == {
+        "Bar": "bar",
+        "baz_": {"FOO": 1},
+    }
+
+
+class Foo7(TypedDict):
+    bar: Annotated[List[Bar7], PropertyInfo(alias="bAr")]
+    foo: Bar7
+
+
+class Bar7(TypedDict):
+    foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_ignores_invalid_input(use_async: bool) -> None:
+    assert await transform({"bar": "<foo>"}, Foo7, use_async) == {"bAr": "<foo>"}
+    assert await transform({"foo": "<foo>"}, Foo7, use_async) == {"foo": "<foo>"}
+
+
+class DatetimeDict(TypedDict, total=False):
+    foo: Annotated[datetime, PropertyInfo(format="iso8601")]
+
+    bar: Annotated[Optional[datetime], PropertyInfo(format="iso8601")]
+
+    required: Required[Annotated[Optional[datetime], PropertyInfo(format="iso8601")]]
+
+    list_: Required[Annotated[Optional[List[datetime]], PropertyInfo(format="iso8601")]]
+
+    union: Annotated[Union[int, datetime], PropertyInfo(format="iso8601")]
+
+
+class DateDict(TypedDict, total=False):
+    foo: Annotated[date, PropertyInfo(format="iso8601")]
+
+
+class DatetimeModel(BaseModel):
+    foo: datetime
+
+
+class DateModel(BaseModel):
+    foo: Optional[date]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    tz = "Z" if PYDANTIC_V2 else "+00:00"
+    assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"}  # type: ignore[comparison-overlap]
+    assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz}  # type: ignore[comparison-overlap]
+
+    dt = dt.replace(tzinfo=None)
+    assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"}  # type: ignore[comparison-overlap]
+    assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"}  # type: ignore[comparison-overlap]
+
+    assert await transform({"foo": None}, DateDict, use_async) == {"foo": None}  # type: ignore[comparison-overlap]
+    assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None}  # type: ignore
+    assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"}  # type: ignore[comparison-overlap]
+    assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == {
+        "foo": "2023-02-23"
+    }  # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_optional_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"}  # type: ignore[comparison-overlap]
+
+    assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_required_iso8601_format(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"required": dt}, DatetimeDict, use_async) == {
+        "required": "2023-02-23T14:16:36.337692+00:00"
+    }  # type: ignore[comparison-overlap]
+
+    assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_datetime(use_async: bool) -> None:
+    dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    assert await transform({"union": dt}, DatetimeDict, use_async) == {  # type: ignore[comparison-overlap]
+        "union": "2023-02-23T14:16:36.337692+00:00"
+    }
+
+    assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_nested_list_iso6801_format(use_async: bool) -> None:
+    dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+    dt2 = parse_datetime("2022-01-15T06:34:23Z")
+    assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == {  # type: ignore[comparison-overlap]
+        "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"]
+    }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_custom_format(use_async: bool) -> None:
+    dt = parse_datetime("2022-01-15T06:34:23Z")
+
+    result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async)
+    assert result == "06"  # type: ignore[comparison-overlap]
+
+
+class DateDictWithRequiredAlias(TypedDict, total=False):
+    required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_with_alias(use_async: bool) -> None:
+    assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None}  # type: ignore[comparison-overlap]
+    assert await transform(
+        {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async
+    ) == {"prop": "2023-02-23"}  # type: ignore[comparison-overlap]
+
+
+class MyModel(BaseModel):
+    foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_model_to_dictionary(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+    assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_empty_model(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_unknown_field(use_async: bool) -> None:
+    assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == {
+        "my_untyped_field": True
+    }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_types(use_async: bool) -> None:
+    model = MyModel.construct(foo=True)
+    if PYDANTIC_V2:
+        with pytest.warns(UserWarning):
+            params = await transform(model, Any, use_async)
+    else:
+        params = await transform(model, Any, use_async)
+    assert cast(Any, params) == {"foo": True}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_object_type(use_async: bool) -> None:
+    model = MyModel.construct(foo=MyModel.construct(hello="world"))
+    if PYDANTIC_V2:
+        with pytest.warns(UserWarning):
+            params = await transform(model, Any, use_async)
+    else:
+        params = await transform(model, Any, use_async)
+    assert cast(Any, params) == {"foo": {"hello": "world"}}
+
+
+class ModelNestedObjects(BaseModel):
+    nested: MyModel
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_nested_objects(use_async: bool) -> None:
+    model = ModelNestedObjects.construct(nested={"foo": "stainless"})
+    assert isinstance(model.nested, MyModel)
+    assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}}
+
+
+class ModelWithDefaultField(BaseModel):
+    foo: str
+    with_none_default: Union[str, None] = None
+    with_str_default: str = "foo"
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_default_field(use_async: bool) -> None:
+    # should be excluded when defaults are used
+    model = ModelWithDefaultField.construct()
+    assert model.with_none_default is None
+    assert model.with_str_default == "foo"
+    assert cast(Any, await transform(model, Any, use_async)) == {}
+
+    # should be included when the default value is explicitly given
+    model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo")
+    assert model.with_none_default is None
+    assert model.with_str_default == "foo"
+    assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"}
+
+    # should be included when a non-default value is explicitly given
+    model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz")
+    assert model.with_none_default == "bar"
+    assert model.with_str_default == "baz"
+    assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"}
+
+
+class TypedDictIterableUnion(TypedDict):
+    foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+class Bar8(TypedDict):
+    foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz8(TypedDict):
+    foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_of_dictionaries(use_async: bool) -> None:
+    assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == {
+        "FOO": [{"fooBaz": "bar"}]
+    }
+    assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == {
+        "FOO": [{"fooBaz": "bar"}]
+    }
+
+    def my_iter() -> Iterable[Baz8]:
+        yield {"foo_baz": "hello"}
+        yield {"foo_baz": "world"}
+
+    assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == {
+        "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}]
+    }
+
+
+class TypedDictIterableUnionStr(TypedDict):
+    foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_union_str(use_async: bool) -> None:
+    assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"}
+    assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [
+        {"fooBaz": "bar"}
+    ]
+
+
+class TypedDictBase64Input(TypedDict):
+    foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_base64_file_input(use_async: bool) -> None:
+    # strings are left as-is
+    assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"}
+
+    # pathlib.Path is automatically converted to base64
+    assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQo="
+    }  # type: ignore[comparison-overlap]
+
+    # io instances are automatically converted to base64
+    assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQ=="
+    }  # type: ignore[comparison-overlap]
+    assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
+        "foo": "SGVsbG8sIHdvcmxkIQ=="
+    }  # type: ignore[comparison-overlap]
diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py
new file mode 100644
index 00000000..1277e93e
--- /dev/null
+++ b/tests/test_utils/test_proxy.py
@@ -0,0 +1,23 @@
+import operator
+from typing import Any
+from typing_extensions import override
+
+from codex._utils import LazyProxy
+
+
+class RecursiveLazyProxy(LazyProxy[Any]):
+    @override
+    def __load__(self) -> Any:
+        return self
+
+    def __call__(self, *_args: Any, **_kwds: Any) -> Any:
+        raise RuntimeError("This should never be called!")
+
+
+def test_recursive_proxy() -> None:
+    proxy = RecursiveLazyProxy()
+    assert repr(proxy) == "RecursiveLazyProxy"
+    assert str(proxy) == "RecursiveLazyProxy"
+    assert dir(proxy) == []
+    assert type(proxy).__name__ == "RecursiveLazyProxy"
+    assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy"
diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py
new file mode 100644
index 00000000..be4ca5e2
--- /dev/null
+++ b/tests/test_utils/test_typing.py
@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+from typing import Generic, TypeVar, cast
+
+from codex._utils import extract_type_var_from_base
+
+_T = TypeVar("_T")
+_T2 = TypeVar("_T2")
+_T3 = TypeVar("_T3")
+
+
+class BaseGeneric(Generic[_T]): ...
+
+
+class SubclassGeneric(BaseGeneric[_T]): ...
+
+
+class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ...
+
+
+class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ...
+
+
+class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ...
+
+
+def test_extract_type_var() -> None:
+    assert (
+        extract_type_var_from_base(
+            BaseGeneric[int],
+            index=0,
+            generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+        )
+        == int
+    )
+
+
+def test_extract_type_var_generic_subclass() -> None:
+    assert (
+        extract_type_var_from_base(
+            SubclassGeneric[int],
+            index=0,
+            generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+        )
+        == int
+    )
+
+
+def test_extract_type_var_multiple() -> None:
+    typ = BaseGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_multiple() -> None:
+    typ = SubclassGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None:
+    typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None]
+
+    generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+    assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+    assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+    assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 00000000..d56fd573
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,159 @@
+from __future__ import annotations
+
+import os
+import inspect
+import traceback
+import contextlib
+from typing import Any, TypeVar, Iterator, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, get_origin, assert_type
+
+from codex._types import Omit, NoneType
+from codex._utils import (
+    is_dict,
+    is_list,
+    is_list_type,
+    is_union_type,
+    extract_type_arg,
+    is_annotated_type,
+    is_type_alias_type,
+)
+from codex._compat import PYDANTIC_V2, field_outer_type, get_model_fields
+from codex._models import BaseModel
+
+BaseModelT = TypeVar("BaseModelT", bound=BaseModel)
+
+
+def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool:
+    for name, field in get_model_fields(model).items():
+        field_value = getattr(value, name)
+        if PYDANTIC_V2:
+            allow_none = False
+        else:
+            # in v1 nullability was structured differently
+            # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields
+            allow_none = getattr(field, "allow_none", False)
+
+        assert_matches_type(
+            field_outer_type(field),
+            field_value,
+            path=[*path, name],
+            allow_none=allow_none,
+        )
+
+    return True
+
+
+# Note: the `path` argument is only used to improve error messages when `--showlocals` is used
+def assert_matches_type(
+    type_: Any,
+    value: object,
+    *,
+    path: list[str],
+    allow_none: bool = False,
+) -> None:
+    if is_type_alias_type(type_):
+        type_ = type_.__value__
+
+    # unwrap `Annotated[T, ...]` -> `T`
+    if is_annotated_type(type_):
+        type_ = extract_type_arg(type_, 0)
+
+    if allow_none and value is None:
+        return
+
+    if type_ is None or type_ is NoneType:
+        assert value is None
+        return
+
+    origin = get_origin(type_) or type_
+
+    if is_list_type(type_):
+        return _assert_list_type(type_, value)
+
+    if origin == str:
+        assert isinstance(value, str)
+    elif origin == int:
+        assert isinstance(value, int)
+    elif origin == bool:
+        assert isinstance(value, bool)
+    elif origin == float:
+        assert isinstance(value, float)
+    elif origin == bytes:
+        assert isinstance(value, bytes)
+    elif origin == datetime:
+        assert isinstance(value, datetime)
+    elif origin == date:
+        assert isinstance(value, date)
+    elif origin == object:
+        # nothing to do here, the expected type is unknown
+        pass
+    elif origin == Literal:
+        assert value in get_args(type_)
+    elif origin == dict:
+        assert is_dict(value)
+
+        args = get_args(type_)
+        key_type = args[0]
+        items_type = args[1]
+
+        for key, item in value.items():
+            assert_matches_type(key_type, key, path=[*path, "<dict key>"])
+            assert_matches_type(items_type, item, path=[*path, "<dict item>"])
+    elif is_union_type(type_):
+        variants = get_args(type_)
+
+        try:
+            none_index = variants.index(type(None))
+        except ValueError:
+            pass
+        else:
+            # special case Optional[T] for better error messages
+            if len(variants) == 2:
+                if value is None:
+                    # valid
+                    return
+
+                return assert_matches_type(type_=variants[not none_index], value=value, path=path)
+
+        for i, variant in enumerate(variants):
+            try:
+                assert_matches_type(variant, value, path=[*path, f"variant {i}"])
+                return
+            except AssertionError:
+                traceback.print_exc()
+                continue
+
+        raise AssertionError("Did not match any variants")
+    elif issubclass(origin, BaseModel):
+        assert isinstance(value, type_)
+        assert assert_matches_model(type_, cast(Any, value), path=path)
+    elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent":
+        assert value.__class__.__name__ == "HttpxBinaryResponseContent"
+    else:
+        assert None, f"Unhandled field type: {type_}"
+
+
+def _assert_list_type(type_: type[object], value: object) -> None:
+    assert is_list(value)
+
+    inner_type = get_args(type_)[0]
+    for entry in value:
+        assert_type(inner_type, entry)  # type: ignore
+
+
+@contextlib.contextmanager
+def update_env(**new_env: str | Omit) -> Iterator[None]:
+    old = os.environ.copy()
+
+    try:
+        for name, value in new_env.items():
+            if isinstance(value, Omit):
+                os.environ.pop(name, None)
+            else:
+                os.environ[name] = value
+
+        yield None
+    finally:
+        os.environ.clear()
+        os.environ.update(old)

From a02e655a22c5cea2b579165bf05e3065abb3d722 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Jan 2025 03:32:38 +0000
Subject: [PATCH 002/227] feat(api): update via SDK Studio

---
 src/codex/_client.py |  93 ++++++----
 tests/conftest.py    |  20 +--
 tests/test_client.py | 399 +++++--------------------------------------
 3 files changed, 106 insertions(+), 406 deletions(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index bda9e89c..d94587d7 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -13,6 +13,7 @@
 from ._types import (
     NOT_GIVEN,
     Omit,
+    Headers,
     Timeout,
     NotGiven,
     Transport,
@@ -26,7 +27,7 @@
 from ._version import __version__
 from .resources import health
 from ._streaming import Stream as Stream, AsyncStream as AsyncStream
-from ._exceptions import CodexError, APIStatusError
+from ._exceptions import APIStatusError
 from ._base_client import (
     DEFAULT_MAX_RETRIES,
     SyncAPIClient,
@@ -48,9 +49,9 @@ class Codex(SyncAPIClient):
     with_streaming_response: CodexWithStreamedResponse
 
     # client options
-    bearer_token: str
-    api_key: str
-    access_key: str
+    bearer_token: str | None
+    api_key: str | None
+    access_key: str | None
 
     def __init__(
         self,
@@ -86,26 +87,14 @@ def __init__(
         """
         if bearer_token is None:
             bearer_token = os.environ.get("BEARER_TOKEN")
-        if bearer_token is None:
-            raise CodexError(
-                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable"
-            )
         self.bearer_token = bearer_token
 
         if api_key is None:
             api_key = os.environ.get("AUTHENTICATED_API_KEY")
-        if api_key is None:
-            raise CodexError(
-                "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable"
-            )
         self.api_key = api_key
 
         if access_key is None:
             access_key = os.environ.get("PUBLIC_ACCESS_KEY")
-        if access_key is None:
-            raise CodexError(
-                "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable"
-            )
         self.access_key = access_key
 
         if base_url is None:
@@ -150,16 +139,22 @@ def auth_headers(self) -> dict[str, str]:
     @property
     def _http_bearer(self) -> dict[str, str]:
         bearer_token = self.bearer_token
+        if bearer_token is None:
+            return {}
         return {"Authorization": f"Bearer {bearer_token}"}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
         api_key = self.api_key
+        if api_key is None:
+            return {}
         return {"X-API-Key": api_key}
 
     @property
     def _public_access_key(self) -> dict[str, str]:
         access_key = self.access_key
+        if access_key is None:
+            return {}
         return {"X-Access-Key": access_key}
 
     @property
@@ -171,6 +166,27 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
+    @override
+    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.bearer_token and headers.get("Authorization"):
+            return
+        if isinstance(custom_headers.get("Authorization"), Omit):
+            return
+
+        if self.api_key and headers.get("X-API-Key"):
+            return
+        if isinstance(custom_headers.get("X-API-Key"), Omit):
+            return
+
+        if self.access_key and headers.get("X-Access-Key"):
+            return
+        if isinstance(custom_headers.get("X-Access-Key"), Omit):
+            return
+
+        raise TypeError(
+            '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+        )
+
     def copy(
         self,
         *,
@@ -269,9 +285,9 @@ class AsyncCodex(AsyncAPIClient):
     with_streaming_response: AsyncCodexWithStreamedResponse
 
     # client options
-    bearer_token: str
-    api_key: str
-    access_key: str
+    bearer_token: str | None
+    api_key: str | None
+    access_key: str | None
 
     def __init__(
         self,
@@ -307,26 +323,14 @@ def __init__(
         """
         if bearer_token is None:
             bearer_token = os.environ.get("BEARER_TOKEN")
-        if bearer_token is None:
-            raise CodexError(
-                "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable"
-            )
         self.bearer_token = bearer_token
 
         if api_key is None:
             api_key = os.environ.get("AUTHENTICATED_API_KEY")
-        if api_key is None:
-            raise CodexError(
-                "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable"
-            )
         self.api_key = api_key
 
         if access_key is None:
             access_key = os.environ.get("PUBLIC_ACCESS_KEY")
-        if access_key is None:
-            raise CodexError(
-                "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable"
-            )
         self.access_key = access_key
 
         if base_url is None:
@@ -371,16 +375,22 @@ def auth_headers(self) -> dict[str, str]:
     @property
     def _http_bearer(self) -> dict[str, str]:
         bearer_token = self.bearer_token
+        if bearer_token is None:
+            return {}
         return {"Authorization": f"Bearer {bearer_token}"}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
         api_key = self.api_key
+        if api_key is None:
+            return {}
         return {"X-API-Key": api_key}
 
     @property
     def _public_access_key(self) -> dict[str, str]:
         access_key = self.access_key
+        if access_key is None:
+            return {}
         return {"X-Access-Key": access_key}
 
     @property
@@ -392,6 +402,27 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
+    @override
+    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.bearer_token and headers.get("Authorization"):
+            return
+        if isinstance(custom_headers.get("Authorization"), Omit):
+            return
+
+        if self.api_key and headers.get("X-API-Key"):
+            return
+        if isinstance(custom_headers.get("X-API-Key"), Omit):
+            return
+
+        if self.access_key and headers.get("X-Access-Key"):
+            return
+        if isinstance(custom_headers.get("X-Access-Key"), Omit):
+            return
+
+        raise TypeError(
+            '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+        )
+
     def copy(
         self,
         *,
diff --git a/tests/conftest.py b/tests/conftest.py
index da139dcc..8823ef7a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -28,10 +28,6 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
-bearer_token = "My Bearer Token"
-api_key = "My API Key"
-access_key = "My Access Key"
-
 
 @pytest.fixture(scope="session")
 def client(request: FixtureRequest) -> Iterator[Codex]:
@@ -39,13 +35,7 @@ def client(request: FixtureRequest) -> Iterator[Codex]:
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    with Codex(
-        base_url=base_url,
-        bearer_token=bearer_token,
-        api_key=api_key,
-        access_key=access_key,
-        _strict_response_validation=strict,
-    ) as client:
+    with Codex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
 
 
@@ -55,11 +45,5 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    async with AsyncCodex(
-        base_url=base_url,
-        bearer_token=bearer_token,
-        api_key=api_key,
-        access_key=access_key,
-        _strict_response_validation=strict,
-    ) as client:
+    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index d36040f6..91933054 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -30,9 +30,6 @@
 from .utils import update_env
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-bearer_token = "My Bearer Token"
-api_key = "My API Key"
-access_key = "My Access Key"
 
 
 def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
@@ -54,13 +51,7 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int:
 
 
 class TestCodex:
-    client = Codex(
-        base_url=base_url,
-        bearer_token=bearer_token,
-        api_key=api_key,
-        access_key=access_key,
-        _strict_response_validation=True,
-    )
+    client = Codex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     def test_raw_response(self, respx_mock: MockRouter) -> None:
@@ -86,18 +77,6 @@ def test_copy(self) -> None:
         copied = self.client.copy()
         assert id(copied) != id(self.client)
 
-        copied = self.client.copy(bearer_token="another My Bearer Token")
-        assert copied.bearer_token == "another My Bearer Token"
-        assert self.client.bearer_token == "My Bearer Token"
-
-        copied = self.client.copy(api_key="another My API Key")
-        assert copied.api_key == "another My API Key"
-        assert self.client.api_key == "My API Key"
-
-        copied = self.client.copy(access_key="another My Access Key")
-        assert copied.access_key == "another My Access Key"
-        assert self.client.access_key == "My Access Key"
-
     def test_copy_default_options(self) -> None:
         # options that have a default are overridden correctly
         copied = self.client.copy(max_retries=7)
@@ -115,14 +94,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_headers={"X-Foo": "bar"},
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -154,14 +126,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_query={"foo": "bar"},
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -284,14 +249,7 @@ def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     def test_client_timeout_option(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            timeout=httpx.Timeout(0),
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -300,14 +258,7 @@ def test_client_timeout_option(self) -> None:
     def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         with httpx.Client(timeout=None) as http_client:
-            client = Codex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -315,14 +266,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         with httpx.Client() as http_client:
-            client = Codex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -330,14 +274,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = Codex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -346,33 +283,16 @@ def test_http_client_timeout_option(self) -> None:
     async def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             async with httpx.AsyncClient() as http_client:
-                Codex(
-                    base_url=base_url,
-                    bearer_token=bearer_token,
-                    api_key=api_key,
-                    access_key=access_key,
-                    _strict_response_validation=True,
-                    http_client=cast(Any, http_client),
-                )
+                Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_headers={"X-Foo": "bar"},
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
         client2 = Codex(
             base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
             _strict_response_validation=True,
             default_headers={
                 "X-Foo": "stainless",
@@ -384,14 +304,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_query={"query_param": "bar"},
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -590,13 +503,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = Codex(
-            base_url="https://example.com/from_init",
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -605,26 +512,15 @@ def test_base_url_setter(self) -> None:
 
     def test_base_url_env(self) -> None:
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = Codex(
-                bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True
-            )
+            client = Codex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
     @pytest.mark.parametrize(
         "client",
         [
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            Codex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -644,18 +540,9 @@ def test_base_url_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            Codex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -675,18 +562,9 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            Codex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -704,13 +582,7 @@ def test_absolute_request_url(self, client: Codex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     def test_copied_client_does_not_close_http(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -721,13 +593,7 @@ def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     def test_client_context_manager(self) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True)
         with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -748,14 +614,7 @@ class Model(BaseModel):
 
     def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            Codex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                max_retries=cast(Any, None),
-            )
+            Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -764,24 +623,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        strict_client = Codex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             strict_client.get("/foo", cast_to=Model)
 
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=False,
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=False)
 
         response = client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -809,13 +656,7 @@ class Model(BaseModel):
     )
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = Codex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = Codex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -941,13 +782,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
 
 
 class TestAsyncCodex:
-    client = AsyncCodex(
-        base_url=base_url,
-        bearer_token=bearer_token,
-        api_key=api_key,
-        access_key=access_key,
-        _strict_response_validation=True,
-    )
+    client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -975,18 +810,6 @@ def test_copy(self) -> None:
         copied = self.client.copy()
         assert id(copied) != id(self.client)
 
-        copied = self.client.copy(bearer_token="another My Bearer Token")
-        assert copied.bearer_token == "another My Bearer Token"
-        assert self.client.bearer_token == "My Bearer Token"
-
-        copied = self.client.copy(api_key="another My API Key")
-        assert copied.api_key == "another My API Key"
-        assert self.client.api_key == "My API Key"
-
-        copied = self.client.copy(access_key="another My Access Key")
-        assert copied.access_key == "another My Access Key"
-        assert self.client.access_key == "My Access Key"
-
     def test_copy_default_options(self) -> None:
         # options that have a default are overridden correctly
         copied = self.client.copy(max_retries=7)
@@ -1004,14 +827,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_headers={"X-Foo": "bar"},
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -1043,14 +859,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_query={"foo": "bar"},
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -1173,14 +982,7 @@ async def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     async def test_client_timeout_option(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            timeout=httpx.Timeout(0),
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1189,14 +991,7 @@ async def test_client_timeout_option(self) -> None:
     async def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         async with httpx.AsyncClient(timeout=None) as http_client:
-            client = AsyncCodex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1204,14 +999,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         async with httpx.AsyncClient() as http_client:
-            client = AsyncCodex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1219,14 +1007,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = AsyncCodex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                http_client=http_client,
-            )
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1235,33 +1016,16 @@ async def test_http_client_timeout_option(self) -> None:
     def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             with httpx.Client() as http_client:
-                AsyncCodex(
-                    base_url=base_url,
-                    bearer_token=bearer_token,
-                    api_key=api_key,
-                    access_key=access_key,
-                    _strict_response_validation=True,
-                    http_client=cast(Any, http_client),
-                )
+                AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_headers={"X-Foo": "bar"},
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
         client2 = AsyncCodex(
             base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
             _strict_response_validation=True,
             default_headers={
                 "X-Foo": "stainless",
@@ -1273,14 +1037,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-            default_query={"query_param": "bar"},
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -1479,13 +1236,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = AsyncCodex(
-            base_url="https://example.com/from_init",
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -1494,26 +1245,15 @@ def test_base_url_setter(self) -> None:
 
     def test_base_url_env(self) -> None:
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = AsyncCodex(
-                bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True
-            )
+            client = AsyncCodex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
     @pytest.mark.parametrize(
         "client",
         [
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            AsyncCodex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1533,18 +1273,9 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            AsyncCodex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1564,18 +1295,9 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-            ),
-            AsyncCodex(
-                base_url="http://localhost:5000/custom/path/",
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1593,13 +1315,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     async def test_copied_client_does_not_close_http(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -1611,13 +1327,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     async def test_client_context_manager(self) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
         async with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -1639,14 +1349,7 @@ class Model(BaseModel):
 
     async def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            AsyncCodex(
-                base_url=base_url,
-                bearer_token=bearer_token,
-                api_key=api_key,
-                access_key=access_key,
-                _strict_response_validation=True,
-                max_retries=cast(Any, None),
-            )
+            AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -1656,24 +1359,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             await strict_client.get("/foo", cast_to=Model)
 
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=False,
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=False)
 
         response = await client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -1702,13 +1393,7 @@ class Model(BaseModel):
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     @pytest.mark.asyncio
     async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = AsyncCodex(
-            base_url=base_url,
-            bearer_token=bearer_token,
-            api_key=api_key,
-            access_key=access_key,
-            _strict_response_validation=True,
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)

From 6e31719f0e871ce1f0dac6fad312b6ca3c18c5e7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Jan 2025 19:02:07 +0000
Subject: [PATCH 003/227] feat(api): update via SDK Studio

---
 README.md               | 10 ++++-
 src/codex/__init__.py   | 14 +++++-
 src/codex/_client.py    | 94 +++++++++++++++++++++++++++++++++++------
 src/codex/pagination.py | 78 ++++++++++++++++++++++++++++++++++
 tests/test_client.py    | 16 +++++++
 5 files changed, 196 insertions(+), 16 deletions(-)
 create mode 100644 src/codex/pagination.py

diff --git a/README.md b/README.md
index e4bf463c..301f9e26 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,10 @@ The full API of this library can be found in [api.md](api.md).
 ```python
 from codex import Codex
 
-client = Codex()
+client = Codex(
+    # or 'production' | 'local'; defaults to "production".
+    environment="staging",
+)
 
 project_return_schema = client.projects.create(
     config={},
@@ -52,7 +55,10 @@ Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call
 import asyncio
 from codex import AsyncCodex
 
-client = AsyncCodex()
+client = AsyncCodex(
+    # or 'production' | 'local'; defaults to "production".
+    environment="staging",
+)
 
 
 async def main() -> None:
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index bdcdb71a..d6dffe2c 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -3,7 +3,18 @@
 from . import types
 from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
 from ._utils import file_from_path
-from ._client import Codex, Client, Stream, Timeout, Transport, AsyncCodex, AsyncClient, AsyncStream, RequestOptions
+from ._client import (
+    ENVIRONMENTS,
+    Codex,
+    Client,
+    Stream,
+    Timeout,
+    Transport,
+    AsyncCodex,
+    AsyncClient,
+    AsyncStream,
+    RequestOptions,
+)
 from ._models import BaseModel
 from ._version import __title__, __version__
 from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
@@ -59,6 +70,7 @@
     "AsyncStream",
     "Codex",
     "AsyncCodex",
+    "ENVIRONMENTS",
     "file_from_path",
     "BaseModel",
     "DEFAULT_TIMEOUT",
diff --git a/src/codex/_client.py b/src/codex/_client.py
index d94587d7..e12ce243 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -3,8 +3,8 @@
 from __future__ import annotations
 
 import os
-from typing import Any, Union, Mapping
-from typing_extensions import Self, override
+from typing import Any, Dict, Union, Mapping, cast
+from typing_extensions import Self, Literal, override
 
 import httpx
 
@@ -37,7 +37,23 @@
 from .resources.projects import projects
 from .resources.organizations import organizations
 
-__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Codex", "AsyncCodex", "Client", "AsyncClient"]
+__all__ = [
+    "ENVIRONMENTS",
+    "Timeout",
+    "Transport",
+    "ProxiesTypes",
+    "RequestOptions",
+    "Codex",
+    "AsyncCodex",
+    "Client",
+    "AsyncClient",
+]
+
+ENVIRONMENTS: Dict[str, str] = {
+    "production": "https://api-alpha-o3gxj3oajfu.cleanlab.ai",
+    "staging": "https://api-alpha-staging-o3gxj3oajfu.cleanlab.ai",
+    "local": "http://localhost:8080",
+}
 
 
 class Codex(SyncAPIClient):
@@ -53,13 +69,16 @@ class Codex(SyncAPIClient):
     api_key: str | None
     access_key: str | None
 
+    _environment: Literal["production", "staging", "local"] | NotGiven
+
     def __init__(
         self,
         *,
         bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
-        base_url: str | httpx.URL | None = None,
+        environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
+        base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN,
         timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
         max_retries: int = DEFAULT_MAX_RETRIES,
         default_headers: Mapping[str, str] | None = None,
@@ -97,10 +116,31 @@ def __init__(
             access_key = os.environ.get("PUBLIC_ACCESS_KEY")
         self.access_key = access_key
 
-        if base_url is None:
-            base_url = os.environ.get("CODEX_BASE_URL")
-        if base_url is None:
-            base_url = f"https://localhost:8080/test-api"
+        self._environment = environment
+
+        base_url_env = os.environ.get("CODEX_BASE_URL")
+        if is_given(base_url) and base_url is not None:
+            # cast required because mypy doesn't understand the type narrowing
+            base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
+        elif is_given(environment):
+            if base_url_env and base_url is not None:
+                raise ValueError(
+                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                )
+
+            try:
+                base_url = ENVIRONMENTS[environment]
+            except KeyError as exc:
+                raise ValueError(f"Unknown environment: {environment}") from exc
+        elif base_url_env is not None:
+            base_url = base_url_env
+        else:
+            self._environment = environment = "production"
+
+            try:
+                base_url = ENVIRONMENTS[environment]
+            except KeyError as exc:
+                raise ValueError(f"Unknown environment: {environment}") from exc
 
         super().__init__(
             version=__version__,
@@ -193,6 +233,7 @@ def copy(
         bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
+        environment: Literal["production", "staging", "local"] | None = None,
         base_url: str | httpx.URL | None = None,
         timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
         http_client: httpx.Client | None = None,
@@ -230,6 +271,7 @@ def copy(
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,
+            environment=environment or self._environment,
             timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
             http_client=http_client,
             max_retries=max_retries if is_given(max_retries) else self.max_retries,
@@ -289,13 +331,16 @@ class AsyncCodex(AsyncAPIClient):
     api_key: str | None
     access_key: str | None
 
+    _environment: Literal["production", "staging", "local"] | NotGiven
+
     def __init__(
         self,
         *,
         bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
-        base_url: str | httpx.URL | None = None,
+        environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
+        base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN,
         timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
         max_retries: int = DEFAULT_MAX_RETRIES,
         default_headers: Mapping[str, str] | None = None,
@@ -333,10 +378,31 @@ def __init__(
             access_key = os.environ.get("PUBLIC_ACCESS_KEY")
         self.access_key = access_key
 
-        if base_url is None:
-            base_url = os.environ.get("CODEX_BASE_URL")
-        if base_url is None:
-            base_url = f"https://localhost:8080/test-api"
+        self._environment = environment
+
+        base_url_env = os.environ.get("CODEX_BASE_URL")
+        if is_given(base_url) and base_url is not None:
+            # cast required because mypy doesn't understand the type narrowing
+            base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
+        elif is_given(environment):
+            if base_url_env and base_url is not None:
+                raise ValueError(
+                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                )
+
+            try:
+                base_url = ENVIRONMENTS[environment]
+            except KeyError as exc:
+                raise ValueError(f"Unknown environment: {environment}") from exc
+        elif base_url_env is not None:
+            base_url = base_url_env
+        else:
+            self._environment = environment = "production"
+
+            try:
+                base_url = ENVIRONMENTS[environment]
+            except KeyError as exc:
+                raise ValueError(f"Unknown environment: {environment}") from exc
 
         super().__init__(
             version=__version__,
@@ -429,6 +495,7 @@ def copy(
         bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
+        environment: Literal["production", "staging", "local"] | None = None,
         base_url: str | httpx.URL | None = None,
         timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
         http_client: httpx.AsyncClient | None = None,
@@ -466,6 +533,7 @@ def copy(
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,
+            environment=environment or self._environment,
             timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
             http_client=http_client,
             max_retries=max_retries if is_given(max_retries) else self.max_retries,
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
new file mode 100644
index 00000000..963a4310
--- /dev/null
+++ b/src/codex/pagination.py
@@ -0,0 +1,78 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
+from typing_extensions import override
+
+from httpx import Response
+
+from ._utils import is_mapping
+from ._models import BaseModel
+from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
+
+__all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"]
+
+_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
+
+_T = TypeVar("_T")
+
+
+class SyncMyOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    items: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        items = self.items
+        if not items:
+            return []
+        return items
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        return PageInfo(params={"offset": current_count})
+
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+            },
+        )
+
+
+class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    items: List[_T]
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        items = self.items
+        if not items:
+            return []
+        return items
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        return PageInfo(params={"offset": current_count})
+
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+            },
+        )
diff --git a/tests/test_client.py b/tests/test_client.py
index 91933054..4e76d238 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -515,6 +515,14 @@ def test_base_url_env(self) -> None:
             client = Codex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
+        # explicit environment arg requires explicitness
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            with pytest.raises(ValueError, match=r"you must pass base_url=None"):
+                Codex(_strict_response_validation=True, environment="production")
+
+            client = Codex(base_url=None, _strict_response_validation=True, environment="production")
+            assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
+
     @pytest.mark.parametrize(
         "client",
         [
@@ -1248,6 +1256,14 @@ def test_base_url_env(self) -> None:
             client = AsyncCodex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
+        # explicit environment arg requires explicitness
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            with pytest.raises(ValueError, match=r"you must pass base_url=None"):
+                AsyncCodex(_strict_response_validation=True, environment="production")
+
+            client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
+            assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
+
     @pytest.mark.parametrize(
         "client",
         [

From e9508424ff814bda845dc3fd0aa38ca97f34e879 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Jan 2025 19:02:33 +0000
Subject: [PATCH 004/227] feat(api): update via SDK Studio

---
 LICENSE                                       |   2 +-
 README.md                                     |  48 ++---
 SECURITY.md                                   |   6 +-
 pyproject.toml                                |   4 +-
 src/codex/__init__.py                         |  12 +-
 src/codex/_client.py                          |  56 +++---
 src/codex/_exceptions.py                      |   4 +-
 src/codex/_resource.py                        |  10 +-
 src/codex/_response.py                        |   4 +-
 src/codex/_streaming.py                       |   6 +-
 src/codex/_utils/_logs.py                     |   2 +-
 .../organizations/test_billing.py             |  34 ++--
 .../projects/test_access_keys.py              |  82 ++++-----
 .../api_resources/projects/test_knowledge.py  | 110 ++++++------
 tests/api_resources/test_health.py            |  38 ++--
 tests/api_resources/test_organizations.py     |  18 +-
 tests/api_resources/test_projects.py          |  82 ++++-----
 .../users/myself/test_api_key.py              |  14 +-
 .../users/myself/test_organizations.py        |  14 +-
 tests/api_resources/users/test_myself.py      |  14 +-
 tests/conftest.py                             |  10 +-
 tests/test_client.py                          | 164 ++++++++++--------
 tests/test_response.py                        |  26 +--
 tests/test_streaming.py                       |  34 ++--
 24 files changed, 405 insertions(+), 389 deletions(-)

diff --git a/LICENSE b/LICENSE
index f00d3a6a..468d5b60 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2025 Codex
+   Copyright 2025 Cleanlab Codex
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 301f9e26..1e28f2a7 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Codex Python API library
+# Cleanlab Codex Python API library
 
 [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/)
 
-The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+
+The Cleanlab Codex Python library provides convenient access to the Cleanlab Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,
 and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
 
@@ -10,7 +10,7 @@ It is generated with [Stainless](https://www.stainlessapi.com/).
 
 ## Documentation
 
-The REST API documentation can be found on [docs.codex.com](https://docs.codex.com). The full API of this library can be found in [api.md](api.md).
+The REST API documentation can be found on [help.cleanlab.ai](https://help.cleanlab.ai). The full API of this library can be found in [api.md](api.md).
 
 ## Installation
 
@@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
 The full API of this library can be found in [api.md](api.md).
 
 ```python
-from codex import Codex
+from codex import CleanlabCodex
 
-client = Codex(
+client = CleanlabCodex(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control.
 
 ## Async usage
 
-Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
+Simply import `AsyncCleanlabCodex` instead of `CleanlabCodex` and use `await` with each API call:
 
 ```python
 import asyncio
-from codex import AsyncCodex
+from codex import AsyncCleanlabCodex
 
-client = AsyncCodex(
+client = AsyncCleanlabCodex(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`.
 
 ```python
 import codex
-from codex import Codex
+from codex import CleanlabCodex
 
-client = Codex()
+client = CleanlabCodex()
 
 try:
     client.projects.create(
@@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ
 You can use the `max_retries` option to configure or disable retry settings:
 
 ```python
-from codex import Codex
+from codex import CleanlabCodex
 
 # Configure the default for all requests:
-client = Codex(
+client = CleanlabCodex(
     # default is 2
     max_retries=0,
 )
@@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time
 which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
 
 ```python
-from codex import Codex
+from codex import CleanlabCodex
 
 # Configure the default for all requests:
-client = Codex(
+client = CleanlabCodex(
     # 20 seconds (default is 1 minute)
     timeout=20.0,
 )
 
 # More granular control:
-client = Codex(
+client = CleanlabCodex(
     timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
 )
 
@@ -191,10 +191,10 @@ Note that requests that time out are [retried twice by default](#retries).
 
 We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
 
-You can enable logging by setting the environment variable `CODEX_LOG` to `info`.
+You can enable logging by setting the environment variable `CLEANLAB_CODEX_LOG` to `info`.
 
 ```shell
-$ export CODEX_LOG=info
+$ export CLEANLAB_CODEX_LOG=info
 ```
 
 Or to `debug` for more verbose logging.
@@ -216,9 +216,9 @@ if response.my_field is None:
 The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
 
 ```py
-from codex import Codex
+from codex import CleanlabCodex
 
-client = Codex()
+client = CleanlabCodex()
 response = client.projects.with_raw_response.create(
     config={},
     name="name",
@@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c
 
 ```python
 import httpx
-from codex import Codex, DefaultHttpxClient
+from codex import CleanlabCodex, DefaultHttpxClient
 
-client = Codex(
-    # Or use the `CODEX_BASE_URL` env var
+client = CleanlabCodex(
+    # Or use the `CLEANLAB_CODEX_BASE_URL` env var
     base_url="http://my.test.server.example.com:8083",
     http_client=DefaultHttpxClient(
         proxy="http://my.test.proxy.example.com",
@@ -321,9 +321,9 @@ client.with_options(http_client=DefaultHttpxClient(...))
 By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
 
 ```py
-from codex import Codex
+from codex import CleanlabCodex
 
-with Codex() as client:
+with CleanlabCodex() as client:
   # make requests here
   ...
 
diff --git a/SECURITY.md b/SECURITY.md
index cf89cd01..c229abd4 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,11 +16,11 @@ before making any information public.
 ## Reporting Non-SDK Related Security Issues
 
 If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Codex please follow the respective company's security reporting guidelines.
+or products provided by Cleanlab Codex please follow the respective company's security reporting guidelines.
 
-### Codex Terms and Policies
+### Cleanlab Codex Terms and Policies
 
-Please contact dev-feedback@codex.com for any questions or concerns regarding security of our services.
+Please contact support@cleanlab.ai for any questions or concerns regarding security of our services.
 
 ---
 
diff --git a/pyproject.toml b/pyproject.toml
index 5116f227..86518e14 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,11 @@
 [project]
 name = "codex"
 version = "0.0.1-alpha.0"
-description = "The official Python library for the codex API"
+description = "The official Python library for the Cleanlab Codex API"
 dynamic = ["readme"]
 license = "Apache-2.0"
 authors = [
-{ name = "Codex", email = "dev-feedback@codex.com" },
+{ name = "Cleanlab Codex", email = "support@cleanlab.ai" },
 ]
 dependencies = [
     "httpx>=0.23.0, <1",
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index d6dffe2c..62559f31 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -5,15 +5,15 @@
 from ._utils import file_from_path
 from ._client import (
     ENVIRONMENTS,
-    Codex,
     Client,
     Stream,
     Timeout,
     Transport,
-    AsyncCodex,
     AsyncClient,
     AsyncStream,
+    CleanlabCodex,
     RequestOptions,
+    AsyncCleanlabCodex,
 )
 from ._models import BaseModel
 from ._version import __title__, __version__
@@ -21,7 +21,6 @@
 from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
 from ._exceptions import (
     APIError,
-    CodexError,
     ConflictError,
     NotFoundError,
     APIStatusError,
@@ -29,6 +28,7 @@
     APITimeoutError,
     BadRequestError,
     APIConnectionError,
+    CleanlabCodexError,
     AuthenticationError,
     InternalServerError,
     PermissionDeniedError,
@@ -48,7 +48,7 @@
     "NotGiven",
     "NOT_GIVEN",
     "Omit",
-    "CodexError",
+    "CleanlabCodexError",
     "APIError",
     "APIStatusError",
     "APITimeoutError",
@@ -68,8 +68,8 @@
     "AsyncClient",
     "Stream",
     "AsyncStream",
-    "Codex",
-    "AsyncCodex",
+    "CleanlabCodex",
+    "AsyncCleanlabCodex",
     "ENVIRONMENTS",
     "file_from_path",
     "BaseModel",
diff --git a/src/codex/_client.py b/src/codex/_client.py
index e12ce243..3a0f8657 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -43,8 +43,8 @@
     "Transport",
     "ProxiesTypes",
     "RequestOptions",
-    "Codex",
-    "AsyncCodex",
+    "CleanlabCodex",
+    "AsyncCleanlabCodex",
     "Client",
     "AsyncClient",
 ]
@@ -56,13 +56,13 @@
 }
 
 
-class Codex(SyncAPIClient):
+class CleanlabCodex(SyncAPIClient):
     health: health.HealthResource
     organizations: organizations.OrganizationsResource
     users: users.UsersResource
     projects: projects.ProjectsResource
-    with_raw_response: CodexWithRawResponse
-    with_streaming_response: CodexWithStreamedResponse
+    with_raw_response: CleanlabCodexWithRawResponse
+    with_streaming_response: CleanlabCodexWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -97,7 +97,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous codex client instance.
+        """Construct a new synchronous Cleanlab Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -118,14 +118,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CODEX_BASE_URL")
+        base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -157,8 +157,8 @@ def __init__(
         self.organizations = organizations.OrganizationsResource(self)
         self.users = users.UsersResource(self)
         self.projects = projects.ProjectsResource(self)
-        self.with_raw_response = CodexWithRawResponse(self)
-        self.with_streaming_response = CodexWithStreamedResponse(self)
+        self.with_raw_response = CleanlabCodexWithRawResponse(self)
+        self.with_streaming_response = CleanlabCodexWithStreamedResponse(self)
 
     @property
     @override
@@ -318,13 +318,13 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class AsyncCodex(AsyncAPIClient):
+class AsyncCleanlabCodex(AsyncAPIClient):
     health: health.AsyncHealthResource
     organizations: organizations.AsyncOrganizationsResource
     users: users.AsyncUsersResource
     projects: projects.AsyncProjectsResource
-    with_raw_response: AsyncCodexWithRawResponse
-    with_streaming_response: AsyncCodexWithStreamedResponse
+    with_raw_response: AsyncCleanlabCodexWithRawResponse
+    with_streaming_response: AsyncCleanlabCodexWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -359,7 +359,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async codex client instance.
+        """Construct a new async Cleanlab Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -380,14 +380,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CODEX_BASE_URL")
+        base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -419,8 +419,8 @@ def __init__(
         self.organizations = organizations.AsyncOrganizationsResource(self)
         self.users = users.AsyncUsersResource(self)
         self.projects = projects.AsyncProjectsResource(self)
-        self.with_raw_response = AsyncCodexWithRawResponse(self)
-        self.with_streaming_response = AsyncCodexWithStreamedResponse(self)
+        self.with_raw_response = AsyncCleanlabCodexWithRawResponse(self)
+        self.with_streaming_response = AsyncCleanlabCodexWithStreamedResponse(self)
 
     @property
     @override
@@ -580,38 +580,38 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class CodexWithRawResponse:
-    def __init__(self, client: Codex) -> None:
+class CleanlabCodexWithRawResponse:
+    def __init__(self, client: CleanlabCodex) -> None:
         self.health = health.HealthResourceWithRawResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.UsersResourceWithRawResponse(client.users)
         self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
 
 
-class AsyncCodexWithRawResponse:
-    def __init__(self, client: AsyncCodex) -> None:
+class AsyncCleanlabCodexWithRawResponse:
+    def __init__(self, client: AsyncCleanlabCodex) -> None:
         self.health = health.AsyncHealthResourceWithRawResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithRawResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
 
 
-class CodexWithStreamedResponse:
-    def __init__(self, client: Codex) -> None:
+class CleanlabCodexWithStreamedResponse:
+    def __init__(self, client: CleanlabCodex) -> None:
         self.health = health.HealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.UsersResourceWithStreamingResponse(client.users)
         self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
 
 
-class AsyncCodexWithStreamedResponse:
-    def __init__(self, client: AsyncCodex) -> None:
+class AsyncCleanlabCodexWithStreamedResponse:
+    def __init__(self, client: AsyncCleanlabCodex) -> None:
         self.health = health.AsyncHealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
 
 
-Client = Codex
+Client = CleanlabCodex
 
-AsyncClient = AsyncCodex
+AsyncClient = AsyncCleanlabCodex
diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py
index 90164ee4..c416e8d6 100644
--- a/src/codex/_exceptions.py
+++ b/src/codex/_exceptions.py
@@ -18,11 +18,11 @@
 ]
 
 
-class CodexError(Exception):
+class CleanlabCodexError(Exception):
     pass
 
 
-class APIError(CodexError):
+class APIError(CleanlabCodexError):
     message: str
     request: httpx.Request
 
diff --git a/src/codex/_resource.py b/src/codex/_resource.py
index 7ba43eeb..bfbd7eef 100644
--- a/src/codex/_resource.py
+++ b/src/codex/_resource.py
@@ -8,13 +8,13 @@
 import anyio
 
 if TYPE_CHECKING:
-    from ._client import Codex, AsyncCodex
+    from ._client import CleanlabCodex, AsyncCleanlabCodex
 
 
 class SyncAPIResource:
-    _client: Codex
+    _client: CleanlabCodex
 
-    def __init__(self, client: Codex) -> None:
+    def __init__(self, client: CleanlabCodex) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
@@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None:
 
 
 class AsyncAPIResource:
-    _client: AsyncCodex
+    _client: AsyncCleanlabCodex
 
-    def __init__(self, client: AsyncCodex) -> None:
+    def __init__(self, client: AsyncCleanlabCodex) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
diff --git a/src/codex/_response.py b/src/codex/_response.py
index b63926f4..3cbf40e5 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -29,7 +29,7 @@
 from ._models import BaseModel, is_basemodel
 from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
 from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
-from ._exceptions import CodexError, APIResponseValidationError
+from ._exceptions import CleanlabCodexError, APIResponseValidationError
 
 if TYPE_CHECKING:
     from ._models import FinalRequestOptions
@@ -554,7 +554,7 @@ def __init__(self) -> None:
         )
 
 
-class StreamAlreadyConsumed(CodexError):
+class StreamAlreadyConsumed(CleanlabCodexError):
     """
     Attempted to read or stream content, but the content has already
     been streamed.
diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py
index 3af102ce..820839f9 100644
--- a/src/codex/_streaming.py
+++ b/src/codex/_streaming.py
@@ -12,7 +12,7 @@
 from ._utils import extract_type_var_from_base
 
 if TYPE_CHECKING:
-    from ._client import Codex, AsyncCodex
+    from ._client import CleanlabCodex, AsyncCleanlabCodex
 
 
 _T = TypeVar("_T")
@@ -30,7 +30,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: Codex,
+        client: CleanlabCodex,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
@@ -93,7 +93,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: AsyncCodex,
+        client: AsyncCleanlabCodex,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py
index 80932c4f..23658f29 100644
--- a/src/codex/_utils/_logs.py
+++ b/src/codex/_utils/_logs.py
@@ -14,7 +14,7 @@ def _basic_config() -> None:
 
 
 def setup_logging() -> None:
-    env = os.environ.get("CODEX_LOG")
+    env = os.environ.get("CLEANLAB_CODEX_LOG")
     if env == "debug":
         _basic_config()
         logger.setLevel(logging.DEBUG)
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index ccb7dc8a..c2dbdedb 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema
 
@@ -18,14 +18,14 @@ class TestBilling:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_invoices(self, client: Codex) -> None:
+    def test_method_invoices(self, client: CleanlabCodex) -> None:
         billing = client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_invoices(self, client: Codex) -> None:
+    def test_raw_response_invoices(self, client: CleanlabCodex) -> None:
         response = client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: Codex) -> None:
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_invoices(self, client: Codex) -> None:
+    def test_streaming_response_invoices(self, client: CleanlabCodex) -> None:
         with client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_invoices(self, client: Codex) -> None:
+    def test_path_params_invoices(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    def test_method_usage(self, client: Codex) -> None:
+    def test_method_usage(self, client: CleanlabCodex) -> None:
         billing = client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_usage(self, client: Codex) -> None:
+    def test_raw_response_usage(self, client: CleanlabCodex) -> None:
         response = client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: Codex) -> None:
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_usage(self, client: Codex) -> None:
+    def test_streaming_response_usage(self, client: CleanlabCodex) -> None:
         with client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_usage(self, client: Codex) -> None:
+    def test_path_params_usage(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.usage(
                 "",
@@ -98,14 +98,14 @@ class TestAsyncBilling:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_invoices(self, async_client: AsyncCodex) -> None:
+    async def test_method_invoices(self, async_client: AsyncCleanlabCodex) -> None:
         billing = await async_client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_invoices(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    async def test_method_usage(self, async_client: AsyncCodex) -> None:
+    async def test_method_usage(self, async_client: AsyncCleanlabCodex) -> None:
         billing = await async_client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_usage(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_usage(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.usage(
                 "",
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 4fa1636f..6828a32a 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
 from codex.types.projects import (
@@ -22,7 +22,7 @@ class TestAccessKeys:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Codex) -> None:
+    def test_method_create(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Codex) -> None:
+    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Codex) -> None:
+    def test_raw_response_create(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -52,7 +52,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Codex) -> None:
+    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
+    def test_method_retrieve(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -74,7 +74,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
+    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
+    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: Codex) -> None:
+    def test_method_update(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -109,7 +109,7 @@ def test_method_update(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Codex) -> None:
+    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Codex) -> None:
+    def test_raw_response_update(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -133,7 +133,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Codex) -> None:
+    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: Codex) -> None:
+    def test_method_list(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
+    def test_raw_response_list(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -166,7 +166,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
+    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Codex) -> None:
+    def test_method_delete(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -187,7 +187,7 @@ def test_method_delete(self, client: Codex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Codex) -> None:
+    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Codex) -> None:
+    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_revoke(self, client: Codex) -> None:
+    def test_method_revoke(self, client: CleanlabCodex) -> None:
         access_key = client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -221,7 +221,7 @@ def test_method_revoke(self, client: Codex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_revoke(self, client: Codex) -> None:
+    def test_raw_response_revoke(self, client: CleanlabCodex) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: Codex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_revoke(self, client: Codex) -> None:
+    def test_streaming_response_revoke(self, client: CleanlabCodex) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -251,7 +251,7 @@ class TestAsyncAccessKeys:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_revoke(self, async_client: AsyncCodex) -> None:
+    async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_revoke(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
index 9052b62d..e74a0bcc 100644
--- a/tests/api_resources/projects/test_knowledge.py
+++ b/tests/api_resources/projects/test_knowledge.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex.types.projects import (
     Entry,
@@ -21,7 +21,7 @@ class TestKnowledge:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Codex) -> None:
+    def test_method_create(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -29,7 +29,7 @@ def test_method_create(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Codex) -> None:
+    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Codex) -> None:
+    def test_raw_response_create(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -50,7 +50,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Codex) -> None:
+    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
+    def test_method_retrieve(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -72,7 +72,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
+    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
+    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: Codex) -> None:
+    def test_path_params_retrieve(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
             )
 
     @parametrize
-    def test_method_update(self, client: Codex) -> None:
+    def test_method_update(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -114,7 +114,7 @@ def test_method_update(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Codex) -> None:
+    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Codex) -> None:
+    def test_raw_response_update(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -136,7 +136,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Codex) -> None:
+    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_update(self, client: Codex) -> None:
+    def test_path_params_update(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -158,14 +158,14 @@ def test_path_params_update(self, client: Codex) -> None:
             )
 
     @parametrize
-    def test_method_list(self, client: Codex) -> None:
+    def test_method_list(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_method_list_with_all_params(self, client: Codex) -> None:
+    def test_method_list_with_all_params(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
+    def test_raw_response_list(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -189,7 +189,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
+    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Codex) -> None:
+    def test_method_delete(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -210,7 +210,7 @@ def test_method_delete(self, client: Codex) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Codex) -> None:
+    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Codex) -> None:
+    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_delete(self, client: Codex) -> None:
+    def test_path_params_delete(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -244,7 +244,7 @@ def test_path_params_delete(self, client: Codex) -> None:
             )
 
     @parametrize
-    def test_method_add_question(self, client: Codex) -> None:
+    def test_method_add_question(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -252,7 +252,7 @@ def test_method_add_question(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_add_question(self, client: Codex) -> None:
+    def test_raw_response_add_question(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: Codex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_add_question(self, client: Codex) -> None:
+    def test_streaming_response_add_question(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_query(self, client: Codex) -> None:
+    def test_method_query(self, client: CleanlabCodex) -> None:
         knowledge = client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -286,7 +286,7 @@ def test_method_query(self, client: Codex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_query(self, client: Codex) -> None:
+    def test_raw_response_query(self, client: CleanlabCodex) -> None:
         response = client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -298,7 +298,7 @@ def test_raw_response_query(self, client: Codex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_query(self, client: Codex) -> None:
+    def test_streaming_response_query(self, client: CleanlabCodex) -> None:
         with client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
@@ -316,7 +316,7 @@ class TestAsyncKnowledge:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
             )
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
             )
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         assert knowledge is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         assert knowledge is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
             )
 
     @parametrize
-    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
+    async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_add_question(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) -
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_query(self, async_client: AsyncCodex) -> None:
+    async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None:
         knowledge = await async_client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_query(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index be4729ab..268c45e2 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from codex.types import HealthCheckResponse
 from tests.utils import assert_matches_type
 
@@ -18,12 +18,12 @@ class TestHealth:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_check(self, client: Codex) -> None:
+    def test_method_check(self, client: CleanlabCodex) -> None:
         health = client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_check(self, client: Codex) -> None:
+    def test_raw_response_check(self, client: CleanlabCodex) -> None:
         response = client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_check(self, client: Codex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_check(self, client: Codex) -> None:
+    def test_streaming_response_check(self, client: CleanlabCodex) -> None:
         with client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_db(self, client: Codex) -> None:
+    def test_method_db(self, client: CleanlabCodex) -> None:
         health = client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_db(self, client: Codex) -> None:
+    def test_raw_response_db(self, client: CleanlabCodex) -> None:
         response = client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -57,7 +57,7 @@ def test_raw_response_db(self, client: Codex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_db(self, client: Codex) -> None:
+    def test_streaming_response_db(self, client: CleanlabCodex) -> None:
         with client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_weaviate(self, client: Codex) -> None:
+    def test_method_weaviate(self, client: CleanlabCodex) -> None:
         health = client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_weaviate(self, client: Codex) -> None:
+    def test_raw_response_weaviate(self, client: CleanlabCodex) -> None:
         response = client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: Codex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_weaviate(self, client: Codex) -> None:
+    def test_streaming_response_weaviate(self, client: CleanlabCodex) -> None:
         with client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -97,12 +97,12 @@ class TestAsyncHealth:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_check(self, async_client: AsyncCodex) -> None:
+    async def test_method_check(self, async_client: AsyncCleanlabCodex) -> None:
         health = await async_client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_db(self, async_client: AsyncCodex) -> None:
+    async def test_method_db(self, async_client: AsyncCleanlabCodex) -> None:
         health = await async_client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_weaviate(self, async_client: AsyncCodex) -> None:
+    async def test_method_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
         health = await async_client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index 2d627c70..bfca62d9 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from codex.types import OrganizationSchemaPublic
 from tests.utils import assert_matches_type
 
@@ -18,14 +18,14 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
+    def test_method_retrieve(self, client: CleanlabCodex) -> None:
         organization = client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
+    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         response = client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
+    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         with client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: Codex) -> None:
+    def test_path_params_retrieve(self, client: CleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.with_raw_response.retrieve(
                 "",
@@ -60,14 +60,14 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         organization = await async_client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.with_raw_response.retrieve(
                 "",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 7e79d8e8..64dbec49 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from codex.types import (
     ProjectListResponse,
     ProjectReturnSchema,
@@ -21,7 +21,7 @@ class TestProjects:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Codex) -> None:
+    def test_method_create(self, client: CleanlabCodex) -> None:
         project = client.projects.create(
             config={},
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Codex) -> None:
+    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         project = client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Codex) -> None:
+    def test_raw_response_create(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -53,7 +53,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Codex) -> None:
+    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
+    def test_method_retrieve(self, client: CleanlabCodex) -> None:
         project = client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
+    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.retrieve(
             0,
         )
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
+    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: Codex) -> None:
+    def test_method_update(self, client: CleanlabCodex) -> None:
         project = client.projects.update(
             project_id=0,
             config={},
@@ -108,7 +108,7 @@ def test_method_update(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Codex) -> None:
+    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         project = client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Codex) -> None:
+    def test_raw_response_update(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -131,7 +131,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Codex) -> None:
+    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: Codex) -> None:
+    def test_method_list(self, client: CleanlabCodex) -> None:
         project = client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
+    def test_raw_response_list(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -164,7 +164,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
+    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Codex) -> None:
+    def test_method_delete(self, client: CleanlabCodex) -> None:
         project = client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Codex) -> None:
+    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.delete(
             0,
         )
@@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         assert project is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Codex) -> None:
+    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: Codex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_export(self, client: Codex) -> None:
+    def test_method_export(self, client: CleanlabCodex) -> None:
         project = client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_raw_response_export(self, client: Codex) -> None:
+    def test_raw_response_export(self, client: CleanlabCodex) -> None:
         response = client.projects.with_raw_response.export(
             0,
         )
@@ -226,7 +226,7 @@ def test_raw_response_export(self, client: Codex) -> None:
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_export(self, client: Codex) -> None:
+    def test_streaming_response_export(self, client: CleanlabCodex) -> None:
         with client.projects.with_streaming_response.export(
             0,
         ) as response:
@@ -243,7 +243,7 @@ class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.create(
             config={},
             name="name",
@@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
             0,
         )
@@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={},
@@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.delete(
             0,
         )
@@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         assert project is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_export(self, async_client: AsyncCodex) -> None:
+    async def test_method_export(self, async_client: AsyncCleanlabCodex) -> None:
         project = await async_client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.projects.with_raw_response.export(
             0,
         )
@@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_export(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.projects.with_streaming_response.export(
             0,
         ) as response:
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index 55bba35f..af5fb8b7 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchema
 
@@ -18,12 +18,12 @@ class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_refresh(self, client: Codex) -> None:
+    def test_method_refresh(self, client: CleanlabCodex) -> None:
         api_key = client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_raw_response_refresh(self, client: Codex) -> None:
+    def test_raw_response_refresh(self, client: CleanlabCodex) -> None:
         response = client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: Codex) -> None:
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_refresh(self, client: Codex) -> None:
+    def test_streaming_response_refresh(self, client: CleanlabCodex) -> None:
         with client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncAPIKey:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_refresh(self, async_client: AsyncCodex) -> None:
+    async def test_method_refresh(self, async_client: AsyncCleanlabCodex) -> None:
         api_key = await async_client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_refresh(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index da485882..15fd00f8 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex.types.users.myself import UserOrganizationsSchema
 
@@ -18,12 +18,12 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_list(self, client: Codex) -> None:
+    def test_method_list(self, client: CleanlabCodex) -> None:
         organization = client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
+    def test_raw_response_list(self, client: CleanlabCodex) -> None:
         response = client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
+    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         with client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
         organization = await async_client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index a206d1f9..de484616 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchemaPublic
 
@@ -18,12 +18,12 @@ class TestMyself:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
+    def test_method_retrieve(self, client: CleanlabCodex) -> None:
         myself = client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
+    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         response = client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
+    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         with client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncMyself:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         myself = await async_client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         response = await async_client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         async with async_client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/conftest.py b/tests/conftest.py
index 8823ef7a..7501cec9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,7 +7,7 @@
 import pytest
 from pytest_asyncio import is_async_test
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 
 if TYPE_CHECKING:
     from _pytest.fixtures import FixtureRequest
@@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
 
 
 @pytest.fixture(scope="session")
-def client(request: FixtureRequest) -> Iterator[Codex]:
+def client(request: FixtureRequest) -> Iterator[CleanlabCodex]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    with Codex(base_url=base_url, _strict_response_validation=strict) as client:
+    with CleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
 
 
 @pytest.fixture(scope="session")
-async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlabCodex]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client:
+    async with AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index 4e76d238..485d19e0 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -20,7 +20,7 @@
 from respx import MockRouter
 from pydantic import ValidationError
 
-from codex import Codex, AsyncCodex, APIResponseValidationError
+from codex import CleanlabCodex, AsyncCleanlabCodex, APIResponseValidationError
 from codex._types import Omit
 from codex._models import BaseModel, FinalRequestOptions
 from codex._constants import RAW_RESPONSE_HEADER
@@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
     return 0.1
 
 
-def _get_open_connections(client: Codex | AsyncCodex) -> int:
+def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int:
     transport = client._client._transport
     assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
 
@@ -50,8 +50,8 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int:
     return len(pool._requests)
 
 
-class TestCodex:
-    client = Codex(base_url=base_url, _strict_response_validation=True)
+class TestCleanlabCodex:
+    client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     def test_raw_response(self, respx_mock: MockRouter) -> None:
@@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -249,7 +249,7 @@ def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     def test_client_timeout_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None:
     def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         with httpx.Client(timeout=None) as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         with httpx.Client() as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None:
     async def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             async with httpx.AsyncClient() as http_client:
-                Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = Codex(
+        client2 = CleanlabCodex(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -304,7 +304,9 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
+        client = CleanlabCodex(
+            base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -416,7 +418,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, client: Codex) -> None:
+    def test_multipart_repeating_array(self, client: CleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -503,7 +505,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = CleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -511,23 +513,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = Codex(_strict_response_validation=True)
+        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = CleanlabCodex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                Codex(_strict_response_validation=True, environment="production")
+                CleanlabCodex(_strict_response_validation=True, environment="production")
 
-            client = Codex(base_url=None, _strict_response_validation=True, environment="production")
+            client = CleanlabCodex(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Codex(
+            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            CleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -535,7 +537,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: Codex) -> None:
+    def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -548,8 +550,8 @@ def test_base_url_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Codex(
+            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            CleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -557,7 +559,7 @@ def test_base_url_trailing_slash(self, client: Codex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: Codex) -> None:
+    def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -570,8 +572,8 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Codex(
+            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            CleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -579,7 +581,7 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: Codex) -> None:
+    def test_absolute_request_url(self, client: CleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -590,7 +592,7 @@ def test_absolute_request_url(self, client: Codex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     def test_copied_client_does_not_close_http(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -601,7 +603,7 @@ def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     def test_client_context_manager(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
         with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -622,7 +624,7 @@ class Model(BaseModel):
 
     def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            CleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -631,12 +633,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = Codex(base_url=base_url, _strict_response_validation=True)
+        strict_client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             strict_client.get("/foo", cast_to=Model)
 
-        client = Codex(base_url=base_url, _strict_response_validation=False)
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=False)
 
         response = client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -664,7 +666,7 @@ class Model(BaseModel):
     )
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -707,7 +709,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     def test_retries_taken(
         self,
-        client: Codex,
+        client: CleanlabCodex,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -737,7 +739,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    def test_omit_retry_count_header(self, client: Codex, failures_before_success: int, respx_mock: MockRouter) -> None:
+    def test_omit_retry_count_header(
+        self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter
+    ) -> None:
         client = client.with_options(max_retries=4)
 
         nb_retries = 0
@@ -764,7 +768,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
     def test_overwrite_retry_count_header(
-        self, client: Codex, failures_before_success: int, respx_mock: MockRouter
+        self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = client.with_options(max_retries=4)
 
@@ -789,8 +793,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
         assert response.http_request.headers.get("x-stainless-retry-count") == "42"
 
 
-class TestAsyncCodex:
-    client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+class TestAsyncCleanlabCodex:
+    client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -835,7 +839,9 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCleanlabCodex(
+            base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -867,7 +873,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -990,7 +996,7 @@ async def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     async def test_client_timeout_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -999,7 +1005,7 @@ async def test_client_timeout_option(self) -> None:
     async def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         async with httpx.AsyncClient(timeout=None) as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1007,7 +1013,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         async with httpx.AsyncClient() as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1015,7 +1021,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1024,15 +1030,19 @@ async def test_http_client_timeout_option(self) -> None:
     def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             with httpx.Client() as http_client:
-                AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                AsyncCleanlabCodex(
+                    base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)
+                )
 
     def test_default_headers_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCleanlabCodex(
+            base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = AsyncCodex(
+        client2 = AsyncCleanlabCodex(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -1045,7 +1055,9 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
+        client = AsyncCleanlabCodex(
+            base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -1157,7 +1169,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None:
+    def test_multipart_repeating_array(self, async_client: AsyncCleanlabCodex) -> None:
         request = async_client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -1244,7 +1256,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = AsyncCleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -1252,23 +1264,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = AsyncCodex(_strict_response_validation=True)
+        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = AsyncCleanlabCodex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                AsyncCodex(_strict_response_validation=True, environment="production")
+                AsyncCleanlabCodex(_strict_response_validation=True, environment="production")
 
-            client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
+            client = AsyncCleanlabCodex(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCodex(
+            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1276,7 +1288,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
+    def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1289,8 +1301,8 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCodex(
+            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1298,7 +1310,7 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
+    def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1311,8 +1323,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCodex(
+            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlabCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1320,7 +1332,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: AsyncCodex) -> None:
+    def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1331,7 +1343,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     async def test_copied_client_does_not_close_http(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -1343,7 +1355,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     async def test_client_context_manager(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
         async with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -1365,7 +1377,7 @@ class Model(BaseModel):
 
     async def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -1375,12 +1387,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        strict_client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             await strict_client.get("/foo", cast_to=Model)
 
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=False)
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=False)
 
         response = await client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -1409,7 +1421,7 @@ class Model(BaseModel):
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     @pytest.mark.asyncio
     async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -1453,7 +1465,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter)
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     async def test_retries_taken(
         self,
-        async_client: AsyncCodex,
+        async_client: AsyncCleanlabCodex,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -1485,7 +1497,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_omit_retry_count_header(
-        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
@@ -1514,7 +1526,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_overwrite_retry_count_header(
-        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
diff --git a/tests/test_response.py b/tests/test_response.py
index 224eb22e..b722c155 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -6,7 +6,7 @@
 import pytest
 import pydantic
 
-from codex import Codex, BaseModel, AsyncCodex
+from codex import BaseModel, CleanlabCodex, AsyncCleanlabCodex
 from codex._response import (
     APIResponse,
     BaseAPIResponse,
@@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None:
 class PydanticModel(pydantic.BaseModel): ...
 
 
-def test_response_parse_mismatched_basemodel(client: Codex) -> None:
+def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: Codex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None:
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlabCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCode
         await response.parse(to=PydanticModel)
 
 
-def test_response_parse_custom_stream(client: Codex) -> None:
+def test_response_parse_custom_stream(client: CleanlabCodex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: Codex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None:
+async def test_async_response_parse_custom_stream(async_client: AsyncCleanlabCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -125,7 +125,7 @@ class CustomModel(BaseModel):
     bar: int
 
 
-def test_response_parse_custom_model(client: Codex) -> None:
+def test_response_parse_custom_model(client: CleanlabCodex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: Codex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None:
+async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> No
     assert obj.bar == 2
 
 
-def test_response_parse_annotated_type(client: Codex) -> None:
+def test_response_parse_annotated_type(client: CleanlabCodex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: Codex) -> None:
     assert obj.bar == 2
 
 
-async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None:
+async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCodex) ->
         ("FalSe", False),
     ],
 )
-def test_response_parse_bool(client: Codex, content: str, expected: bool) -> None:
+def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -226,7 +226,7 @@ def test_response_parse_bool(client: Codex, content: str, expected: bool) -> Non
         ("FalSe", False),
     ],
 )
-async def test_async_response_parse_bool(client: AsyncCodex, content: str, expected: bool) -> None:
+async def test_async_response_parse_bool(client: AsyncCleanlabCodex, content: str, expected: bool) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -245,7 +245,7 @@ class OtherModel(BaseModel):
 
 
 @pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
-def test_response_parse_expect_model_union_non_json_content(client: Codex) -> None:
+def test_response_parse_expect_model_union_non_json_content(client: CleanlabCodex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=client,
@@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: Codex) -> No
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("async_client", [False], indirect=True)  # loose validation
-async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCodex) -> None:
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlabCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=async_client,
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index 443b5aa5..b6b669a6 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -5,13 +5,13 @@
 import httpx
 import pytest
 
-from codex import Codex, AsyncCodex
+from codex import CleanlabCodex, AsyncCleanlabCodex
 from codex._streaming import Stream, AsyncStream, ServerSentEvent
 
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_basic(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: completion\n"
         yield b'data: {"foo":true}\n'
@@ -28,7 +28,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_data_missing_event(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"foo":true}\n'
         yield b"\n"
@@ -44,7 +44,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_event_missing_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -60,7 +60,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_multiple_events(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -82,7 +82,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_multiple_events_with_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo":true}\n'
@@ -106,7 +106,9 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_multiple_data_lines_with_empty_line(
+    sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex
+) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -128,7 +130,9 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_data_json_escaped_double_new_line(
+    sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex
+) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo": "my long\\n\\ncontent"}'
@@ -145,7 +149,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
+async def test_multiple_data_lines(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -165,8 +169,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_special_new_line_character(
     sync: bool,
-    client: Codex,
-    async_client: AsyncCodex,
+    client: CleanlabCodex,
+    async_client: AsyncCleanlabCodex,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":" culpa"}\n'
@@ -196,8 +200,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_multi_byte_character_multiple_chunks(
     sync: bool,
-    client: Codex,
-    async_client: AsyncCodex,
+    client: CleanlabCodex,
+    async_client: AsyncCleanlabCodex,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":"'
@@ -237,8 +241,8 @@ def make_event_iterator(
     content: Iterator[bytes],
     *,
     sync: bool,
-    client: Codex,
-    async_client: AsyncCodex,
+    client: CleanlabCodex,
+    async_client: AsyncCleanlabCodex,
 ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
     if sync:
         return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()

From b012d46ac7d3053f7996d471e69e3a515df75fde Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Jan 2025 19:04:51 +0000
Subject: [PATCH 005/227] fix: update organization name

---
 LICENSE                                       |   2 +-
 README.md                                     |  46 ++---
 SECURITY.md                                   |   4 +-
 pyproject.toml                                |   4 +-
 src/codex/__init__.py                         |  12 +-
 src/codex/_client.py                          |  56 +++---
 src/codex/_exceptions.py                      |   4 +-
 src/codex/_resource.py                        |  10 +-
 src/codex/_response.py                        |   4 +-
 src/codex/_streaming.py                       |   6 +-
 src/codex/_utils/_logs.py                     |   2 +-
 .../organizations/test_billing.py             |  34 ++--
 .../projects/test_access_keys.py              |  82 ++++-----
 .../api_resources/projects/test_knowledge.py  | 110 ++++++------
 tests/api_resources/test_health.py            |  38 ++---
 tests/api_resources/test_organizations.py     |  18 +-
 tests/api_resources/test_projects.py          |  82 ++++-----
 .../users/myself/test_api_key.py              |  14 +-
 .../users/myself/test_organizations.py        |  14 +-
 tests/api_resources/users/test_myself.py      |  14 +-
 tests/conftest.py                             |  10 +-
 tests/test_client.py                          | 160 +++++++++---------
 tests/test_response.py                        |  26 +--
 tests/test_streaming.py                       |  34 ++--
 24 files changed, 387 insertions(+), 399 deletions(-)

diff --git a/LICENSE b/LICENSE
index 468d5b60..e575118f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2025 Cleanlab Codex
+   Copyright 2025 Cleanlab
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 1e28f2a7..b156eb36 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Cleanlab Codex Python API library
+# Cleanlab Python API library
 
 [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/)
 
-The Cleanlab Codex Python library provides convenient access to the Cleanlab Codex REST API from any Python 3.8+
+The Cleanlab Python library provides convenient access to the Cleanlab REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,
 and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
 
@@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
 The full API of this library can be found in [api.md](api.md).
 
 ```python
-from codex import CleanlabCodex
+from codex import Cleanlab
 
-client = CleanlabCodex(
+client = Cleanlab(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control.
 
 ## Async usage
 
-Simply import `AsyncCleanlabCodex` instead of `CleanlabCodex` and use `await` with each API call:
+Simply import `AsyncCleanlab` instead of `Cleanlab` and use `await` with each API call:
 
 ```python
 import asyncio
-from codex import AsyncCleanlabCodex
+from codex import AsyncCleanlab
 
-client = AsyncCleanlabCodex(
+client = AsyncCleanlab(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`.
 
 ```python
 import codex
-from codex import CleanlabCodex
+from codex import Cleanlab
 
-client = CleanlabCodex()
+client = Cleanlab()
 
 try:
     client.projects.create(
@@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ
 You can use the `max_retries` option to configure or disable retry settings:
 
 ```python
-from codex import CleanlabCodex
+from codex import Cleanlab
 
 # Configure the default for all requests:
-client = CleanlabCodex(
+client = Cleanlab(
     # default is 2
     max_retries=0,
 )
@@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time
 which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
 
 ```python
-from codex import CleanlabCodex
+from codex import Cleanlab
 
 # Configure the default for all requests:
-client = CleanlabCodex(
+client = Cleanlab(
     # 20 seconds (default is 1 minute)
     timeout=20.0,
 )
 
 # More granular control:
-client = CleanlabCodex(
+client = Cleanlab(
     timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
 )
 
@@ -191,10 +191,10 @@ Note that requests that time out are [retried twice by default](#retries).
 
 We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
 
-You can enable logging by setting the environment variable `CLEANLAB_CODEX_LOG` to `info`.
+You can enable logging by setting the environment variable `CLEANLAB_LOG` to `info`.
 
 ```shell
-$ export CLEANLAB_CODEX_LOG=info
+$ export CLEANLAB_LOG=info
 ```
 
 Or to `debug` for more verbose logging.
@@ -216,9 +216,9 @@ if response.my_field is None:
 The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
 
 ```py
-from codex import CleanlabCodex
+from codex import Cleanlab
 
-client = CleanlabCodex()
+client = Cleanlab()
 response = client.projects.with_raw_response.create(
     config={},
     name="name",
@@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c
 
 ```python
 import httpx
-from codex import CleanlabCodex, DefaultHttpxClient
+from codex import Cleanlab, DefaultHttpxClient
 
-client = CleanlabCodex(
-    # Or use the `CLEANLAB_CODEX_BASE_URL` env var
+client = Cleanlab(
+    # Or use the `CLEANLAB_BASE_URL` env var
     base_url="http://my.test.server.example.com:8083",
     http_client=DefaultHttpxClient(
         proxy="http://my.test.proxy.example.com",
@@ -321,9 +321,9 @@ client.with_options(http_client=DefaultHttpxClient(...))
 By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
 
 ```py
-from codex import CleanlabCodex
+from codex import Cleanlab
 
-with CleanlabCodex() as client:
+with Cleanlab() as client:
   # make requests here
   ...
 
diff --git a/SECURITY.md b/SECURITY.md
index c229abd4..2ca30344 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,9 +16,9 @@ before making any information public.
 ## Reporting Non-SDK Related Security Issues
 
 If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Cleanlab Codex please follow the respective company's security reporting guidelines.
+or products provided by Cleanlab please follow the respective company's security reporting guidelines.
 
-### Cleanlab Codex Terms and Policies
+### Cleanlab Terms and Policies
 
 Please contact support@cleanlab.ai for any questions or concerns regarding security of our services.
 
diff --git a/pyproject.toml b/pyproject.toml
index 86518e14..5ba1074c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,11 @@
 [project]
 name = "codex"
 version = "0.0.1-alpha.0"
-description = "The official Python library for the Cleanlab Codex API"
+description = "The official Python library for the Cleanlab API"
 dynamic = ["readme"]
 license = "Apache-2.0"
 authors = [
-{ name = "Cleanlab Codex", email = "support@cleanlab.ai" },
+{ name = "Cleanlab", email = "support@cleanlab.ai" },
 ]
 dependencies = [
     "httpx>=0.23.0, <1",
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index 62559f31..e3c0220d 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -8,12 +8,12 @@
     Client,
     Stream,
     Timeout,
+    Cleanlab,
     Transport,
     AsyncClient,
     AsyncStream,
-    CleanlabCodex,
+    AsyncCleanlab,
     RequestOptions,
-    AsyncCleanlabCodex,
 )
 from ._models import BaseModel
 from ._version import __title__, __version__
@@ -21,6 +21,7 @@
 from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
 from ._exceptions import (
     APIError,
+    CleanlabError,
     ConflictError,
     NotFoundError,
     APIStatusError,
@@ -28,7 +29,6 @@
     APITimeoutError,
     BadRequestError,
     APIConnectionError,
-    CleanlabCodexError,
     AuthenticationError,
     InternalServerError,
     PermissionDeniedError,
@@ -48,7 +48,7 @@
     "NotGiven",
     "NOT_GIVEN",
     "Omit",
-    "CleanlabCodexError",
+    "CleanlabError",
     "APIError",
     "APIStatusError",
     "APITimeoutError",
@@ -68,8 +68,8 @@
     "AsyncClient",
     "Stream",
     "AsyncStream",
-    "CleanlabCodex",
-    "AsyncCleanlabCodex",
+    "Cleanlab",
+    "AsyncCleanlab",
     "ENVIRONMENTS",
     "file_from_path",
     "BaseModel",
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 3a0f8657..0d3b6b8d 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -43,8 +43,8 @@
     "Transport",
     "ProxiesTypes",
     "RequestOptions",
-    "CleanlabCodex",
-    "AsyncCleanlabCodex",
+    "Cleanlab",
+    "AsyncCleanlab",
     "Client",
     "AsyncClient",
 ]
@@ -56,13 +56,13 @@
 }
 
 
-class CleanlabCodex(SyncAPIClient):
+class Cleanlab(SyncAPIClient):
     health: health.HealthResource
     organizations: organizations.OrganizationsResource
     users: users.UsersResource
     projects: projects.ProjectsResource
-    with_raw_response: CleanlabCodexWithRawResponse
-    with_streaming_response: CleanlabCodexWithStreamedResponse
+    with_raw_response: CleanlabWithRawResponse
+    with_streaming_response: CleanlabWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -97,7 +97,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous Cleanlab Codex client instance.
+        """Construct a new synchronous Cleanlab client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -118,14 +118,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL")
+        base_url_env = os.environ.get("CLEANLAB_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -157,8 +157,8 @@ def __init__(
         self.organizations = organizations.OrganizationsResource(self)
         self.users = users.UsersResource(self)
         self.projects = projects.ProjectsResource(self)
-        self.with_raw_response = CleanlabCodexWithRawResponse(self)
-        self.with_streaming_response = CleanlabCodexWithStreamedResponse(self)
+        self.with_raw_response = CleanlabWithRawResponse(self)
+        self.with_streaming_response = CleanlabWithStreamedResponse(self)
 
     @property
     @override
@@ -318,13 +318,13 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class AsyncCleanlabCodex(AsyncAPIClient):
+class AsyncCleanlab(AsyncAPIClient):
     health: health.AsyncHealthResource
     organizations: organizations.AsyncOrganizationsResource
     users: users.AsyncUsersResource
     projects: projects.AsyncProjectsResource
-    with_raw_response: AsyncCleanlabCodexWithRawResponse
-    with_streaming_response: AsyncCleanlabCodexWithStreamedResponse
+    with_raw_response: AsyncCleanlabWithRawResponse
+    with_streaming_response: AsyncCleanlabWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -359,7 +359,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Cleanlab Codex client instance.
+        """Construct a new async Cleanlab client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -380,14 +380,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL")
+        base_url_env = os.environ.get("CLEANLAB_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -419,8 +419,8 @@ def __init__(
         self.organizations = organizations.AsyncOrganizationsResource(self)
         self.users = users.AsyncUsersResource(self)
         self.projects = projects.AsyncProjectsResource(self)
-        self.with_raw_response = AsyncCleanlabCodexWithRawResponse(self)
-        self.with_streaming_response = AsyncCleanlabCodexWithStreamedResponse(self)
+        self.with_raw_response = AsyncCleanlabWithRawResponse(self)
+        self.with_streaming_response = AsyncCleanlabWithStreamedResponse(self)
 
     @property
     @override
@@ -580,38 +580,38 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class CleanlabCodexWithRawResponse:
-    def __init__(self, client: CleanlabCodex) -> None:
+class CleanlabWithRawResponse:
+    def __init__(self, client: Cleanlab) -> None:
         self.health = health.HealthResourceWithRawResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.UsersResourceWithRawResponse(client.users)
         self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
 
 
-class AsyncCleanlabCodexWithRawResponse:
-    def __init__(self, client: AsyncCleanlabCodex) -> None:
+class AsyncCleanlabWithRawResponse:
+    def __init__(self, client: AsyncCleanlab) -> None:
         self.health = health.AsyncHealthResourceWithRawResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithRawResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
 
 
-class CleanlabCodexWithStreamedResponse:
-    def __init__(self, client: CleanlabCodex) -> None:
+class CleanlabWithStreamedResponse:
+    def __init__(self, client: Cleanlab) -> None:
         self.health = health.HealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.UsersResourceWithStreamingResponse(client.users)
         self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
 
 
-class AsyncCleanlabCodexWithStreamedResponse:
-    def __init__(self, client: AsyncCleanlabCodex) -> None:
+class AsyncCleanlabWithStreamedResponse:
+    def __init__(self, client: AsyncCleanlab) -> None:
         self.health = health.AsyncHealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
 
 
-Client = CleanlabCodex
+Client = Cleanlab
 
-AsyncClient = AsyncCleanlabCodex
+AsyncClient = AsyncCleanlab
diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py
index c416e8d6..db0f4b62 100644
--- a/src/codex/_exceptions.py
+++ b/src/codex/_exceptions.py
@@ -18,11 +18,11 @@
 ]
 
 
-class CleanlabCodexError(Exception):
+class CleanlabError(Exception):
     pass
 
 
-class APIError(CleanlabCodexError):
+class APIError(CleanlabError):
     message: str
     request: httpx.Request
 
diff --git a/src/codex/_resource.py b/src/codex/_resource.py
index bfbd7eef..1bf30f6b 100644
--- a/src/codex/_resource.py
+++ b/src/codex/_resource.py
@@ -8,13 +8,13 @@
 import anyio
 
 if TYPE_CHECKING:
-    from ._client import CleanlabCodex, AsyncCleanlabCodex
+    from ._client import Cleanlab, AsyncCleanlab
 
 
 class SyncAPIResource:
-    _client: CleanlabCodex
+    _client: Cleanlab
 
-    def __init__(self, client: CleanlabCodex) -> None:
+    def __init__(self, client: Cleanlab) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
@@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None:
 
 
 class AsyncAPIResource:
-    _client: AsyncCleanlabCodex
+    _client: AsyncCleanlab
 
-    def __init__(self, client: AsyncCleanlabCodex) -> None:
+    def __init__(self, client: AsyncCleanlab) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
diff --git a/src/codex/_response.py b/src/codex/_response.py
index 3cbf40e5..4faa64ef 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -29,7 +29,7 @@
 from ._models import BaseModel, is_basemodel
 from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
 from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
-from ._exceptions import CleanlabCodexError, APIResponseValidationError
+from ._exceptions import CleanlabError, APIResponseValidationError
 
 if TYPE_CHECKING:
     from ._models import FinalRequestOptions
@@ -554,7 +554,7 @@ def __init__(self) -> None:
         )
 
 
-class StreamAlreadyConsumed(CleanlabCodexError):
+class StreamAlreadyConsumed(CleanlabError):
     """
     Attempted to read or stream content, but the content has already
     been streamed.
diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py
index 820839f9..e545fc0f 100644
--- a/src/codex/_streaming.py
+++ b/src/codex/_streaming.py
@@ -12,7 +12,7 @@
 from ._utils import extract_type_var_from_base
 
 if TYPE_CHECKING:
-    from ._client import CleanlabCodex, AsyncCleanlabCodex
+    from ._client import Cleanlab, AsyncCleanlab
 
 
 _T = TypeVar("_T")
@@ -30,7 +30,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: CleanlabCodex,
+        client: Cleanlab,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
@@ -93,7 +93,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: AsyncCleanlabCodex,
+        client: AsyncCleanlab,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py
index 23658f29..58944f5f 100644
--- a/src/codex/_utils/_logs.py
+++ b/src/codex/_utils/_logs.py
@@ -14,7 +14,7 @@ def _basic_config() -> None:
 
 
 def setup_logging() -> None:
-    env = os.environ.get("CLEANLAB_CODEX_LOG")
+    env = os.environ.get("CLEANLAB_LOG")
     if env == "debug":
         _basic_config()
         logger.setLevel(logging.DEBUG)
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index c2dbdedb..c1530b3b 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema
 
@@ -18,14 +18,14 @@ class TestBilling:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_invoices(self, client: CleanlabCodex) -> None:
+    def test_method_invoices(self, client: Cleanlab) -> None:
         billing = client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_invoices(self, client: CleanlabCodex) -> None:
+    def test_raw_response_invoices(self, client: Cleanlab) -> None:
         response = client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: CleanlabCodex) -> None:
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_invoices(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_invoices(self, client: Cleanlab) -> None:
         with client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_invoices(self, client: CleanlabCodex) -> None:
+    def test_path_params_invoices(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    def test_method_usage(self, client: CleanlabCodex) -> None:
+    def test_method_usage(self, client: Cleanlab) -> None:
         billing = client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_usage(self, client: CleanlabCodex) -> None:
+    def test_raw_response_usage(self, client: Cleanlab) -> None:
         response = client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: CleanlabCodex) -> None:
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_usage(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_usage(self, client: Cleanlab) -> None:
         with client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_usage(self, client: CleanlabCodex) -> None:
+    def test_path_params_usage(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.usage(
                 "",
@@ -98,14 +98,14 @@ class TestAsyncBilling:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_invoices(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_invoices(self, async_client: AsyncCleanlab) -> None:
         billing = await async_client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) -> None:
         async with async_client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCode
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_invoices(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_invoices(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    async def test_method_usage(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_usage(self, async_client: AsyncCleanlab) -> None:
         billing = await async_client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> Non
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> None:
         async with async_client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_usage(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_usage(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.usage(
                 "",
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 6828a32a..26f984b0 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
 from codex.types.projects import (
@@ -22,7 +22,7 @@ class TestAccessKeys:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: CleanlabCodex) -> None:
+    def test_method_create(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: CleanlabCodex) -> None:
+    def test_raw_response_create(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -52,7 +52,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_create(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: CleanlabCodex) -> None:
+    def test_method_retrieve(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -74,7 +74,7 @@ def test_method_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: CleanlabCodex) -> None:
+    def test_method_update(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -109,7 +109,7 @@ def test_method_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: CleanlabCodex) -> None:
+    def test_raw_response_update(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -133,7 +133,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_update(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: CleanlabCodex) -> None:
+    def test_method_list(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: CleanlabCodex) -> None:
+    def test_raw_response_list(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -166,7 +166,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None:
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_list(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: CleanlabCodex) -> None:
+    def test_method_delete(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -187,7 +187,7 @@ def test_method_delete(self, client: CleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
+    def test_raw_response_delete(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_delete(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_revoke(self, client: CleanlabCodex) -> None:
+    def test_method_revoke(self, client: Cleanlab) -> None:
         access_key = client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -221,7 +221,7 @@ def test_method_revoke(self, client: CleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_revoke(self, client: CleanlabCodex) -> None:
+    def test_raw_response_revoke(self, client: Cleanlab) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: CleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_revoke(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_revoke(self, client: Cleanlab) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -251,7 +251,7 @@ class TestAsyncAccessKeys:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_revoke(self, async_client: AsyncCleanlab) -> None:
         access_key = await async_client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> No
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_revoke(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_revoke(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
index e74a0bcc..9755583f 100644
--- a/tests/api_resources/projects/test_knowledge.py
+++ b/tests/api_resources/projects/test_knowledge.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex.types.projects import (
     Entry,
@@ -21,7 +21,7 @@ class TestKnowledge:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: CleanlabCodex) -> None:
+    def test_method_create(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -29,7 +29,7 @@ def test_method_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: CleanlabCodex) -> None:
+    def test_raw_response_create(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -50,7 +50,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_create(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: CleanlabCodex) -> None:
+    def test_method_retrieve(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -72,7 +72,7 @@ def test_method_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: CleanlabCodex) -> None:
+    def test_path_params_retrieve(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: CleanlabCodex) -> None:
             )
 
     @parametrize
-    def test_method_update(self, client: CleanlabCodex) -> None:
+    def test_method_update(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -114,7 +114,7 @@ def test_method_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: CleanlabCodex) -> None:
+    def test_raw_response_update(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -136,7 +136,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_update(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_update(self, client: CleanlabCodex) -> None:
+    def test_path_params_update(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -158,14 +158,14 @@ def test_path_params_update(self, client: CleanlabCodex) -> None:
             )
 
     @parametrize
-    def test_method_list(self, client: CleanlabCodex) -> None:
+    def test_method_list(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_method_list_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_list_with_all_params(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: CleanlabCodex) -> None:
+    def test_raw_response_list(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -189,7 +189,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_list(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: CleanlabCodex) -> None:
+    def test_method_delete(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -210,7 +210,7 @@ def test_method_delete(self, client: CleanlabCodex) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
+    def test_raw_response_delete(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_delete(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_delete(self, client: CleanlabCodex) -> None:
+    def test_path_params_delete(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -244,7 +244,7 @@ def test_path_params_delete(self, client: CleanlabCodex) -> None:
             )
 
     @parametrize
-    def test_method_add_question(self, client: CleanlabCodex) -> None:
+    def test_method_add_question(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -252,7 +252,7 @@ def test_method_add_question(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_add_question(self, client: CleanlabCodex) -> None:
+    def test_raw_response_add_question(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: CleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_add_question(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_add_question(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_query(self, client: CleanlabCodex) -> None:
+    def test_method_query(self, client: Cleanlab) -> None:
         knowledge = client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -286,7 +286,7 @@ def test_method_query(self, client: CleanlabCodex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_query(self, client: CleanlabCodex) -> None:
+    def test_raw_response_query(self, client: Cleanlab) -> None:
         response = client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -298,7 +298,7 @@ def test_raw_response_query(self, client: CleanlabCodex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_query(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_query(self, client: Cleanlab) -> None:
         with client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
@@ -316,7 +316,7 @@ class TestAsyncKnowledge:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> N
             )
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_update(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> Non
             )
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCode
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
         assert knowledge is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No
         assert knowledge is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> Non
             )
 
     @parametrize
-    async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_add_question(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex)
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_add_question(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_add_question(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCleanlab
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_query(self, async_client: AsyncCleanlab) -> None:
         knowledge = await async_client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> Non
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_query(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_query(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index 268c45e2..98e6c91e 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from codex.types import HealthCheckResponse
 from tests.utils import assert_matches_type
 
@@ -18,12 +18,12 @@ class TestHealth:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_check(self, client: CleanlabCodex) -> None:
+    def test_method_check(self, client: Cleanlab) -> None:
         health = client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_check(self, client: CleanlabCodex) -> None:
+    def test_raw_response_check(self, client: Cleanlab) -> None:
         response = client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_check(self, client: CleanlabCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_check(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_check(self, client: Cleanlab) -> None:
         with client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_db(self, client: CleanlabCodex) -> None:
+    def test_method_db(self, client: Cleanlab) -> None:
         health = client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_db(self, client: CleanlabCodex) -> None:
+    def test_raw_response_db(self, client: Cleanlab) -> None:
         response = client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -57,7 +57,7 @@ def test_raw_response_db(self, client: CleanlabCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_db(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_db(self, client: Cleanlab) -> None:
         with client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_weaviate(self, client: CleanlabCodex) -> None:
+    def test_method_weaviate(self, client: Cleanlab) -> None:
         health = client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_weaviate(self, client: CleanlabCodex) -> None:
+    def test_raw_response_weaviate(self, client: Cleanlab) -> None:
         response = client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: CleanlabCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_weaviate(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_weaviate(self, client: Cleanlab) -> None:
         with client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -97,12 +97,12 @@ class TestAsyncHealth:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_check(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_check(self, async_client: AsyncCleanlab) -> None:
         health = await async_client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> Non
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> None:
         async with async_client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_db(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_db(self, async_client: AsyncCleanlab) -> None:
         health = await async_client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None:
         async with async_client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_weaviate(self, async_client: AsyncCleanlab) -> None:
         health = await async_client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_weaviate(self, async_client: AsyncCleanlab) -> None:
         async with async_client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index bfca62d9..fa17b24b 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from codex.types import OrganizationSchemaPublic
 from tests.utils import assert_matches_type
 
@@ -18,14 +18,14 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: CleanlabCodex) -> None:
+    def test_method_retrieve(self, client: Cleanlab) -> None:
         organization = client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         response = client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         with client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: CleanlabCodex) -> None:
+    def test_path_params_retrieve(self, client: Cleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.with_raw_response.retrieve(
                 "",
@@ -60,14 +60,14 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         organization = await async_client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         async with async_client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.with_raw_response.retrieve(
                 "",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 64dbec49..c516e727 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from codex.types import (
     ProjectListResponse,
     ProjectReturnSchema,
@@ -21,7 +21,7 @@ class TestProjects:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: CleanlabCodex) -> None:
+    def test_method_create(self, client: Cleanlab) -> None:
         project = client.projects.create(
             config={},
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         project = client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: CleanlabCodex) -> None:
+    def test_raw_response_create(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -53,7 +53,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_create(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: CleanlabCodex) -> None:
+    def test_method_retrieve(self, client: Cleanlab) -> None:
         project = client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.retrieve(
             0,
         )
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: CleanlabCodex) -> None:
+    def test_method_update(self, client: Cleanlab) -> None:
         project = client.projects.update(
             project_id=0,
             config={},
@@ -108,7 +108,7 @@ def test_method_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
+    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         project = client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: CleanlabCodex) -> None:
+    def test_raw_response_update(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -131,7 +131,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_update(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: CleanlabCodex) -> None:
+    def test_method_list(self, client: Cleanlab) -> None:
         project = client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: CleanlabCodex) -> None:
+    def test_raw_response_list(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -164,7 +164,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None:
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_list(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: CleanlabCodex) -> None:
+    def test_method_delete(self, client: Cleanlab) -> None:
         project = client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    def test_raw_response_delete(self, client: CleanlabCodex) -> None:
+    def test_raw_response_delete(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.delete(
             0,
         )
@@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None:
         assert project is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_delete(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_export(self, client: CleanlabCodex) -> None:
+    def test_method_export(self, client: Cleanlab) -> None:
         project = client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_raw_response_export(self, client: CleanlabCodex) -> None:
+    def test_raw_response_export(self, client: Cleanlab) -> None:
         response = client.projects.with_raw_response.export(
             0,
         )
@@ -226,7 +226,7 @@ def test_raw_response_export(self, client: CleanlabCodex) -> None:
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_export(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_export(self, client: Cleanlab) -> None:
         with client.projects.with_streaming_response.export(
             0,
         ) as response:
@@ -243,7 +243,7 @@ class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.create(
             config={},
             name="name",
@@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
             0,
         )
@@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={},
@@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.delete(
             0,
         )
@@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No
         assert project is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex)
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_export(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_export(self, async_client: AsyncCleanlab) -> None:
         project = await async_client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.projects.with_raw_response.export(
             0,
         )
@@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> No
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_export(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_export(self, async_client: AsyncCleanlab) -> None:
         async with async_client.projects.with_streaming_response.export(
             0,
         ) as response:
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index af5fb8b7..36a68194 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchema
 
@@ -18,12 +18,12 @@ class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_refresh(self, client: CleanlabCodex) -> None:
+    def test_method_refresh(self, client: Cleanlab) -> None:
         api_key = client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_raw_response_refresh(self, client: CleanlabCodex) -> None:
+    def test_raw_response_refresh(self, client: Cleanlab) -> None:
         response = client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: CleanlabCodex) -> None:
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_refresh(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_refresh(self, client: Cleanlab) -> None:
         with client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncAPIKey:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_refresh(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_refresh(self, async_client: AsyncCleanlab) -> None:
         api_key = await async_client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> N
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_refresh(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_refresh(self, async_client: AsyncCleanlab) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index 15fd00f8..d50c4742 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex.types.users.myself import UserOrganizationsSchema
 
@@ -18,12 +18,12 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_list(self, client: CleanlabCodex) -> None:
+    def test_method_list(self, client: Cleanlab) -> None:
         organization = client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: CleanlabCodex) -> None:
+    def test_raw_response_list(self, client: Cleanlab) -> None:
         response = client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None:
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_list(self, client: Cleanlab) -> None:
         with client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
         organization = await async_client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
         async with async_client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index de484616..f90be5c0 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchemaPublic
 
@@ -18,12 +18,12 @@ class TestMyself:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: CleanlabCodex) -> None:
+    def test_method_retrieve(self, client: Cleanlab) -> None:
         myself = client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         response = client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None:
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None:
+    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         with client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncMyself:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         myself = await async_client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         response = await async_client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) ->
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         async with async_client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/conftest.py b/tests/conftest.py
index 7501cec9..40cbc004 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,7 +7,7 @@
 import pytest
 from pytest_asyncio import is_async_test
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 
 if TYPE_CHECKING:
     from _pytest.fixtures import FixtureRequest
@@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
 
 
 @pytest.fixture(scope="session")
-def client(request: FixtureRequest) -> Iterator[CleanlabCodex]:
+def client(request: FixtureRequest) -> Iterator[Cleanlab]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    with CleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client:
+    with Cleanlab(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
 
 
 @pytest.fixture(scope="session")
-async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlabCodex]:
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlab]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    async with AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client:
+    async with AsyncCleanlab(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index 485d19e0..b9a2b4b9 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -20,7 +20,7 @@
 from respx import MockRouter
 from pydantic import ValidationError
 
-from codex import CleanlabCodex, AsyncCleanlabCodex, APIResponseValidationError
+from codex import Cleanlab, AsyncCleanlab, APIResponseValidationError
 from codex._types import Omit
 from codex._models import BaseModel, FinalRequestOptions
 from codex._constants import RAW_RESPONSE_HEADER
@@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
     return 0.1
 
 
-def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int:
+def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int:
     transport = client._client._transport
     assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
 
@@ -50,8 +50,8 @@ def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int:
     return len(pool._requests)
 
 
-class TestCleanlabCodex:
-    client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
+class TestCleanlab:
+    client = Cleanlab(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     def test_raw_response(self, respx_mock: MockRouter) -> None:
@@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -249,7 +249,7 @@ def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     def test_client_timeout_option(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None:
     def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         with httpx.Client(timeout=None) as http_client:
-            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         with httpx.Client() as http_client:
-            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None:
     async def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             async with httpx.AsyncClient() as http_client:
-                CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = CleanlabCodex(
+        client2 = Cleanlab(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -304,9 +304,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = CleanlabCodex(
-            base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}
-        )
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -418,7 +416,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, client: CleanlabCodex) -> None:
+    def test_multipart_repeating_array(self, client: Cleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -505,7 +503,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = CleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = Cleanlab(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -513,23 +511,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = CleanlabCodex(_strict_response_validation=True)
+        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
+            client = Cleanlab(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                CleanlabCodex(_strict_response_validation=True, environment="production")
+                Cleanlab(_strict_response_validation=True, environment="production")
 
-            client = CleanlabCodex(base_url=None, _strict_response_validation=True, environment="production")
+            client = Cleanlab(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            CleanlabCodex(
+            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Cleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -537,7 +535,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None:
+    def test_base_url_trailing_slash(self, client: Cleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -550,8 +548,8 @@ def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            CleanlabCodex(
+            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Cleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -559,7 +557,7 @@ def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None:
+    def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -572,8 +570,8 @@ def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            CleanlabCodex(
+            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Cleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -581,7 +579,7 @@ def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: CleanlabCodex) -> None:
+    def test_absolute_request_url(self, client: Cleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -592,7 +590,7 @@ def test_absolute_request_url(self, client: CleanlabCodex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     def test_copied_client_does_not_close_http(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -603,7 +601,7 @@ def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     def test_client_context_manager(self) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
         with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -624,7 +622,7 @@ class Model(BaseModel):
 
     def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            CleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            Cleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -633,12 +631,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        strict_client = Cleanlab(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             strict_client.get("/foo", cast_to=Model)
 
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=False)
+        client = Cleanlab(base_url=base_url, _strict_response_validation=False)
 
         response = client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -666,7 +664,7 @@ class Model(BaseModel):
     )
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = CleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -709,7 +707,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     def test_retries_taken(
         self,
-        client: CleanlabCodex,
+        client: Cleanlab,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -740,7 +738,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
     def test_omit_retry_count_header(
-        self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter
+        self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = client.with_options(max_retries=4)
 
@@ -768,7 +766,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
     def test_overwrite_retry_count_header(
-        self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter
+        self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = client.with_options(max_retries=4)
 
@@ -793,8 +791,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
         assert response.http_request.headers.get("x-stainless-retry-count") == "42"
 
 
-class TestAsyncCleanlabCodex:
-    client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
+class TestAsyncCleanlab:
+    client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -839,9 +837,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = AsyncCleanlabCodex(
-            base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
-        )
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -873,7 +869,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -996,7 +992,7 @@ async def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     async def test_client_timeout_option(self) -> None:
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1005,7 +1001,7 @@ async def test_client_timeout_option(self) -> None:
     async def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         async with httpx.AsyncClient(timeout=None) as http_client:
-            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1013,7 +1009,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         async with httpx.AsyncClient() as http_client:
-            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1021,7 +1017,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1030,19 +1026,15 @@ async def test_http_client_timeout_option(self) -> None:
     def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             with httpx.Client() as http_client:
-                AsyncCleanlabCodex(
-                    base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)
-                )
+                AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = AsyncCleanlabCodex(
-            base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
-        )
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = AsyncCleanlabCodex(
+        client2 = AsyncCleanlab(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -1055,7 +1047,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = AsyncCleanlabCodex(
+        client = AsyncCleanlab(
             base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}
         )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1169,7 +1161,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, async_client: AsyncCleanlabCodex) -> None:
+    def test_multipart_repeating_array(self, async_client: AsyncCleanlab) -> None:
         request = async_client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -1256,7 +1248,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = AsyncCleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = AsyncCleanlab(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -1264,23 +1256,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = AsyncCleanlabCodex(_strict_response_validation=True)
+        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
+            client = AsyncCleanlab(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                AsyncCleanlabCodex(_strict_response_validation=True, environment="production")
+                AsyncCleanlab(_strict_response_validation=True, environment="production")
 
-            client = AsyncCleanlabCodex(base_url=None, _strict_response_validation=True, environment="production")
+            client = AsyncCleanlab(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlabCodex(
+            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1288,7 +1280,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
+    def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1301,8 +1293,8 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlabCodex(
+            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1310,7 +1302,7 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
+    def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1323,8 +1315,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlabCodex(
+            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCleanlab(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1332,7 +1324,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None:
+    def test_absolute_request_url(self, client: AsyncCleanlab) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1343,7 +1335,7 @@ def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     async def test_copied_client_does_not_close_http(self) -> None:
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -1355,7 +1347,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     async def test_client_context_manager(self) -> None:
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
         async with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -1377,7 +1369,7 @@ class Model(BaseModel):
 
     async def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            AsyncCleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -1387,12 +1379,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        strict_client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             await strict_client.get("/foo", cast_to=Model)
 
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=False)
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=False)
 
         response = await client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -1421,7 +1413,7 @@ class Model(BaseModel):
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     @pytest.mark.asyncio
     async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -1465,7 +1457,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter)
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     async def test_retries_taken(
         self,
-        async_client: AsyncCleanlabCodex,
+        async_client: AsyncCleanlab,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -1497,7 +1489,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_omit_retry_count_header(
-        self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
@@ -1526,7 +1518,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_overwrite_retry_count_header(
-        self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
diff --git a/tests/test_response.py b/tests/test_response.py
index b722c155..55b100ce 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -6,7 +6,7 @@
 import pytest
 import pydantic
 
-from codex import BaseModel, CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, BaseModel, AsyncCleanlab
 from codex._response import (
     APIResponse,
     BaseAPIResponse,
@@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None:
 class PydanticModel(pydantic.BaseModel): ...
 
 
-def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None:
+def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlabCodex) -> None:
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlab) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncClea
         await response.parse(to=PydanticModel)
 
 
-def test_response_parse_custom_stream(client: CleanlabCodex) -> None:
+def test_response_parse_custom_stream(client: Cleanlab) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: CleanlabCodex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_stream(async_client: AsyncCleanlabCodex) -> None:
+async def test_async_response_parse_custom_stream(async_client: AsyncCleanlab) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -125,7 +125,7 @@ class CustomModel(BaseModel):
     bar: int
 
 
-def test_response_parse_custom_model(client: CleanlabCodex) -> None:
+def test_response_parse_custom_model(client: Cleanlab) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: CleanlabCodex) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCodex) -> None:
+async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCode
     assert obj.bar == 2
 
 
-def test_response_parse_annotated_type(client: CleanlabCodex) -> None:
+def test_response_parse_annotated_type(client: Cleanlab) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: CleanlabCodex) -> None:
     assert obj.bar == 2
 
 
-async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCodex) -> None:
+async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCo
         ("FalSe", False),
     ],
 )
-def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool) -> None:
+def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -226,7 +226,7 @@ def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool
         ("FalSe", False),
     ],
 )
-async def test_async_response_parse_bool(client: AsyncCleanlabCodex, content: str, expected: bool) -> None:
+async def test_async_response_parse_bool(client: AsyncCleanlab, content: str, expected: bool) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -245,7 +245,7 @@ class OtherModel(BaseModel):
 
 
 @pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
-def test_response_parse_expect_model_union_non_json_content(client: CleanlabCodex) -> None:
+def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=client,
@@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: CleanlabCode
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("async_client", [False], indirect=True)  # loose validation
-async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlabCodex) -> None:
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlab) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=async_client,
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index b6b669a6..9d0b6456 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -5,13 +5,13 @@
 import httpx
 import pytest
 
-from codex import CleanlabCodex, AsyncCleanlabCodex
+from codex import Cleanlab, AsyncCleanlab
 from codex._streaming import Stream, AsyncStream, ServerSentEvent
 
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_basic(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_basic(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: completion\n"
         yield b'data: {"foo":true}\n'
@@ -28,7 +28,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_missing_event(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_data_missing_event(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"foo":true}\n'
         yield b"\n"
@@ -44,7 +44,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_event_missing_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_event_missing_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -60,7 +60,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_multiple_events(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -82,7 +82,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events_with_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_multiple_events_with_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo":true}\n'
@@ -106,9 +106,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines_with_empty_line(
-    sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex
-) -> None:
+async def test_multiple_data_lines_with_empty_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -130,9 +128,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_json_escaped_double_new_line(
-    sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex
-) -> None:
+async def test_data_json_escaped_double_new_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo": "my long\\n\\ncontent"}'
@@ -149,7 +145,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None:
+async def test_multiple_data_lines(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -169,8 +165,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_special_new_line_character(
     sync: bool,
-    client: CleanlabCodex,
-    async_client: AsyncCleanlabCodex,
+    client: Cleanlab,
+    async_client: AsyncCleanlab,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":" culpa"}\n'
@@ -200,8 +196,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_multi_byte_character_multiple_chunks(
     sync: bool,
-    client: CleanlabCodex,
-    async_client: AsyncCleanlabCodex,
+    client: Cleanlab,
+    async_client: AsyncCleanlab,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":"'
@@ -241,8 +237,8 @@ def make_event_iterator(
     content: Iterator[bytes],
     *,
     sync: bool,
-    client: CleanlabCodex,
-    async_client: AsyncCleanlabCodex,
+    client: Cleanlab,
+    async_client: AsyncCleanlab,
 ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
     if sync:
         return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()

From d13a0082f0d691ae9c771595213d2f1d115c1266 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sun, 12 Jan 2025 23:31:15 +0000
Subject: [PATCH 006/227] feat(api): update org name

---
 LICENSE                                       |   2 +-
 README.md                                     |  46 +++---
 SECURITY.md                                   |   4 +-
 pyproject.toml                                |   4 +-
 src/codex/__init__.py                         |  12 +-
 src/codex/_client.py                          |  56 +++----
 src/codex/_exceptions.py                      |   4 +-
 src/codex/_resource.py                        |  10 +-
 src/codex/_response.py                        |   4 +-
 src/codex/_streaming.py                       |   6 +-
 src/codex/_utils/_logs.py                     |   2 +-
 .../organizations/test_billing.py             |  34 ++--
 .../projects/test_access_keys.py              |  82 ++++-----
 .../api_resources/projects/test_knowledge.py  | 110 ++++++------
 tests/api_resources/test_health.py            |  38 ++---
 tests/api_resources/test_organizations.py     |  18 +-
 tests/api_resources/test_projects.py          |  82 ++++-----
 .../users/myself/test_api_key.py              |  14 +-
 .../users/myself/test_organizations.py        |  14 +-
 tests/api_resources/users/test_myself.py      |  14 +-
 tests/conftest.py                             |  10 +-
 tests/test_client.py                          | 156 +++++++++---------
 tests/test_response.py                        |  26 +--
 tests/test_streaming.py                       |  30 ++--
 24 files changed, 387 insertions(+), 391 deletions(-)

diff --git a/LICENSE b/LICENSE
index e575118f..f00d3a6a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2025 Cleanlab
+   Copyright 2025 Codex
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index b156eb36..f84291e1 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Cleanlab Python API library
+# Codex Python API library
 
 [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/)
 
-The Cleanlab Python library provides convenient access to the Cleanlab REST API from any Python 3.8+
+The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,
 and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
 
@@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
 The full API of this library can be found in [api.md](api.md).
 
 ```python
-from codex import Cleanlab
+from codex import Codex
 
-client = Cleanlab(
+client = Codex(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control.
 
 ## Async usage
 
-Simply import `AsyncCleanlab` instead of `Cleanlab` and use `await` with each API call:
+Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
 
 ```python
 import asyncio
-from codex import AsyncCleanlab
+from codex import AsyncCodex
 
-client = AsyncCleanlab(
+client = AsyncCodex(
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`.
 
 ```python
 import codex
-from codex import Cleanlab
+from codex import Codex
 
-client = Cleanlab()
+client = Codex()
 
 try:
     client.projects.create(
@@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ
 You can use the `max_retries` option to configure or disable retry settings:
 
 ```python
-from codex import Cleanlab
+from codex import Codex
 
 # Configure the default for all requests:
-client = Cleanlab(
+client = Codex(
     # default is 2
     max_retries=0,
 )
@@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time
 which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
 
 ```python
-from codex import Cleanlab
+from codex import Codex
 
 # Configure the default for all requests:
-client = Cleanlab(
+client = Codex(
     # 20 seconds (default is 1 minute)
     timeout=20.0,
 )
 
 # More granular control:
-client = Cleanlab(
+client = Codex(
     timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
 )
 
@@ -191,10 +191,10 @@ Note that requests that time out are [retried twice by default](#retries).
 
 We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
 
-You can enable logging by setting the environment variable `CLEANLAB_LOG` to `info`.
+You can enable logging by setting the environment variable `CODEX_LOG` to `info`.
 
 ```shell
-$ export CLEANLAB_LOG=info
+$ export CODEX_LOG=info
 ```
 
 Or to `debug` for more verbose logging.
@@ -216,9 +216,9 @@ if response.my_field is None:
 The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
 
 ```py
-from codex import Cleanlab
+from codex import Codex
 
-client = Cleanlab()
+client = Codex()
 response = client.projects.with_raw_response.create(
     config={},
     name="name",
@@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c
 
 ```python
 import httpx
-from codex import Cleanlab, DefaultHttpxClient
+from codex import Codex, DefaultHttpxClient
 
-client = Cleanlab(
-    # Or use the `CLEANLAB_BASE_URL` env var
+client = Codex(
+    # Or use the `CODEX_BASE_URL` env var
     base_url="http://my.test.server.example.com:8083",
     http_client=DefaultHttpxClient(
         proxy="http://my.test.proxy.example.com",
@@ -321,9 +321,9 @@ client.with_options(http_client=DefaultHttpxClient(...))
 By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
 
 ```py
-from codex import Cleanlab
+from codex import Codex
 
-with Cleanlab() as client:
+with Codex() as client:
   # make requests here
   ...
 
diff --git a/SECURITY.md b/SECURITY.md
index 2ca30344..fcad344e 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,9 +16,9 @@ before making any information public.
 ## Reporting Non-SDK Related Security Issues
 
 If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Cleanlab please follow the respective company's security reporting guidelines.
+or products provided by Codex please follow the respective company's security reporting guidelines.
 
-### Cleanlab Terms and Policies
+### Codex Terms and Policies
 
 Please contact support@cleanlab.ai for any questions or concerns regarding security of our services.
 
diff --git a/pyproject.toml b/pyproject.toml
index 5ba1074c..3fba9e70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,11 @@
 [project]
 name = "codex"
 version = "0.0.1-alpha.0"
-description = "The official Python library for the Cleanlab API"
+description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "Apache-2.0"
 authors = [
-{ name = "Cleanlab", email = "support@cleanlab.ai" },
+{ name = "Codex", email = "support@cleanlab.ai" },
 ]
 dependencies = [
     "httpx>=0.23.0, <1",
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index e3c0220d..d6dffe2c 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -5,14 +5,14 @@
 from ._utils import file_from_path
 from ._client import (
     ENVIRONMENTS,
+    Codex,
     Client,
     Stream,
     Timeout,
-    Cleanlab,
     Transport,
+    AsyncCodex,
     AsyncClient,
     AsyncStream,
-    AsyncCleanlab,
     RequestOptions,
 )
 from ._models import BaseModel
@@ -21,7 +21,7 @@
 from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
 from ._exceptions import (
     APIError,
-    CleanlabError,
+    CodexError,
     ConflictError,
     NotFoundError,
     APIStatusError,
@@ -48,7 +48,7 @@
     "NotGiven",
     "NOT_GIVEN",
     "Omit",
-    "CleanlabError",
+    "CodexError",
     "APIError",
     "APIStatusError",
     "APITimeoutError",
@@ -68,8 +68,8 @@
     "AsyncClient",
     "Stream",
     "AsyncStream",
-    "Cleanlab",
-    "AsyncCleanlab",
+    "Codex",
+    "AsyncCodex",
     "ENVIRONMENTS",
     "file_from_path",
     "BaseModel",
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 0d3b6b8d..d9e44320 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -43,8 +43,8 @@
     "Transport",
     "ProxiesTypes",
     "RequestOptions",
-    "Cleanlab",
-    "AsyncCleanlab",
+    "Codex",
+    "AsyncCodex",
     "Client",
     "AsyncClient",
 ]
@@ -56,13 +56,13 @@
 }
 
 
-class Cleanlab(SyncAPIClient):
+class Codex(SyncAPIClient):
     health: health.HealthResource
     organizations: organizations.OrganizationsResource
     users: users.UsersResource
     projects: projects.ProjectsResource
-    with_raw_response: CleanlabWithRawResponse
-    with_streaming_response: CleanlabWithStreamedResponse
+    with_raw_response: CodexWithRawResponse
+    with_streaming_response: CodexWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -97,7 +97,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous Cleanlab client instance.
+        """Construct a new synchronous Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -118,14 +118,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CLEANLAB_BASE_URL")
+        base_url_env = os.environ.get("CODEX_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -157,8 +157,8 @@ def __init__(
         self.organizations = organizations.OrganizationsResource(self)
         self.users = users.UsersResource(self)
         self.projects = projects.ProjectsResource(self)
-        self.with_raw_response = CleanlabWithRawResponse(self)
-        self.with_streaming_response = CleanlabWithStreamedResponse(self)
+        self.with_raw_response = CodexWithRawResponse(self)
+        self.with_streaming_response = CodexWithStreamedResponse(self)
 
     @property
     @override
@@ -318,13 +318,13 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class AsyncCleanlab(AsyncAPIClient):
+class AsyncCodex(AsyncAPIClient):
     health: health.AsyncHealthResource
     organizations: organizations.AsyncOrganizationsResource
     users: users.AsyncUsersResource
     projects: projects.AsyncProjectsResource
-    with_raw_response: AsyncCleanlabWithRawResponse
-    with_streaming_response: AsyncCleanlabWithStreamedResponse
+    with_raw_response: AsyncCodexWithRawResponse
+    with_streaming_response: AsyncCodexWithStreamedResponse
 
     # client options
     bearer_token: str | None
@@ -359,7 +359,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Cleanlab client instance.
+        """Construct a new async Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
         - `bearer_token` from `BEARER_TOKEN`
@@ -380,14 +380,14 @@ def __init__(
 
         self._environment = environment
 
-        base_url_env = os.environ.get("CLEANLAB_BASE_URL")
+        base_url_env = os.environ.get("CODEX_BASE_URL")
         if is_given(base_url) and base_url is not None:
             # cast required because mypy doesn't understand the type narrowing
             base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
         elif is_given(environment):
             if base_url_env and base_url is not None:
                 raise ValueError(
-                    "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
+                    "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                 )
 
             try:
@@ -419,8 +419,8 @@ def __init__(
         self.organizations = organizations.AsyncOrganizationsResource(self)
         self.users = users.AsyncUsersResource(self)
         self.projects = projects.AsyncProjectsResource(self)
-        self.with_raw_response = AsyncCleanlabWithRawResponse(self)
-        self.with_streaming_response = AsyncCleanlabWithStreamedResponse(self)
+        self.with_raw_response = AsyncCodexWithRawResponse(self)
+        self.with_streaming_response = AsyncCodexWithStreamedResponse(self)
 
     @property
     @override
@@ -580,38 +580,38 @@ def _make_status_error(
         return APIStatusError(err_msg, response=response, body=body)
 
 
-class CleanlabWithRawResponse:
-    def __init__(self, client: Cleanlab) -> None:
+class CodexWithRawResponse:
+    def __init__(self, client: Codex) -> None:
         self.health = health.HealthResourceWithRawResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.UsersResourceWithRawResponse(client.users)
         self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
 
 
-class AsyncCleanlabWithRawResponse:
-    def __init__(self, client: AsyncCleanlab) -> None:
+class AsyncCodexWithRawResponse:
+    def __init__(self, client: AsyncCodex) -> None:
         self.health = health.AsyncHealthResourceWithRawResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithRawResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
 
 
-class CleanlabWithStreamedResponse:
-    def __init__(self, client: Cleanlab) -> None:
+class CodexWithStreamedResponse:
+    def __init__(self, client: Codex) -> None:
         self.health = health.HealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.UsersResourceWithStreamingResponse(client.users)
         self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
 
 
-class AsyncCleanlabWithStreamedResponse:
-    def __init__(self, client: AsyncCleanlab) -> None:
+class AsyncCodexWithStreamedResponse:
+    def __init__(self, client: AsyncCodex) -> None:
         self.health = health.AsyncHealthResourceWithStreamingResponse(client.health)
         self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
 
 
-Client = Cleanlab
+Client = Codex
 
-AsyncClient = AsyncCleanlab
+AsyncClient = AsyncCodex
diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py
index db0f4b62..90164ee4 100644
--- a/src/codex/_exceptions.py
+++ b/src/codex/_exceptions.py
@@ -18,11 +18,11 @@
 ]
 
 
-class CleanlabError(Exception):
+class CodexError(Exception):
     pass
 
 
-class APIError(CleanlabError):
+class APIError(CodexError):
     message: str
     request: httpx.Request
 
diff --git a/src/codex/_resource.py b/src/codex/_resource.py
index 1bf30f6b..7ba43eeb 100644
--- a/src/codex/_resource.py
+++ b/src/codex/_resource.py
@@ -8,13 +8,13 @@
 import anyio
 
 if TYPE_CHECKING:
-    from ._client import Cleanlab, AsyncCleanlab
+    from ._client import Codex, AsyncCodex
 
 
 class SyncAPIResource:
-    _client: Cleanlab
+    _client: Codex
 
-    def __init__(self, client: Cleanlab) -> None:
+    def __init__(self, client: Codex) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
@@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None:
 
 
 class AsyncAPIResource:
-    _client: AsyncCleanlab
+    _client: AsyncCodex
 
-    def __init__(self, client: AsyncCleanlab) -> None:
+    def __init__(self, client: AsyncCodex) -> None:
         self._client = client
         self._get = client.get
         self._post = client.post
diff --git a/src/codex/_response.py b/src/codex/_response.py
index 4faa64ef..b63926f4 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -29,7 +29,7 @@
 from ._models import BaseModel, is_basemodel
 from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
 from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
-from ._exceptions import CleanlabError, APIResponseValidationError
+from ._exceptions import CodexError, APIResponseValidationError
 
 if TYPE_CHECKING:
     from ._models import FinalRequestOptions
@@ -554,7 +554,7 @@ def __init__(self) -> None:
         )
 
 
-class StreamAlreadyConsumed(CleanlabError):
+class StreamAlreadyConsumed(CodexError):
     """
     Attempted to read or stream content, but the content has already
     been streamed.
diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py
index e545fc0f..3af102ce 100644
--- a/src/codex/_streaming.py
+++ b/src/codex/_streaming.py
@@ -12,7 +12,7 @@
 from ._utils import extract_type_var_from_base
 
 if TYPE_CHECKING:
-    from ._client import Cleanlab, AsyncCleanlab
+    from ._client import Codex, AsyncCodex
 
 
 _T = TypeVar("_T")
@@ -30,7 +30,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: Cleanlab,
+        client: Codex,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
@@ -93,7 +93,7 @@ def __init__(
         *,
         cast_to: type[_T],
         response: httpx.Response,
-        client: AsyncCleanlab,
+        client: AsyncCodex,
     ) -> None:
         self.response = response
         self._cast_to = cast_to
diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py
index 58944f5f..80932c4f 100644
--- a/src/codex/_utils/_logs.py
+++ b/src/codex/_utils/_logs.py
@@ -14,7 +14,7 @@ def _basic_config() -> None:
 
 
 def setup_logging() -> None:
-    env = os.environ.get("CLEANLAB_LOG")
+    env = os.environ.get("CODEX_LOG")
     if env == "debug":
         _basic_config()
         logger.setLevel(logging.DEBUG)
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index c1530b3b..ccb7dc8a 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema
 
@@ -18,14 +18,14 @@ class TestBilling:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_invoices(self, client: Cleanlab) -> None:
+    def test_method_invoices(self, client: Codex) -> None:
         billing = client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_invoices(self, client: Cleanlab) -> None:
+    def test_raw_response_invoices(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: Cleanlab) -> None:
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_invoices(self, client: Cleanlab) -> None:
+    def test_streaming_response_invoices(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_invoices(self, client: Cleanlab) -> None:
+    def test_path_params_invoices(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    def test_method_usage(self, client: Cleanlab) -> None:
+    def test_method_usage(self, client: Codex) -> None:
         billing = client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_raw_response_usage(self, client: Cleanlab) -> None:
+    def test_raw_response_usage(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: Cleanlab) -> None:
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    def test_streaming_response_usage(self, client: Cleanlab) -> None:
+    def test_streaming_response_usage(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_usage(self, client: Cleanlab) -> None:
+    def test_path_params_usage(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.billing.with_raw_response.usage(
                 "",
@@ -98,14 +98,14 @@ class TestAsyncBilling:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_invoices(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_invoices(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.invoices(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_invoices(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.invoices(
                 "",
             )
 
     @parametrize
-    async def test_method_usage(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_usage(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
     @parametrize
-    async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.usage(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_usage(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_usage(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.billing.with_raw_response.usage(
                 "",
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 26f984b0..4fa1636f 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
 from codex.types.projects import (
@@ -22,7 +22,7 @@ class TestAccessKeys:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Cleanlab) -> None:
+    def test_method_create(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_create_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Cleanlab) -> None:
+    def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -52,7 +52,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Cleanlab) -> None:
+    def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Cleanlab) -> None:
+    def test_method_retrieve(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -74,7 +74,7 @@ def test_method_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
+    def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: Cleanlab) -> None:
+    def test_method_update(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -109,7 +109,7 @@ def test_method_update(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_update_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Cleanlab) -> None:
+    def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -133,7 +133,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Cleanlab) -> None:
+    def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: Cleanlab) -> None:
+    def test_method_list(self, client: Codex) -> None:
         access_key = client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Cleanlab) -> None:
+    def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -166,7 +166,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None:
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Cleanlab) -> None:
+    def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Cleanlab) -> None:
+    def test_method_delete(self, client: Codex) -> None:
         access_key = client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -187,7 +187,7 @@ def test_method_delete(self, client: Cleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Cleanlab) -> None:
+    def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Cleanlab) -> None:
+    def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_revoke(self, client: Cleanlab) -> None:
+    def test_method_revoke(self, client: Codex) -> None:
         access_key = client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -221,7 +221,7 @@ def test_method_revoke(self, client: Cleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    def test_raw_response_revoke(self, client: Cleanlab) -> None:
+    def test_raw_response_revoke(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: Cleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    def test_streaming_response_revoke(self, client: Cleanlab) -> None:
+    def test_streaming_response_revoke(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -251,7 +251,7 @@ class TestAsyncAccessKeys:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
             project_id=0,
             name="name",
@@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
             project_id=0,
             name="name",
@@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
             project_id=0,
             name="name",
@@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
             access_key_id=0,
             project_id=0,
@@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
             access_key_id=0,
             project_id=0,
@@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
             access_key_id=0,
             project_id=0,
@@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
             access_key_id=0,
             project_id=0,
@@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
             access_key_id=0,
             project_id=0,
@@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.list(
             0,
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
             0,
         )
@@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
             0,
         ) as response:
@@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.delete(
             access_key_id=0,
             project_id=0,
@@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
             access_key_id=0,
             project_id=0,
@@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
             access_key_id=0,
             project_id=0,
@@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_revoke(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(
             access_key_id=0,
             project_id=0,
@@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
             access_key_id=0,
             project_id=0,
@@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None:
         assert access_key is None
 
     @parametrize
-    async def test_streaming_response_revoke(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
             access_key_id=0,
             project_id=0,
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
index 9755583f..9052b62d 100644
--- a/tests/api_resources/projects/test_knowledge.py
+++ b/tests/api_resources/projects/test_knowledge.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex.types.projects import (
     Entry,
@@ -21,7 +21,7 @@ class TestKnowledge:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Cleanlab) -> None:
+    def test_method_create(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -29,7 +29,7 @@ def test_method_create(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_create_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Cleanlab) -> None:
+    def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -50,7 +50,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Cleanlab) -> None:
+    def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Cleanlab) -> None:
+    def test_method_retrieve(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -72,7 +72,7 @@ def test_method_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
+    def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: Cleanlab) -> None:
+    def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: Cleanlab) -> None:
             )
 
     @parametrize
-    def test_method_update(self, client: Cleanlab) -> None:
+    def test_method_update(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -114,7 +114,7 @@ def test_method_update(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_update_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Cleanlab) -> None:
+    def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -136,7 +136,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Cleanlab) -> None:
+    def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_update(self, client: Cleanlab) -> None:
+    def test_path_params_update(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -158,14 +158,14 @@ def test_path_params_update(self, client: Cleanlab) -> None:
             )
 
     @parametrize
-    def test_method_list(self, client: Cleanlab) -> None:
+    def test_method_list(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_method_list_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_list_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Cleanlab) -> None:
+    def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -189,7 +189,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Cleanlab) -> None:
+    def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Cleanlab) -> None:
+    def test_method_delete(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -210,7 +210,7 @@ def test_method_delete(self, client: Cleanlab) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Cleanlab) -> None:
+    def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None:
         assert knowledge is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Cleanlab) -> None:
+    def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_delete(self, client: Cleanlab) -> None:
+    def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -244,7 +244,7 @@ def test_path_params_delete(self, client: Cleanlab) -> None:
             )
 
     @parametrize
-    def test_method_add_question(self, client: Cleanlab) -> None:
+    def test_method_add_question(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -252,7 +252,7 @@ def test_method_add_question(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_add_question(self, client: Cleanlab) -> None:
+    def test_raw_response_add_question(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: Cleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_add_question(self, client: Cleanlab) -> None:
+    def test_streaming_response_add_question(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_query(self, client: Cleanlab) -> None:
+    def test_method_query(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -286,7 +286,7 @@ def test_method_query(self, client: Cleanlab) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_raw_response_query(self, client: Cleanlab) -> None:
+    def test_raw_response_query(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -298,7 +298,7 @@ def test_raw_response_query(self, client: Cleanlab) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    def test_streaming_response_query(self, client: Cleanlab) -> None:
+    def test_streaming_response_query(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
@@ -316,7 +316,7 @@ class TestAsyncKnowledge:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
             project_id=0,
             question="question",
@@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.create(
             project_id=0,
             question="question",
@@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.create(
             project_id=0,
             question="question",
@@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.retrieve(
                 entry_id="",
@@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None:
             )
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.update(
                 entry_id="",
@@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCleanlab) -> None:
             )
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
             project_id=0,
             answered_only=True,
@@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) ->
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.list(
             project_id=0,
         )
@@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.list(
             project_id=0,
         ) as response:
@@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
         assert knowledge is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         assert knowledge is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id=0,
@@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.knowledge.with_raw_response.delete(
                 entry_id="",
@@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None:
             )
 
     @parametrize
-    async def test_method_add_question(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.add_question(
             project_id=0,
             question="question",
@@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.add_question(
             project_id=0,
             question="question",
@@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> N
         assert_matches_type(Entry, knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_add_question(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.add_question(
             project_id=0,
             question="question",
@@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCleanlab
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_query(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_query(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.query(
             project_id=0,
             question="question",
@@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.query(
             project_id=0,
             question="question",
@@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
     @parametrize
-    async def test_streaming_response_query(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.query(
             project_id=0,
             question="question",
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index 98e6c91e..be4729ab 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from codex.types import HealthCheckResponse
 from tests.utils import assert_matches_type
 
@@ -18,12 +18,12 @@ class TestHealth:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_check(self, client: Cleanlab) -> None:
+    def test_method_check(self, client: Codex) -> None:
         health = client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_check(self, client: Cleanlab) -> None:
+    def test_raw_response_check(self, client: Codex) -> None:
         response = client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_check(self, client: Cleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_check(self, client: Cleanlab) -> None:
+    def test_streaming_response_check(self, client: Codex) -> None:
         with client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_db(self, client: Cleanlab) -> None:
+    def test_method_db(self, client: Codex) -> None:
         health = client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_db(self, client: Cleanlab) -> None:
+    def test_raw_response_db(self, client: Codex) -> None:
         response = client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -57,7 +57,7 @@ def test_raw_response_db(self, client: Cleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_db(self, client: Cleanlab) -> None:
+    def test_streaming_response_db(self, client: Codex) -> None:
         with client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_weaviate(self, client: Cleanlab) -> None:
+    def test_method_weaviate(self, client: Codex) -> None:
         health = client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_raw_response_weaviate(self, client: Cleanlab) -> None:
+    def test_raw_response_weaviate(self, client: Codex) -> None:
         response = client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: Cleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    def test_streaming_response_weaviate(self, client: Cleanlab) -> None:
+    def test_streaming_response_weaviate(self, client: Codex) -> None:
         with client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -97,12 +97,12 @@ class TestAsyncHealth:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_check(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_check(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.check()
 
         assert response.is_closed is True
@@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.check() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> No
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_db(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_db(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.db()
 
         assert response.is_closed is True
@@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.db() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_weaviate(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_weaviate(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.weaviate()
 
         assert response.is_closed is True
@@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
     @parametrize
-    async def test_streaming_response_weaviate(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.weaviate() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index fa17b24b..2d627c70 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from codex.types import OrganizationSchemaPublic
 from tests.utils import assert_matches_type
 
@@ -18,14 +18,14 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: Cleanlab) -> None:
+    def test_method_retrieve(self, client: Codex) -> None:
         organization = client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
+    def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_path_params_retrieve(self, client: Cleanlab) -> None:
+    def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             client.organizations.with_raw_response.retrieve(
                 "",
@@ -60,14 +60,14 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         organization = await async_client.organizations.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.with_raw_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.with_streaming_response.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
             await async_client.organizations.with_raw_response.retrieve(
                 "",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index c516e727..7e79d8e8 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from codex.types import (
     ProjectListResponse,
     ProjectReturnSchema,
@@ -21,7 +21,7 @@ class TestProjects:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_create(self, client: Cleanlab) -> None:
+    def test_method_create(self, client: Codex) -> None:
         project = client.projects.create(
             config={},
             name="name",
@@ -30,7 +30,7 @@ def test_method_create(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_create_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_create_with_all_params(self, client: Codex) -> None:
         project = client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_create(self, client: Cleanlab) -> None:
+    def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -53,7 +53,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_create(self, client: Cleanlab) -> None:
+    def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_retrieve(self, client: Cleanlab) -> None:
+    def test_method_retrieve(self, client: Codex) -> None:
         project = client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
+    def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.with_raw_response.retrieve(
             0,
         )
@@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_update(self, client: Cleanlab) -> None:
+    def test_method_update(self, client: Codex) -> None:
         project = client.projects.update(
             project_id=0,
             config={},
@@ -108,7 +108,7 @@ def test_method_update(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_method_update_with_all_params(self, client: Cleanlab) -> None:
+    def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_raw_response_update(self, client: Cleanlab) -> None:
+    def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -131,7 +131,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_update(self, client: Cleanlab) -> None:
+    def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_list(self, client: Cleanlab) -> None:
+    def test_method_list(self, client: Codex) -> None:
         project = client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Cleanlab) -> None:
+    def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -164,7 +164,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None:
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Cleanlab) -> None:
+    def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_delete(self, client: Cleanlab) -> None:
+    def test_method_delete(self, client: Codex) -> None:
         project = client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    def test_raw_response_delete(self, client: Cleanlab) -> None:
+    def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.with_raw_response.delete(
             0,
         )
@@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None:
         assert project is None
 
     @parametrize
-    def test_streaming_response_delete(self, client: Cleanlab) -> None:
+    def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None:
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    def test_method_export(self, client: Cleanlab) -> None:
+    def test_method_export(self, client: Codex) -> None:
         project = client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_raw_response_export(self, client: Cleanlab) -> None:
+    def test_raw_response_export(self, client: Codex) -> None:
         response = client.projects.with_raw_response.export(
             0,
         )
@@ -226,7 +226,7 @@ def test_raw_response_export(self, client: Cleanlab) -> None:
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    def test_streaming_response_export(self, client: Cleanlab) -> None:
+    def test_streaming_response_export(self, client: Codex) -> None:
         with client.projects.with_streaming_response.export(
             0,
         ) as response:
@@ -243,7 +243,7 @@ class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
             config={},
             name="name",
@@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
             config={"max_distance": 0},
             name="name",
@@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.create(
             config={},
             name="name",
@@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.create(
             config={},
             name="name",
@@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve(
             0,
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
             0,
         )
@@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
             0,
         ) as response:
@@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) ->
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={},
@@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
             project_id=0,
             config={"max_distance": 0},
@@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab)
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
             project_id=0,
             config={},
@@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
             project_id=0,
             config={},
@@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
@@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.list(
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
@@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.delete(
             0,
         )
         assert project is None
 
     @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.delete(
             0,
         )
@@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None:
         assert project is None
 
     @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.delete(
             0,
         ) as response:
@@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N
         assert cast(Any, response.is_closed) is True
 
     @parametrize
-    async def test_method_export(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_export(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.export(
             0,
         )
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.export(
             0,
         )
@@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(object, project, path=["response"])
 
     @parametrize
-    async def test_streaming_response_export(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.export(
             0,
         ) as response:
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index 36a68194..55bba35f 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchema
 
@@ -18,12 +18,12 @@ class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_refresh(self, client: Cleanlab) -> None:
+    def test_method_refresh(self, client: Codex) -> None:
         api_key = client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_raw_response_refresh(self, client: Cleanlab) -> None:
+    def test_raw_response_refresh(self, client: Codex) -> None:
         response = client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: Cleanlab) -> None:
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    def test_streaming_response_refresh(self, client: Cleanlab) -> None:
+    def test_streaming_response_refresh(self, client: Codex) -> None:
         with client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncAPIKey:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_refresh(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_refresh(self, async_client: AsyncCodex) -> None:
         api_key = await async_client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.refresh()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(UserSchema, api_key, path=["response"])
 
     @parametrize
-    async def test_streaming_response_refresh(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index d50c4742..da485882 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex.types.users.myself import UserOrganizationsSchema
 
@@ -18,12 +18,12 @@ class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_list(self, client: Cleanlab) -> None:
+    def test_method_list(self, client: Codex) -> None:
         organization = client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_raw_response_list(self, client: Cleanlab) -> None:
+    def test_raw_response_list(self, client: Codex) -> None:
         response = client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None:
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    def test_streaming_response_list(self, client: Cleanlab) -> None:
+    def test_streaming_response_list(self, client: Codex) -> None:
         with client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
         organization = await async_client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.organizations.with_raw_response.list()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
     @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.organizations.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index f90be5c0..a206d1f9 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchemaPublic
 
@@ -18,12 +18,12 @@ class TestMyself:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    def test_method_retrieve(self, client: Cleanlab) -> None:
+    def test_method_retrieve(self, client: Codex) -> None:
         myself = client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_raw_response_retrieve(self, client: Cleanlab) -> None:
+    def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None:
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    def test_streaming_response_retrieve(self, client: Cleanlab) -> None:
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -47,12 +47,12 @@ class TestAsyncMyself:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
     @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await async_client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.with_raw_response.retrieve()
 
         assert response.is_closed is True
@@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None:
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None:
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.with_streaming_response.retrieve() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/conftest.py b/tests/conftest.py
index 40cbc004..8823ef7a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,7 +7,7 @@
 import pytest
 from pytest_asyncio import is_async_test
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 
 if TYPE_CHECKING:
     from _pytest.fixtures import FixtureRequest
@@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
 
 
 @pytest.fixture(scope="session")
-def client(request: FixtureRequest) -> Iterator[Cleanlab]:
+def client(request: FixtureRequest) -> Iterator[Codex]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    with Cleanlab(base_url=base_url, _strict_response_validation=strict) as client:
+    with Codex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
 
 
 @pytest.fixture(scope="session")
-async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlab]:
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
     strict = getattr(request, "param", True)
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    async with AsyncCleanlab(base_url=base_url, _strict_response_validation=strict) as client:
+    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client:
         yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index b9a2b4b9..4e76d238 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -20,7 +20,7 @@
 from respx import MockRouter
 from pydantic import ValidationError
 
-from codex import Cleanlab, AsyncCleanlab, APIResponseValidationError
+from codex import Codex, AsyncCodex, APIResponseValidationError
 from codex._types import Omit
 from codex._models import BaseModel, FinalRequestOptions
 from codex._constants import RAW_RESPONSE_HEADER
@@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
     return 0.1
 
 
-def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int:
+def _get_open_connections(client: Codex | AsyncCodex) -> int:
     transport = client._client._transport
     assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
 
@@ -50,8 +50,8 @@ def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int:
     return len(pool._requests)
 
 
-class TestCleanlab:
-    client = Cleanlab(base_url=base_url, _strict_response_validation=True)
+class TestCodex:
+    client = Codex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     def test_raw_response(self, respx_mock: MockRouter) -> None:
@@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -249,7 +249,7 @@ def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     def test_client_timeout_option(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None:
     def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         with httpx.Client(timeout=None) as http_client:
-            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         with httpx.Client() as http_client:
-            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None:
     async def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             async with httpx.AsyncClient() as http_client:
-                Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = Cleanlab(
+        client2 = Codex(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -304,7 +304,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
+        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -416,7 +416,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, client: Cleanlab) -> None:
+    def test_multipart_repeating_array(self, client: Codex) -> None:
         request = client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -503,7 +503,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = Cleanlab(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -511,23 +511,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
-            client = Cleanlab(_strict_response_validation=True)
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = Codex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                Cleanlab(_strict_response_validation=True, environment="production")
+                Codex(_strict_response_validation=True, environment="production")
 
-            client = Cleanlab(base_url=None, _strict_response_validation=True, environment="production")
+            client = Codex(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Cleanlab(
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -535,7 +535,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: Cleanlab) -> None:
+    def test_base_url_trailing_slash(self, client: Codex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -548,8 +548,8 @@ def test_base_url_trailing_slash(self, client: Cleanlab) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Cleanlab(
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -557,7 +557,7 @@ def test_base_url_trailing_slash(self, client: Cleanlab) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None:
+    def test_base_url_no_trailing_slash(self, client: Codex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -570,8 +570,8 @@ def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            Cleanlab(
+            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
@@ -579,7 +579,7 @@ def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: Cleanlab) -> None:
+    def test_absolute_request_url(self, client: Codex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -590,7 +590,7 @@ def test_absolute_request_url(self, client: Cleanlab) -> None:
         assert request.url == "https://myapi.com/foo"
 
     def test_copied_client_does_not_close_http(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -601,7 +601,7 @@ def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     def test_client_context_manager(self) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, _strict_response_validation=True)
         with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -622,7 +622,7 @@ class Model(BaseModel):
 
     def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            Cleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -631,12 +631,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = Cleanlab(base_url=base_url, _strict_response_validation=True)
+        strict_client = Codex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             strict_client.get("/foo", cast_to=Model)
 
-        client = Cleanlab(base_url=base_url, _strict_response_validation=False)
+        client = Codex(base_url=base_url, _strict_response_validation=False)
 
         response = client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -664,7 +664,7 @@ class Model(BaseModel):
     )
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = Cleanlab(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -707,7 +707,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     def test_retries_taken(
         self,
-        client: Cleanlab,
+        client: Codex,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -737,9 +737,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    def test_omit_retry_count_header(
-        self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter
-    ) -> None:
+    def test_omit_retry_count_header(self, client: Codex, failures_before_success: int, respx_mock: MockRouter) -> None:
         client = client.with_options(max_retries=4)
 
         nb_retries = 0
@@ -766,7 +764,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
     def test_overwrite_retry_count_header(
-        self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter
+        self, client: Codex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = client.with_options(max_retries=4)
 
@@ -791,8 +789,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
         assert response.http_request.headers.get("x-stainless-retry-count") == "42"
 
 
-class TestAsyncCleanlab:
-    client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
+class TestAsyncCodex:
+    client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -837,7 +835,7 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -869,7 +867,7 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -992,7 +990,7 @@ async def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     async def test_client_timeout_option(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1001,7 +999,7 @@ async def test_client_timeout_option(self) -> None:
     async def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         async with httpx.AsyncClient(timeout=None) as http_client:
-            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1009,7 +1007,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         async with httpx.AsyncClient() as http_client:
-            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1017,7 +1015,7 @@ async def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1026,15 +1024,15 @@ async def test_http_client_timeout_option(self) -> None:
     def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             with httpx.Client() as http_client:
-                AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
 
     def test_default_headers_option(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
-        client2 = AsyncCleanlab(
+        client2 = AsyncCodex(
             base_url=base_url,
             _strict_response_validation=True,
             default_headers={
@@ -1047,9 +1045,7 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = AsyncCleanlab(
-            base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}
-        )
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -1161,7 +1157,7 @@ def test_request_extra_query(self) -> None:
         params = dict(request.url.params)
         assert params == {"foo": "2"}
 
-    def test_multipart_repeating_array(self, async_client: AsyncCleanlab) -> None:
+    def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None:
         request = async_client._build_request(
             FinalRequestOptions.construct(
                 method="get",
@@ -1248,7 +1244,7 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = AsyncCleanlab(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -1256,23 +1252,23 @@ def test_base_url_setter(self) -> None:
         assert client.base_url == "https://example.com/from_setter/"
 
     def test_base_url_env(self) -> None:
-        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
-            client = AsyncCleanlab(_strict_response_validation=True)
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
+            client = AsyncCodex(_strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
-        with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"):
+        with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                AsyncCleanlab(_strict_response_validation=True, environment="production")
+                AsyncCodex(_strict_response_validation=True, environment="production")
 
-            client = AsyncCleanlab(base_url=None, _strict_response_validation=True, environment="production")
+            client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
             assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlab(
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1280,7 +1276,7 @@ def test_base_url_env(self) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None:
+    def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1293,8 +1289,8 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlab(
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1302,7 +1298,7 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None:
+    def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1315,8 +1311,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
-            AsyncCleanlab(
+            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
@@ -1324,7 +1320,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None:
         ],
         ids=["standard", "custom http client"],
     )
-    def test_absolute_request_url(self, client: AsyncCleanlab) -> None:
+    def test_absolute_request_url(self, client: AsyncCodex) -> None:
         request = client._build_request(
             FinalRequestOptions(
                 method="post",
@@ -1335,7 +1331,7 @@ def test_absolute_request_url(self, client: AsyncCleanlab) -> None:
         assert request.url == "https://myapi.com/foo"
 
     async def test_copied_client_does_not_close_http(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -1347,7 +1343,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     async def test_client_context_manager(self) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
         async with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -1369,7 +1365,7 @@ class Model(BaseModel):
 
     async def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            AsyncCleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -1379,12 +1375,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
+        strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             await strict_client.get("/foo", cast_to=Model)
 
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=False)
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=False)
 
         response = await client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -1413,7 +1409,7 @@ class Model(BaseModel):
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     @pytest.mark.asyncio
     async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -1457,7 +1453,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter)
     @pytest.mark.parametrize("failure_mode", ["status", "exception"])
     async def test_retries_taken(
         self,
-        async_client: AsyncCleanlab,
+        async_client: AsyncCodex,
         failures_before_success: int,
         failure_mode: Literal["status", "exception"],
         respx_mock: MockRouter,
@@ -1489,7 +1485,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_omit_retry_count_header(
-        self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
@@ -1518,7 +1514,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
     async def test_overwrite_retry_count_header(
-        self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter
+        self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter
     ) -> None:
         client = async_client.with_options(max_retries=4)
 
diff --git a/tests/test_response.py b/tests/test_response.py
index 55b100ce..224eb22e 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -6,7 +6,7 @@
 import pytest
 import pydantic
 
-from codex import Cleanlab, BaseModel, AsyncCleanlab
+from codex import Codex, BaseModel, AsyncCodex
 from codex._response import (
     APIResponse,
     BaseAPIResponse,
@@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None:
 class PydanticModel(pydantic.BaseModel): ...
 
 
-def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None:
+def test_response_parse_mismatched_basemodel(client: Codex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlab) -> None:
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncClea
         await response.parse(to=PydanticModel)
 
 
-def test_response_parse_custom_stream(client: Cleanlab) -> None:
+def test_response_parse_custom_stream(client: Codex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=client,
@@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: Cleanlab) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_stream(async_client: AsyncCleanlab) -> None:
+async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo"),
         client=async_client,
@@ -125,7 +125,7 @@ class CustomModel(BaseModel):
     bar: int
 
 
-def test_response_parse_custom_model(client: Cleanlab) -> None:
+def test_response_parse_custom_model(client: Codex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: Cleanlab) -> None:
 
 
 @pytest.mark.asyncio
-async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) -> None:
+async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) ->
     assert obj.bar == 2
 
 
-def test_response_parse_annotated_type(client: Cleanlab) -> None:
+def test_response_parse_annotated_type(client: Codex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=client,
@@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: Cleanlab) -> None:
     assert obj.bar == 2
 
 
-async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab) -> None:
+async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
         client=async_client,
@@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab)
         ("FalSe", False),
     ],
 )
-def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) -> None:
+def test_response_parse_bool(client: Codex, content: str, expected: bool) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -226,7 +226,7 @@ def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) ->
         ("FalSe", False),
     ],
 )
-async def test_async_response_parse_bool(client: AsyncCleanlab, content: str, expected: bool) -> None:
+async def test_async_response_parse_bool(client: AsyncCodex, content: str, expected: bool) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=content),
         client=client,
@@ -245,7 +245,7 @@ class OtherModel(BaseModel):
 
 
 @pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
-def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) -> None:
+def test_response_parse_expect_model_union_non_json_content(client: Codex) -> None:
     response = APIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=client,
@@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) ->
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("async_client", [False], indirect=True)  # loose validation
-async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlab) -> None:
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCodex) -> None:
     response = AsyncAPIResponse(
         raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
         client=async_client,
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index 9d0b6456..443b5aa5 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -5,13 +5,13 @@
 import httpx
 import pytest
 
-from codex import Cleanlab, AsyncCleanlab
+from codex import Codex, AsyncCodex
 from codex._streaming import Stream, AsyncStream, ServerSentEvent
 
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_basic(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: completion\n"
         yield b'data: {"foo":true}\n'
@@ -28,7 +28,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_missing_event(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"foo":true}\n'
         yield b"\n"
@@ -44,7 +44,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_event_missing_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -60,7 +60,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"\n"
@@ -82,7 +82,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_events_with_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo":true}\n'
@@ -106,7 +106,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines_with_empty_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -128,7 +128,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_data_json_escaped_double_new_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b'data: {"foo": "my long\\n\\ncontent"}'
@@ -145,7 +145,7 @@ def body() -> Iterator[bytes]:
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
-async def test_multiple_data_lines(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None:
+async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None:
     def body() -> Iterator[bytes]:
         yield b"event: ping\n"
         yield b"data: {\n"
@@ -165,8 +165,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_special_new_line_character(
     sync: bool,
-    client: Cleanlab,
-    async_client: AsyncCleanlab,
+    client: Codex,
+    async_client: AsyncCodex,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":" culpa"}\n'
@@ -196,8 +196,8 @@ def body() -> Iterator[bytes]:
 @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
 async def test_multi_byte_character_multiple_chunks(
     sync: bool,
-    client: Cleanlab,
-    async_client: AsyncCleanlab,
+    client: Codex,
+    async_client: AsyncCodex,
 ) -> None:
     def body() -> Iterator[bytes]:
         yield b'data: {"content":"'
@@ -237,8 +237,8 @@ def make_event_iterator(
     content: Iterator[bytes],
     *,
     sync: bool,
-    client: Cleanlab,
-    async_client: AsyncCleanlab,
+    client: Codex,
+    async_client: AsyncCodex,
 ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
     if sync:
         return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()

From 1c7da06305804f627b15e06dee377ab22a41601d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 13 Jan 2025 00:14:43 +0000
Subject: [PATCH 007/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 71d28223..b9a5b6ba 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
 configured_endpoints: 28
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-f4a89fd69be5055c12ea6d3aa795af4161605848eb9e010e1a7be22f4b1b712c.yml
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml

From fa64ac38902c6d243e45166922181b00b5e79880 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 13 Jan 2025 00:25:17 +0000
Subject: [PATCH 008/227] feat(api): manual updates

---
 .stats.yml                                    |  2 +-
 api.md                                        |  7 ++-
 src/codex/resources/projects/access_keys.py   | 51 +++++++++++++++++++
 src/codex/types/projects/__init__.py          |  3 ++
 ...access_key_retrieve_project_id_response.py |  7 +++
 .../projects/test_access_keys.py              | 51 +++++++++++++++++++
 6 files changed, 119 insertions(+), 2 deletions(-)
 create mode 100644 src/codex/types/projects/access_key_retrieve_project_id_response.py

diff --git a/.stats.yml b/.stats.yml
index b9a5b6ba..a156c044 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 28
+configured_endpoints: 29
 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml
diff --git a/api.md b/api.md
index c9ef6c6c..672aeeb6 100644
--- a/api.md
+++ b/api.md
@@ -94,7 +94,11 @@ Methods:
 Types:
 
 ```python
-from codex.types.projects import AccessKeySchema, AccessKeyListResponse
+from codex.types.projects import (
+    AccessKeySchema,
+    AccessKeyListResponse,
+    AccessKeyRetrieveProjectIDResponse,
+)
 ```
 
 Methods:
@@ -104,6 +108,7 @@ Methods:
 - <code title="put /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">update</a>(access_key_id, \*, project_id, \*\*<a href="src/codex/types/projects/access_key_update_params.py">params</a>) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
 - <code title="get /api/projects/{project_id}/access_keys/">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">list</a>(project_id) -> <a href="./src/codex/types/projects/access_key_list_response.py">AccessKeyListResponse</a></code>
 - <code title="delete /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">delete</a>(access_key_id, \*, project_id) -> None</code>
+- <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> <a href="./src/codex/types/projects/access_key_retrieve_project_id_response.py">AccessKeyRetrieveProjectIDResponse</a></code>
 - <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
 
 ## Knowledge
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 7fc21ae9..2882e65b 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -24,6 +24,7 @@
 from ...types.projects import access_key_create_params, access_key_update_params
 from ...types.projects.access_key_schema import AccessKeySchema
 from ...types.projects.access_key_list_response import AccessKeyListResponse
+from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse
 
 __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"]
 
@@ -229,6 +230,25 @@ def delete(
             cast_to=NoneType,
         )
 
+    def retrieve_project_id(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeyRetrieveProjectIDResponse:
+        """Get the project ID from an access key."""
+        return self._get(
+            "/api/projects/id_from_access_key",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=int,
+        )
+
     def revoke(
         self,
         access_key_id: int,
@@ -464,6 +484,25 @@ async def delete(
             cast_to=NoneType,
         )
 
+    async def retrieve_project_id(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AccessKeyRetrieveProjectIDResponse:
+        """Get the project ID from an access key."""
+        return await self._get(
+            "/api/projects/id_from_access_key",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=int,
+        )
+
     async def revoke(
         self,
         access_key_id: int,
@@ -517,6 +556,9 @@ def __init__(self, access_keys: AccessKeysResource) -> None:
         self.delete = to_raw_response_wrapper(
             access_keys.delete,
         )
+        self.retrieve_project_id = to_raw_response_wrapper(
+            access_keys.retrieve_project_id,
+        )
         self.revoke = to_raw_response_wrapper(
             access_keys.revoke,
         )
@@ -541,6 +583,9 @@ def __init__(self, access_keys: AsyncAccessKeysResource) -> None:
         self.delete = async_to_raw_response_wrapper(
             access_keys.delete,
         )
+        self.retrieve_project_id = async_to_raw_response_wrapper(
+            access_keys.retrieve_project_id,
+        )
         self.revoke = async_to_raw_response_wrapper(
             access_keys.revoke,
         )
@@ -565,6 +610,9 @@ def __init__(self, access_keys: AccessKeysResource) -> None:
         self.delete = to_streamed_response_wrapper(
             access_keys.delete,
         )
+        self.retrieve_project_id = to_streamed_response_wrapper(
+            access_keys.retrieve_project_id,
+        )
         self.revoke = to_streamed_response_wrapper(
             access_keys.revoke,
         )
@@ -589,6 +637,9 @@ def __init__(self, access_keys: AsyncAccessKeysResource) -> None:
         self.delete = async_to_streamed_response_wrapper(
             access_keys.delete,
         )
+        self.retrieve_project_id = async_to_streamed_response_wrapper(
+            access_keys.retrieve_project_id,
+        )
         self.revoke = async_to_streamed_response_wrapper(
             access_keys.revoke,
         )
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 89e09d61..9813f780 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -13,3 +13,6 @@
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
 from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams
+from .access_key_retrieve_project_id_response import (
+    AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
+)
diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py
new file mode 100644
index 00000000..5659cd21
--- /dev/null
+++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AccessKeyRetrieveProjectIDResponse"]
+
+AccessKeyRetrieveProjectIDResponse: TypeAlias = int
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 4fa1636f..eab961cc 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -13,6 +13,7 @@
 from codex.types.projects import (
     AccessKeySchema,
     AccessKeyListResponse,
+    AccessKeyRetrieveProjectIDResponse,
 )
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -212,6 +213,31 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @parametrize
+    def test_method_retrieve_project_id(self, client: Codex) -> None:
+        access_key = client.projects.access_keys.retrieve_project_id()
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+    @parametrize
+    def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
+        response = client.projects.access_keys.with_raw_response.retrieve_project_id()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = response.parse()
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+    @parametrize
+    def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
+        with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = response.parse()
+            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
     @parametrize
     def test_method_revoke(self, client: Codex) -> None:
         access_key = client.projects.access_keys.revoke(
@@ -441,6 +467,31 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @parametrize
+    async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None:
+        access_key = await async_client.projects.access_keys.retrieve_project_id()
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+    @parametrize
+    async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        access_key = await response.parse()
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+    @parametrize
+    async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            access_key = await response.parse()
+            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
     @parametrize
     async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(

From f9167e0a25690d70f896f59945efd64ca33dd896 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 19:12:27 +0000
Subject: [PATCH 009/227] chore: go live (#1)

---
 .github/workflows/publish-pypi.yml            | 31 +++++++++
 .github/workflows/release-doctor.yml          | 21 ++++++
 .release-please-manifest.json                 |  3 +
 CONTRIBUTING.md                               |  4 +-
 README.md                                     | 10 +--
 bin/check-release-environment                 | 21 ++++++
 pyproject.toml                                |  6 +-
 release-please-config.json                    | 66 +++++++++++++++++++
 src/codex/_version.py                         |  2 +-
 src/codex/resources/health.py                 |  8 +--
 src/codex/resources/organizations/billing.py  |  8 +--
 .../resources/organizations/organizations.py  |  8 +--
 src/codex/resources/projects/access_keys.py   |  8 +--
 src/codex/resources/projects/knowledge.py     |  8 +--
 src/codex/resources/projects/projects.py      |  8 +--
 src/codex/resources/users/myself/api_key.py   |  8 +--
 src/codex/resources/users/myself/myself.py    |  8 +--
 .../resources/users/myself/organizations.py   |  8 +--
 src/codex/resources/users/users.py            |  8 +--
 19 files changed, 193 insertions(+), 51 deletions(-)
 create mode 100644 .github/workflows/publish-pypi.yml
 create mode 100644 .github/workflows/release-doctor.yml
 create mode 100644 .release-please-manifest.json
 create mode 100644 bin/check-release-environment
 create mode 100644 release-please-config.json

diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
new file mode 100644
index 00000000..5ed611c7
--- /dev/null
+++ b/.github/workflows/publish-pypi.yml
@@ -0,0 +1,31 @@
+# This workflow is triggered when a GitHub release is created.
+# It can also be run manually to re-publish to PyPI in case it failed for some reason.
+# You can run this workflow by navigating to https://www.github.com/cleanlab/codex-python/actions/workflows/publish-pypi.yml
+name: Publish PyPI
+on:
+  workflow_dispatch:
+
+  release:
+    types: [published]
+
+jobs:
+  publish:
+    name: publish
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.35.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Publish to PyPI
+        run: |
+          bash ./bin/publish-pypi
+        env:
+          PYPI_TOKEN: ${{ secrets.CODEX_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
new file mode 100644
index 00000000..2ce3cdc2
--- /dev/null
+++ b/.github/workflows/release-doctor.yml
@@ -0,0 +1,21 @@
+name: Release Doctor
+on:
+  pull_request:
+    branches:
+      - main
+  workflow_dispatch:
+
+jobs:
+  release_doctor:
+    name: release doctor
+    runs-on: ubuntu-latest
+    if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Check release environment
+        run: |
+          bash ./bin/check-release-environment
+        env:
+          PYPI_TOKEN: ${{ secrets.CODEX_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 00000000..c4762802
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+  ".": "0.0.1-alpha.0"
+}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7111bafc..b9fa9a13 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -63,7 +63,7 @@ If you’d like to use the repository from source, you can either install from g
 To install via git:
 
 ```sh
-$ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
+$ pip install git+ssh://git@github.com/cleanlab/codex-python.git
 ```
 
 Alternatively, you can build from source and install the wheel file:
@@ -121,7 +121,7 @@ the changes aren't made through the automated pipeline, you may want to make rel
 
 ### Publish with a GitHub workflow
 
-You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
+You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/cleanlab/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
 
 ### Publish manually
 
diff --git a/README.md b/README.md
index f84291e1..683c158c 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,8 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean
 ## Installation
 
 ```sh
-# install from this staging repo
-pip install git+ssh://git@github.com/stainless-sdks/codex-python.git
+# install from the production repo
+pip install git+ssh://git@github.com/cleanlab/codex-python.git
 ```
 
 > [!NOTE]
@@ -230,9 +230,9 @@ project = response.parse()  # get the object that `projects.create()` would have
 print(project.id)
 ```
 
-These methods return an [`APIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) object.
+These methods return an [`APIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) object.
 
-The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
+The async client returns an [`AsyncAPIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
 
 #### `.with_streaming_response`
 
@@ -340,7 +340,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con
 
 We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
 
-We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/codex-python/issues) with questions, bugs, or suggestions.
+We are keen for your feedback; please open an [issue](https://www.github.com/cleanlab/codex-python/issues) with questions, bugs, or suggestions.
 
 ### Determining the installed version
 
diff --git a/bin/check-release-environment b/bin/check-release-environment
new file mode 100644
index 00000000..a1446a75
--- /dev/null
+++ b/bin/check-release-environment
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+errors=()
+
+if [ -z "${PYPI_TOKEN}" ]; then
+  errors+=("The CODEX_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
+fi
+
+lenErrors=${#errors[@]}
+
+if [[ lenErrors -gt 0 ]]; then
+  echo -e "Found the following errors in the release environment:\n"
+
+  for error in "${errors[@]}"; do
+    echo -e "- $error\n"
+  done
+
+  exit 1
+fi
+
+echo "The environment is ready to push releases!"
diff --git a/pyproject.toml b/pyproject.toml
index 3fba9e70..248f7eaa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,8 +34,8 @@ classifiers = [
 ]
 
 [project.urls]
-Homepage = "https://github.com/stainless-sdks/codex-python"
-Repository = "https://github.com/stainless-sdks/codex-python"
+Homepage = "https://github.com/cleanlab/codex-python"
+Repository = "https://github.com/cleanlab/codex-python"
 
 
 
@@ -122,7 +122,7 @@ path = "README.md"
 [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
 # replace relative links with absolute links
 pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
-replacement = '[\1](https://github.com/stainless-sdks/codex-python/tree/main/\g<2>)'
+replacement = '[\1](https://github.com/cleanlab/codex-python/tree/main/\g<2>)'
 
 [tool.pytest.ini_options]
 testpaths = ["tests"]
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 00000000..8fd34900
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,66 @@
+{
+  "packages": {
+    ".": {}
+  },
+  "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
+  "include-v-in-tag": true,
+  "include-component-in-tag": false,
+  "versioning": "prerelease",
+  "prerelease": true,
+  "bump-minor-pre-major": true,
+  "bump-patch-for-minor-pre-major": false,
+  "pull-request-header": "Automated Release PR",
+  "pull-request-title-pattern": "release: ${version}",
+  "changelog-sections": [
+    {
+      "type": "feat",
+      "section": "Features"
+    },
+    {
+      "type": "fix",
+      "section": "Bug Fixes"
+    },
+    {
+      "type": "perf",
+      "section": "Performance Improvements"
+    },
+    {
+      "type": "revert",
+      "section": "Reverts"
+    },
+    {
+      "type": "chore",
+      "section": "Chores"
+    },
+    {
+      "type": "docs",
+      "section": "Documentation"
+    },
+    {
+      "type": "style",
+      "section": "Styles"
+    },
+    {
+      "type": "refactor",
+      "section": "Refactors"
+    },
+    {
+      "type": "test",
+      "section": "Tests",
+      "hidden": true
+    },
+    {
+      "type": "build",
+      "section": "Build System"
+    },
+    {
+      "type": "ci",
+      "section": "Continuous Integration",
+      "hidden": true
+    }
+  ],
+  "release-type": "python",
+  "extra-files": [
+    "src/codex/_version.py"
+  ]
+}
\ No newline at end of file
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 7ee46487..fd0542b8 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.0.1-alpha.0"
+__version__ = "0.0.1-alpha.0"  # x-release-please-version
diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py
index 29c374e6..b50884e0 100644
--- a/src/codex/resources/health.py
+++ b/src/codex/resources/health.py
@@ -26,7 +26,7 @@ def with_raw_response(self) -> HealthResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return HealthResourceWithRawResponse(self)
 
@@ -35,7 +35,7 @@ def with_streaming_response(self) -> HealthResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return HealthResourceWithStreamingResponse(self)
 
@@ -104,7 +104,7 @@ def with_raw_response(self) -> AsyncHealthResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncHealthResourceWithRawResponse(self)
 
@@ -113,7 +113,7 @@ def with_streaming_response(self) -> AsyncHealthResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncHealthResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py
index fb234032..9c916d83 100644
--- a/src/codex/resources/organizations/billing.py
+++ b/src/codex/resources/organizations/billing.py
@@ -27,7 +27,7 @@ def with_raw_response(self) -> BillingResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return BillingResourceWithRawResponse(self)
 
@@ -36,7 +36,7 @@ def with_streaming_response(self) -> BillingResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return BillingResourceWithStreamingResponse(self)
 
@@ -114,7 +114,7 @@ def with_raw_response(self) -> AsyncBillingResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncBillingResourceWithRawResponse(self)
 
@@ -123,7 +123,7 @@ def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncBillingResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py
index 3aad77e0..e91099e3 100644
--- a/src/codex/resources/organizations/organizations.py
+++ b/src/codex/resources/organizations/organizations.py
@@ -38,7 +38,7 @@ def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return OrganizationsResourceWithRawResponse(self)
 
@@ -47,7 +47,7 @@ def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return OrganizationsResourceWithStreamingResponse(self)
 
@@ -96,7 +96,7 @@ def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncOrganizationsResourceWithRawResponse(self)
 
@@ -105,7 +105,7 @@ def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResp
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncOrganizationsResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 2882e65b..e282ad4f 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -36,7 +36,7 @@ def with_raw_response(self) -> AccessKeysResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AccessKeysResourceWithRawResponse(self)
 
@@ -45,7 +45,7 @@ def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AccessKeysResourceWithStreamingResponse(self)
 
@@ -290,7 +290,7 @@ def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncAccessKeysResourceWithRawResponse(self)
 
@@ -299,7 +299,7 @@ def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingRespons
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncAccessKeysResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py
index 6005f9cc..9c43c669 100644
--- a/src/codex/resources/projects/knowledge.py
+++ b/src/codex/resources/projects/knowledge.py
@@ -41,7 +41,7 @@ def with_raw_response(self) -> KnowledgeResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return KnowledgeResourceWithRawResponse(self)
 
@@ -50,7 +50,7 @@ def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return KnowledgeResourceWithStreamingResponse(self)
 
@@ -339,7 +339,7 @@ def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncKnowledgeResourceWithRawResponse(self)
 
@@ -348,7 +348,7 @@ def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncKnowledgeResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 20e95366..845669a8 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -58,7 +58,7 @@ def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return ProjectsResourceWithRawResponse(self)
 
@@ -67,7 +67,7 @@ def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return ProjectsResourceWithStreamingResponse(self)
 
@@ -301,7 +301,7 @@ def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncProjectsResourceWithRawResponse(self)
 
@@ -310,7 +310,7 @@ def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncProjectsResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py
index f4108375..c2784b81 100644
--- a/src/codex/resources/users/myself/api_key.py
+++ b/src/codex/resources/users/myself/api_key.py
@@ -26,7 +26,7 @@ def with_raw_response(self) -> APIKeyResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return APIKeyResourceWithRawResponse(self)
 
@@ -35,7 +35,7 @@ def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return APIKeyResourceWithStreamingResponse(self)
 
@@ -66,7 +66,7 @@ def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncAPIKeyResourceWithRawResponse(self)
 
@@ -75,7 +75,7 @@ def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncAPIKeyResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py
index 51d0b16b..ba4548e5 100644
--- a/src/codex/resources/users/myself/myself.py
+++ b/src/codex/resources/users/myself/myself.py
@@ -50,7 +50,7 @@ def with_raw_response(self) -> MyselfResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return MyselfResourceWithRawResponse(self)
 
@@ -59,7 +59,7 @@ def with_streaming_response(self) -> MyselfResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return MyselfResourceWithStreamingResponse(self)
 
@@ -98,7 +98,7 @@ def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncMyselfResourceWithRawResponse(self)
 
@@ -107,7 +107,7 @@ def with_streaming_response(self) -> AsyncMyselfResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncMyselfResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py
index 5c37dd69..12e799aa 100644
--- a/src/codex/resources/users/myself/organizations.py
+++ b/src/codex/resources/users/myself/organizations.py
@@ -26,7 +26,7 @@ def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return OrganizationsResourceWithRawResponse(self)
 
@@ -35,7 +35,7 @@ def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return OrganizationsResourceWithStreamingResponse(self)
 
@@ -66,7 +66,7 @@ def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncOrganizationsResourceWithRawResponse(self)
 
@@ -75,7 +75,7 @@ def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResp
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncOrganizationsResourceWithStreamingResponse(self)
 
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index 4d229677..ea5019c2 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -27,7 +27,7 @@ def with_raw_response(self) -> UsersResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return UsersResourceWithRawResponse(self)
 
@@ -36,7 +36,7 @@ def with_streaming_response(self) -> UsersResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return UsersResourceWithStreamingResponse(self)
 
@@ -52,7 +52,7 @@ def with_raw_response(self) -> AsyncUsersResourceWithRawResponse:
         This property can be used as a prefix for any HTTP method call to return the
         the raw response object instead of the parsed content.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
         """
         return AsyncUsersResourceWithRawResponse(self)
 
@@ -61,7 +61,7 @@ def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse:
         """
         An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 
-        For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
         """
         return AsyncUsersResourceWithStreamingResponse(self)
 

From 7307f43fe1d203a665d914ca79c23f8ebc5a0dd4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 19:57:46 +0000
Subject: [PATCH 010/227] fix: update org info and package name (#4)

---
 README.md             |  9 +++------
 SECURITY.md           |  2 +-
 pyproject.toml        |  4 ++--
 requirements-dev.lock | 12 ++++++------
 requirements.lock     | 12 ++++++------
 5 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md
index 683c158c..634367e0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Codex Python API library
 
-[![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/)
+[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/)
 
 The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,
@@ -15,13 +15,10 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean
 ## Installation
 
 ```sh
-# install from the production repo
-pip install git+ssh://git@github.com/cleanlab/codex-python.git
+# install from PyPI
+pip install --pre codex-sdk
 ```
 
-> [!NOTE]
-> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex`
-
 ## Usage
 
 The full API of this library can be found in [api.md](api.md).
diff --git a/SECURITY.md b/SECURITY.md
index fcad344e..54f64467 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -20,7 +20,7 @@ or products provided by Codex please follow the respective company's security re
 
 ### Codex Terms and Policies
 
-Please contact support@cleanlab.ai for any questions or concerns regarding security of our services.
+Please contact team@cleanlab.ai for any questions or concerns regarding security of our services.
 
 ---
 
diff --git a/pyproject.toml b/pyproject.toml
index 248f7eaa..647b75c0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,11 @@
 [project]
-name = "codex"
+name = "codex-sdk"
 version = "0.0.1-alpha.0"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "Apache-2.0"
 authors = [
-{ name = "Codex", email = "support@cleanlab.ai" },
+{ name = "Codex", email = "team@cleanlab.ai" },
 ]
 dependencies = [
     "httpx>=0.23.0, <1",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 11bf46e6..47eb5e34 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -12,7 +12,7 @@
 annotated-types==0.6.0
     # via pydantic
 anyio==4.4.0
-    # via codex
+    # via codex-sdk
     # via httpx
 argcomplete==3.1.2
     # via nox
@@ -25,7 +25,7 @@ dirty-equals==0.6.0
 distlib==0.3.7
     # via virtualenv
 distro==1.8.0
-    # via codex
+    # via codex-sdk
 exceptiongroup==1.2.2
     # via anyio
     # via pytest
@@ -36,7 +36,7 @@ h11==0.14.0
 httpcore==1.0.2
     # via httpx
 httpx==0.28.1
-    # via codex
+    # via codex-sdk
     # via respx
 idna==3.4
     # via anyio
@@ -63,7 +63,7 @@ platformdirs==3.11.0
 pluggy==1.5.0
     # via pytest
 pydantic==2.10.3
-    # via codex
+    # via codex-sdk
 pydantic-core==2.27.1
     # via pydantic
 pygments==2.18.0
@@ -85,14 +85,14 @@ six==1.16.0
     # via python-dateutil
 sniffio==1.3.0
     # via anyio
-    # via codex
+    # via codex-sdk
 time-machine==2.9.0
 tomli==2.0.2
     # via mypy
     # via pytest
 typing-extensions==4.12.2
     # via anyio
-    # via codex
+    # via codex-sdk
     # via mypy
     # via pydantic
     # via pydantic-core
diff --git a/requirements.lock b/requirements.lock
index fb7b045f..5d7fff82 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -12,13 +12,13 @@
 annotated-types==0.6.0
     # via pydantic
 anyio==4.4.0
-    # via codex
+    # via codex-sdk
     # via httpx
 certifi==2023.7.22
     # via httpcore
     # via httpx
 distro==1.8.0
-    # via codex
+    # via codex-sdk
 exceptiongroup==1.2.2
     # via anyio
 h11==0.14.0
@@ -26,19 +26,19 @@ h11==0.14.0
 httpcore==1.0.2
     # via httpx
 httpx==0.28.1
-    # via codex
+    # via codex-sdk
 idna==3.4
     # via anyio
     # via httpx
 pydantic==2.10.3
-    # via codex
+    # via codex-sdk
 pydantic-core==2.27.1
     # via pydantic
 sniffio==1.3.0
     # via anyio
-    # via codex
+    # via codex-sdk
 typing-extensions==4.12.2
     # via anyio
-    # via codex
+    # via codex-sdk
     # via pydantic
     # via pydantic-core

From 78b8475a328666c2b85b770f96c061f433e7960a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 20:02:43 +0000
Subject: [PATCH 011/227] fix: disable tests (#6)

---
 .../organizations/test_billing.py             | 16 ++++++
 .../projects/test_access_keys.py              | 46 ++++++++++++++++
 .../api_resources/projects/test_knowledge.py  | 54 +++++++++++++++++++
 tests/api_resources/test_health.py            | 18 +++++++
 tests/api_resources/test_organizations.py     |  8 +++
 tests/api_resources/test_projects.py          | 40 ++++++++++++++
 .../users/myself/test_api_key.py              |  6 +++
 .../users/myself/test_organizations.py        |  6 +++
 tests/api_resources/users/test_myself.py      |  6 +++
 9 files changed, 200 insertions(+)

diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index ccb7dc8a..e3bb1d1d 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -17,6 +17,7 @@
 class TestBilling:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_invoices(self, client: Codex) -> None:
         billing = client.organizations.billing.invoices(
@@ -24,6 +25,7 @@ def test_method_invoices(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_invoices(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.invoices(
@@ -35,6 +37,7 @@ def test_raw_response_invoices(self, client: Codex) -> None:
         billing = response.parse()
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_invoices(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.invoices(
@@ -48,6 +51,7 @@ def test_streaming_response_invoices(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_invoices(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -55,6 +59,7 @@ def test_path_params_invoices(self, client: Codex) -> None:
                 "",
             )
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_usage(self, client: Codex) -> None:
         billing = client.organizations.billing.usage(
@@ -62,6 +67,7 @@ def test_method_usage(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_usage(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.usage(
@@ -73,6 +79,7 @@ def test_raw_response_usage(self, client: Codex) -> None:
         billing = response.parse()
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_usage(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.usage(
@@ -86,6 +93,7 @@ def test_streaming_response_usage(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_usage(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -97,6 +105,7 @@ def test_path_params_usage(self, client: Codex) -> None:
 class TestAsyncBilling:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_invoices(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.invoices(
@@ -104,6 +113,7 @@ async def test_method_invoices(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.invoices(
@@ -115,6 +125,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         billing = await response.parse()
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.invoices(
@@ -128,6 +139,7 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -135,6 +147,7 @@ async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_usage(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.usage(
@@ -142,6 +155,7 @@ async def test_method_usage(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.usage(
@@ -153,6 +167,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         billing = await response.parse()
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.usage(
@@ -166,6 +181,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_usage(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index eab961cc..cdb893e6 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -22,6 +22,7 @@
 class TestAccessKeys:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
@@ -30,6 +31,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
@@ -40,6 +42,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.create(
@@ -52,6 +55,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.create(
@@ -66,6 +70,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve(
@@ -74,6 +79,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
@@ -86,6 +92,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
@@ -100,6 +107,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
@@ -109,6 +117,7 @@ def test_method_update(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
@@ -120,6 +129,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.update(
@@ -133,6 +143,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.update(
@@ -148,6 +159,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         access_key = client.projects.access_keys.list(
@@ -155,6 +167,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.list(
@@ -166,6 +179,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.list(
@@ -179,6 +193,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         access_key = client.projects.access_keys.delete(
@@ -187,6 +202,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
@@ -199,6 +215,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         access_key = response.parse()
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
@@ -213,11 +230,13 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve_project_id(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve_project_id()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve_project_id()
@@ -227,6 +246,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
@@ -238,6 +258,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_revoke(self, client: Codex) -> None:
         access_key = client.projects.access_keys.revoke(
@@ -246,6 +267,7 @@ def test_method_revoke(self, client: Codex) -> None:
         )
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_revoke(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
@@ -258,6 +280,7 @@ def test_raw_response_revoke(self, client: Codex) -> None:
         access_key = response.parse()
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_revoke(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
@@ -276,6 +299,7 @@ def test_streaming_response_revoke(self, client: Codex) -> None:
 class TestAsyncAccessKeys:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
@@ -284,6 +308,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
@@ -294,6 +319,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
@@ -306,6 +332,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
@@ -320,6 +347,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
@@ -328,6 +356,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
@@ -340,6 +369,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
@@ -354,6 +384,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
@@ -363,6 +394,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
@@ -374,6 +406,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
@@ -387,6 +420,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
@@ -402,6 +436,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.list(
@@ -409,6 +444,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
@@ -420,6 +456,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
@@ -433,6 +470,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.delete(
@@ -441,6 +479,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
@@ -453,6 +492,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
@@ -467,11 +507,13 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve_project_id()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id()
@@ -481,6 +523,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex)
         access_key = await response.parse()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
@@ -492,6 +535,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(
@@ -500,6 +544,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         )
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
@@ -512,6 +557,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert access_key is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
index 9052b62d..ac538743 100644
--- a/tests/api_resources/projects/test_knowledge.py
+++ b/tests/api_resources/projects/test_knowledge.py
@@ -20,6 +20,7 @@
 class TestKnowledge:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.create(
@@ -28,6 +29,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.create(
@@ -37,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.create(
@@ -49,6 +52,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.create(
@@ -63,6 +67,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.retrieve(
@@ -71,6 +76,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.retrieve(
@@ -83,6 +89,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.retrieve(
@@ -97,6 +104,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -105,6 +113,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.update(
@@ -113,6 +122,7 @@ def test_method_update(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.update(
@@ -123,6 +133,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.update(
@@ -135,6 +146,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.update(
@@ -149,6 +161,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_update(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -157,6 +170,7 @@ def test_path_params_update(self, client: Codex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.list(
@@ -164,6 +178,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.list(
@@ -177,6 +192,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.list(
@@ -188,6 +204,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.list(
@@ -201,6 +218,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.delete(
@@ -209,6 +227,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert knowledge is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.delete(
@@ -221,6 +240,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         knowledge = response.parse()
         assert knowledge is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.delete(
@@ -235,6 +255,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -243,6 +264,7 @@ def test_path_params_delete(self, client: Codex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_add_question(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.add_question(
@@ -251,6 +273,7 @@ def test_method_add_question(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_add_question(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.add_question(
@@ -263,6 +286,7 @@ def test_raw_response_add_question(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_add_question(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.add_question(
@@ -277,6 +301,7 @@ def test_streaming_response_add_question(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_query(self, client: Codex) -> None:
         knowledge = client.projects.knowledge.query(
@@ -285,6 +310,7 @@ def test_method_query(self, client: Codex) -> None:
         )
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_query(self, client: Codex) -> None:
         response = client.projects.knowledge.with_raw_response.query(
@@ -297,6 +323,7 @@ def test_raw_response_query(self, client: Codex) -> None:
         knowledge = response.parse()
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_query(self, client: Codex) -> None:
         with client.projects.knowledge.with_streaming_response.query(
@@ -315,6 +342,7 @@ def test_streaming_response_query(self, client: Codex) -> None:
 class TestAsyncKnowledge:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
@@ -323,6 +351,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.create(
@@ -332,6 +361,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.create(
@@ -344,6 +374,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.create(
@@ -358,6 +389,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.retrieve(
@@ -366,6 +398,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.retrieve(
@@ -378,6 +411,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.retrieve(
@@ -392,6 +426,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -400,6 +435,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
@@ -408,6 +444,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.update(
@@ -418,6 +455,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.update(
@@ -430,6 +468,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.update(
@@ -444,6 +483,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_update(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -452,6 +492,7 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
@@ -459,6 +500,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.list(
@@ -472,6 +514,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.list(
@@ -483,6 +526,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.list(
@@ -496,6 +540,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.delete(
@@ -504,6 +549,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert knowledge is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.delete(
@@ -516,6 +562,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert knowledge is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.delete(
@@ -530,6 +577,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
@@ -538,6 +586,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 project_id=0,
             )
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.add_question(
@@ -546,6 +595,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.add_question(
@@ -558,6 +608,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None
         knowledge = await response.parse()
         assert_matches_type(Entry, knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.add_question(
@@ -572,6 +623,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) -
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_query(self, async_client: AsyncCodex) -> None:
         knowledge = await async_client.projects.knowledge.query(
@@ -580,6 +632,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.knowledge.with_raw_response.query(
@@ -592,6 +645,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         knowledge = await response.parse()
         assert_matches_type(Optional[Entry], knowledge, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.knowledge.with_streaming_response.query(
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index be4729ab..09700120 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -17,11 +17,13 @@
 class TestHealth:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_check(self, client: Codex) -> None:
         health = client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_check(self, client: Codex) -> None:
         response = client.health.with_raw_response.check()
@@ -31,6 +33,7 @@ def test_raw_response_check(self, client: Codex) -> None:
         health = response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_check(self, client: Codex) -> None:
         with client.health.with_streaming_response.check() as response:
@@ -42,11 +45,13 @@ def test_streaming_response_check(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_db(self, client: Codex) -> None:
         health = client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_db(self, client: Codex) -> None:
         response = client.health.with_raw_response.db()
@@ -56,6 +61,7 @@ def test_raw_response_db(self, client: Codex) -> None:
         health = response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_db(self, client: Codex) -> None:
         with client.health.with_streaming_response.db() as response:
@@ -67,11 +73,13 @@ def test_streaming_response_db(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_weaviate(self, client: Codex) -> None:
         health = client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_weaviate(self, client: Codex) -> None:
         response = client.health.with_raw_response.weaviate()
@@ -81,6 +89,7 @@ def test_raw_response_weaviate(self, client: Codex) -> None:
         health = response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_weaviate(self, client: Codex) -> None:
         with client.health.with_streaming_response.weaviate() as response:
@@ -96,11 +105,13 @@ def test_streaming_response_weaviate(self, client: Codex) -> None:
 class TestAsyncHealth:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_check(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.check()
@@ -110,6 +121,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         health = await response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.check() as response:
@@ -121,11 +133,13 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_db(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.db()
@@ -135,6 +149,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         health = await response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.db() as response:
@@ -146,11 +161,13 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_weaviate(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.weaviate()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.weaviate()
@@ -160,6 +177,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
         health = await response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.weaviate() as response:
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index 2d627c70..1a13f77a 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -17,6 +17,7 @@
 class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         organization = client.organizations.retrieve(
@@ -24,6 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.organizations.with_raw_response.retrieve(
@@ -35,6 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.organizations.with_streaming_response.retrieve(
@@ -48,6 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -59,6 +63,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
 class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         organization = await async_client.organizations.retrieve(
@@ -66,6 +71,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.with_raw_response.retrieve(
@@ -77,6 +83,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         organization = await response.parse()
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.with_streaming_response.retrieve(
@@ -90,6 +97,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 7e79d8e8..388601d2 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -20,6 +20,7 @@
 class TestProjects:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         project = client.projects.create(
@@ -29,6 +30,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         project = client.projects.create(
@@ -39,6 +41,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.with_raw_response.create(
@@ -52,6 +55,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.with_streaming_response.create(
@@ -67,6 +71,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         project = client.projects.retrieve(
@@ -74,6 +79,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.with_raw_response.retrieve(
@@ -85,6 +91,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.with_streaming_response.retrieve(
@@ -98,6 +105,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         project = client.projects.update(
@@ -107,6 +115,7 @@ def test_method_update(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
@@ -117,6 +126,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.with_raw_response.update(
@@ -130,6 +140,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.with_streaming_response.update(
@@ -145,6 +156,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         project = client.projects.list(
@@ -152,6 +164,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.with_raw_response.list(
@@ -163,6 +176,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.with_streaming_response.list(
@@ -176,6 +190,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         project = client.projects.delete(
@@ -183,6 +198,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert project is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.with_raw_response.delete(
@@ -194,6 +210,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         project = response.parse()
         assert project is None
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.with_streaming_response.delete(
@@ -207,6 +224,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_export(self, client: Codex) -> None:
         project = client.projects.export(
@@ -214,6 +232,7 @@ def test_method_export(self, client: Codex) -> None:
         )
         assert_matches_type(object, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_export(self, client: Codex) -> None:
         response = client.projects.with_raw_response.export(
@@ -225,6 +244,7 @@ def test_raw_response_export(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(object, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_export(self, client: Codex) -> None:
         with client.projects.with_streaming_response.export(
@@ -242,6 +262,7 @@ def test_streaming_response_export(self, client: Codex) -> None:
 class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
@@ -251,6 +272,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
@@ -261,6 +283,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.create(
@@ -274,6 +297,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.create(
@@ -289,6 +313,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve(
@@ -296,6 +321,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
@@ -307,6 +333,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
@@ -320,6 +347,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
@@ -329,6 +357,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
@@ -339,6 +368,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
@@ -352,6 +382,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
@@ -367,6 +398,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
@@ -374,6 +406,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.list(
@@ -385,6 +418,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.list(
@@ -398,6 +432,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.delete(
@@ -405,6 +440,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert project is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.delete(
@@ -416,6 +452,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert project is None
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.delete(
@@ -429,6 +466,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_export(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.export(
@@ -436,6 +474,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(object, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.export(
@@ -447,6 +486,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(object, project, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.export(
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index 55bba35f..68996997 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -17,11 +17,13 @@
 class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_refresh(self, client: Codex) -> None:
         api_key = client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_refresh(self, client: Codex) -> None:
         response = client.users.myself.api_key.with_raw_response.refresh()
@@ -31,6 +33,7 @@ def test_raw_response_refresh(self, client: Codex) -> None:
         api_key = response.parse()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_refresh(self, client: Codex) -> None:
         with client.users.myself.api_key.with_streaming_response.refresh() as response:
@@ -46,11 +49,13 @@ def test_streaming_response_refresh(self, client: Codex) -> None:
 class TestAsyncAPIKey:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_refresh(self, async_client: AsyncCodex) -> None:
         api_key = await async_client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.refresh()
@@ -60,6 +65,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         api_key = await response.parse()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index da485882..9f35f7c6 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -17,11 +17,13 @@
 class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         organization = client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.users.myself.organizations.with_raw_response.list()
@@ -31,6 +33,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.users.myself.organizations.with_streaming_response.list() as response:
@@ -46,11 +49,13 @@ def test_streaming_response_list(self, client: Codex) -> None:
 class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         organization = await async_client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.organizations.with_raw_response.list()
@@ -60,6 +65,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         organization = await response.parse()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.organizations.with_streaming_response.list() as response:
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index a206d1f9..63123275 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -17,11 +17,13 @@
 class TestMyself:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         myself = client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.users.myself.with_raw_response.retrieve()
@@ -31,6 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         myself = response.parse()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.users.myself.with_streaming_response.retrieve() as response:
@@ -46,11 +49,13 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 class TestAsyncMyself:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await async_client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.with_raw_response.retrieve()
@@ -60,6 +65,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await response.parse()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
+    @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.with_streaming_response.retrieve() as response:

From caeb663e9711aab43d308968a4df56da30d8a880 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:12:15 +0000
Subject: [PATCH 012/227] feat(api): api update (#7)

---
 .stats.yml                                    |   4 +-
 api.md                                        |  12 +-
 src/codex/resources/projects/__init__.py      |  14 -
 src/codex/resources/projects/knowledge.py     | 740 ------------------
 src/codex/resources/projects/projects.py      |  32 -
 src/codex/types/projects/__init__.py          |   7 -
 src/codex/types/projects/entry.py             |  20 -
 .../projects/knowledge_add_question_params.py |  11 -
 .../types/projects/knowledge_create_params.py |  14 -
 .../types/projects/knowledge_list_params.py   |  21 -
 .../types/projects/knowledge_query_params.py  |  11 -
 .../types/projects/knowledge_update_params.py |  16 -
 .../types/projects/list_knowledge_response.py |  14 -
 .../api_resources/projects/test_knowledge.py  | 661 ----------------
 14 files changed, 3 insertions(+), 1574 deletions(-)
 delete mode 100644 src/codex/resources/projects/knowledge.py
 delete mode 100644 src/codex/types/projects/entry.py
 delete mode 100644 src/codex/types/projects/knowledge_add_question_params.py
 delete mode 100644 src/codex/types/projects/knowledge_create_params.py
 delete mode 100644 src/codex/types/projects/knowledge_list_params.py
 delete mode 100644 src/codex/types/projects/knowledge_query_params.py
 delete mode 100644 src/codex/types/projects/knowledge_update_params.py
 delete mode 100644 src/codex/types/projects/list_knowledge_response.py
 delete mode 100644 tests/api_resources/projects/test_knowledge.py

diff --git a/.stats.yml b/.stats.yml
index a156c044..f56fed6e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 29
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml
+configured_endpoints: 22
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml
diff --git a/api.md b/api.md
index 672aeeb6..ff5b8fd3 100644
--- a/api.md
+++ b/api.md
@@ -116,15 +116,5 @@ Methods:
 Types:
 
 ```python
-from codex.types.projects import Entry, ListKnowledgeResponse
+from codex.types.projects import Entry
 ```
-
-Methods:
-
-- <code title="post /api/projects/{project_id}/knowledge/">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="get /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="put /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/knowledge_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="get /api/projects/{project_id}/knowledge/">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_list_params.py">params</a>) -> <a href="./src/codex/types/projects/list_knowledge_response.py">ListKnowledgeResponse</a></code>
-- <code title="delete /api/projects/{project_id}/knowledge/{entry_id}">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">delete</a>(entry_id, \*, project_id) -> None</code>
-- <code title="post /api/projects/{project_id}/knowledge/add_question">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="post /api/projects/{project_id}/knowledge/query">client.projects.knowledge.<a href="./src/codex/resources/projects/knowledge.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/knowledge_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
index 5250b0b3..9c31620c 100644
--- a/src/codex/resources/projects/__init__.py
+++ b/src/codex/resources/projects/__init__.py
@@ -8,14 +8,6 @@
     ProjectsResourceWithStreamingResponse,
     AsyncProjectsResourceWithStreamingResponse,
 )
-from .knowledge import (
-    KnowledgeResource,
-    AsyncKnowledgeResource,
-    KnowledgeResourceWithRawResponse,
-    AsyncKnowledgeResourceWithRawResponse,
-    KnowledgeResourceWithStreamingResponse,
-    AsyncKnowledgeResourceWithStreamingResponse,
-)
 from .access_keys import (
     AccessKeysResource,
     AsyncAccessKeysResource,
@@ -32,12 +24,6 @@
     "AsyncAccessKeysResourceWithRawResponse",
     "AccessKeysResourceWithStreamingResponse",
     "AsyncAccessKeysResourceWithStreamingResponse",
-    "KnowledgeResource",
-    "AsyncKnowledgeResource",
-    "KnowledgeResourceWithRawResponse",
-    "AsyncKnowledgeResourceWithRawResponse",
-    "KnowledgeResourceWithStreamingResponse",
-    "AsyncKnowledgeResourceWithStreamingResponse",
     "ProjectsResource",
     "AsyncProjectsResource",
     "ProjectsResourceWithRawResponse",
diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py
deleted file mode 100644
index 9c43c669..00000000
--- a/src/codex/resources/projects/knowledge.py
+++ /dev/null
@@ -1,740 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Literal
-
-import httpx
-
-from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import (
-    maybe_transform,
-    async_maybe_transform,
-)
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
-    to_raw_response_wrapper,
-    to_streamed_response_wrapper,
-    async_to_raw_response_wrapper,
-    async_to_streamed_response_wrapper,
-)
-from ..._base_client import make_request_options
-from ...types.projects import (
-    knowledge_list_params,
-    knowledge_query_params,
-    knowledge_create_params,
-    knowledge_update_params,
-    knowledge_add_question_params,
-)
-from ...types.projects.entry import Entry
-from ...types.projects.list_knowledge_response import ListKnowledgeResponse
-
-__all__ = ["KnowledgeResource", "AsyncKnowledgeResource"]
-
-
-class KnowledgeResource(SyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> KnowledgeResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return the
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return KnowledgeResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return KnowledgeResourceWithStreamingResponse(self)
-
-    def create(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Create a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return self._post(
-            f"/api/projects/{project_id}/knowledge/",
-            body=maybe_transform(
-                {
-                    "question": question,
-                    "answer": answer,
-                },
-                knowledge_create_params.KnowledgeCreateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def retrieve(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Get a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._get(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def update(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        question: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Update a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._put(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            body=maybe_transform(
-                {
-                    "answer": answer,
-                    "question": question,
-                },
-                knowledge_update_params.KnowledgeUpdateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def list(
-        self,
-        project_id: int,
-        *,
-        answered_only: bool | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
-        unanswered_only: bool | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ListKnowledgeResponse:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return self._get(
-            f"/api/projects/{project_id}/knowledge/",
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform(
-                    {
-                        "answered_only": answered_only,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "unanswered_only": unanswered_only,
-                    },
-                    knowledge_list_params.KnowledgeListParams,
-                ),
-            ),
-            cast_to=ListKnowledgeResponse,
-        )
-
-    def delete(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> None:
-        """
-        Delete a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
-        return self._delete(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=NoneType,
-        )
-
-    def add_question(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Add a question to a project.
-
-        Returns: 201 Created if a new question was added 200 OK if the question already
-        existed
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return self._post(
-            f"/api/projects/{project_id}/knowledge/add_question",
-            body=maybe_transform({"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def query(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Optional[Entry]:
-        """
-        Query knowledge for a project.
-
-        Returns the matching entry if found and answered, otherwise returns None. This
-        allows the client to distinguish between: (1) no matching question found
-        (returns None), and (2) matching question found but not yet answered (returns
-        Entry with answer=None).
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return self._post(
-            f"/api/projects/{project_id}/knowledge/query",
-            body=maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-
-class AsyncKnowledgeResource(AsyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return the
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return AsyncKnowledgeResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return AsyncKnowledgeResourceWithStreamingResponse(self)
-
-    async def create(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Create a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return await self._post(
-            f"/api/projects/{project_id}/knowledge/",
-            body=await async_maybe_transform(
-                {
-                    "question": question,
-                    "answer": answer,
-                },
-                knowledge_create_params.KnowledgeCreateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def retrieve(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Get a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._get(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def update(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        question: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Update a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._put(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            body=await async_maybe_transform(
-                {
-                    "answer": answer,
-                    "question": question,
-                },
-                knowledge_update_params.KnowledgeUpdateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def list(
-        self,
-        project_id: int,
-        *,
-        answered_only: bool | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
-        unanswered_only: bool | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ListKnowledgeResponse:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return await self._get(
-            f"/api/projects/{project_id}/knowledge/",
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=await async_maybe_transform(
-                    {
-                        "answered_only": answered_only,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "unanswered_only": unanswered_only,
-                    },
-                    knowledge_list_params.KnowledgeListParams,
-                ),
-            ),
-            cast_to=ListKnowledgeResponse,
-        )
-
-    async def delete(
-        self,
-        entry_id: str,
-        *,
-        project_id: int,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> None:
-        """
-        Delete a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
-        return await self._delete(
-            f"/api/projects/{project_id}/knowledge/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=NoneType,
-        )
-
-    async def add_question(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Add a question to a project.
-
-        Returns: 201 Created if a new question was added 200 OK if the question already
-        existed
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return await self._post(
-            f"/api/projects/{project_id}/knowledge/add_question",
-            body=await async_maybe_transform(
-                {"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def query(
-        self,
-        project_id: int,
-        *,
-        question: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Optional[Entry]:
-        """
-        Query knowledge for a project.
-
-        Returns the matching entry if found and answered, otherwise returns None. This
-        allows the client to distinguish between: (1) no matching question found
-        (returns None), and (2) matching question found but not yet answered (returns
-        Entry with answer=None).
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        return await self._post(
-            f"/api/projects/{project_id}/knowledge/query",
-            body=await async_maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-
-class KnowledgeResourceWithRawResponse:
-    def __init__(self, knowledge: KnowledgeResource) -> None:
-        self._knowledge = knowledge
-
-        self.create = to_raw_response_wrapper(
-            knowledge.create,
-        )
-        self.retrieve = to_raw_response_wrapper(
-            knowledge.retrieve,
-        )
-        self.update = to_raw_response_wrapper(
-            knowledge.update,
-        )
-        self.list = to_raw_response_wrapper(
-            knowledge.list,
-        )
-        self.delete = to_raw_response_wrapper(
-            knowledge.delete,
-        )
-        self.add_question = to_raw_response_wrapper(
-            knowledge.add_question,
-        )
-        self.query = to_raw_response_wrapper(
-            knowledge.query,
-        )
-
-
-class AsyncKnowledgeResourceWithRawResponse:
-    def __init__(self, knowledge: AsyncKnowledgeResource) -> None:
-        self._knowledge = knowledge
-
-        self.create = async_to_raw_response_wrapper(
-            knowledge.create,
-        )
-        self.retrieve = async_to_raw_response_wrapper(
-            knowledge.retrieve,
-        )
-        self.update = async_to_raw_response_wrapper(
-            knowledge.update,
-        )
-        self.list = async_to_raw_response_wrapper(
-            knowledge.list,
-        )
-        self.delete = async_to_raw_response_wrapper(
-            knowledge.delete,
-        )
-        self.add_question = async_to_raw_response_wrapper(
-            knowledge.add_question,
-        )
-        self.query = async_to_raw_response_wrapper(
-            knowledge.query,
-        )
-
-
-class KnowledgeResourceWithStreamingResponse:
-    def __init__(self, knowledge: KnowledgeResource) -> None:
-        self._knowledge = knowledge
-
-        self.create = to_streamed_response_wrapper(
-            knowledge.create,
-        )
-        self.retrieve = to_streamed_response_wrapper(
-            knowledge.retrieve,
-        )
-        self.update = to_streamed_response_wrapper(
-            knowledge.update,
-        )
-        self.list = to_streamed_response_wrapper(
-            knowledge.list,
-        )
-        self.delete = to_streamed_response_wrapper(
-            knowledge.delete,
-        )
-        self.add_question = to_streamed_response_wrapper(
-            knowledge.add_question,
-        )
-        self.query = to_streamed_response_wrapper(
-            knowledge.query,
-        )
-
-
-class AsyncKnowledgeResourceWithStreamingResponse:
-    def __init__(self, knowledge: AsyncKnowledgeResource) -> None:
-        self._knowledge = knowledge
-
-        self.create = async_to_streamed_response_wrapper(
-            knowledge.create,
-        )
-        self.retrieve = async_to_streamed_response_wrapper(
-            knowledge.retrieve,
-        )
-        self.update = async_to_streamed_response_wrapper(
-            knowledge.update,
-        )
-        self.list = async_to_streamed_response_wrapper(
-            knowledge.list,
-        )
-        self.delete = async_to_streamed_response_wrapper(
-            knowledge.delete,
-        )
-        self.add_question = async_to_streamed_response_wrapper(
-            knowledge.add_question,
-        )
-        self.query = async_to_streamed_response_wrapper(
-            knowledge.query,
-        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 845669a8..a7bea419 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -13,14 +13,6 @@
     async_maybe_transform,
 )
 from ..._compat import cached_property
-from .knowledge import (
-    KnowledgeResource,
-    AsyncKnowledgeResource,
-    KnowledgeResourceWithRawResponse,
-    AsyncKnowledgeResourceWithRawResponse,
-    KnowledgeResourceWithStreamingResponse,
-    AsyncKnowledgeResourceWithStreamingResponse,
-)
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (
     to_raw_response_wrapper,
@@ -48,10 +40,6 @@ class ProjectsResource(SyncAPIResource):
     def access_keys(self) -> AccessKeysResource:
         return AccessKeysResource(self._client)
 
-    @cached_property
-    def knowledge(self) -> KnowledgeResource:
-        return KnowledgeResource(self._client)
-
     @cached_property
     def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         """
@@ -291,10 +279,6 @@ class AsyncProjectsResource(AsyncAPIResource):
     def access_keys(self) -> AsyncAccessKeysResource:
         return AsyncAccessKeysResource(self._client)
 
-    @cached_property
-    def knowledge(self) -> AsyncKnowledgeResource:
-        return AsyncKnowledgeResource(self._client)
-
     @cached_property
     def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         """
@@ -558,10 +542,6 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
         return AccessKeysResourceWithRawResponse(self._projects.access_keys)
 
-    @cached_property
-    def knowledge(self) -> KnowledgeResourceWithRawResponse:
-        return KnowledgeResourceWithRawResponse(self._projects.knowledge)
-
 
 class AsyncProjectsResourceWithRawResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -590,10 +570,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
         return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys)
 
-    @cached_property
-    def knowledge(self) -> AsyncKnowledgeResourceWithRawResponse:
-        return AsyncKnowledgeResourceWithRawResponse(self._projects.knowledge)
-
 
 class ProjectsResourceWithStreamingResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -622,10 +598,6 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
         return AccessKeysResourceWithStreamingResponse(self._projects.access_keys)
 
-    @cached_property
-    def knowledge(self) -> KnowledgeResourceWithStreamingResponse:
-        return KnowledgeResourceWithStreamingResponse(self._projects.knowledge)
-
 
 class AsyncProjectsResourceWithStreamingResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -653,7 +625,3 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
         return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys)
-
-    @cached_property
-    def knowledge(self) -> AsyncKnowledgeResourceWithStreamingResponse:
-        return AsyncKnowledgeResourceWithStreamingResponse(self._projects.knowledge)
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 9813f780..7aadc5f0 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -2,17 +2,10 @@
 
 from __future__ import annotations
 
-from .entry import Entry as Entry
 from .access_key_schema import AccessKeySchema as AccessKeySchema
-from .knowledge_list_params import KnowledgeListParams as KnowledgeListParams
-from .knowledge_query_params import KnowledgeQueryParams as KnowledgeQueryParams
-from .knowledge_create_params import KnowledgeCreateParams as KnowledgeCreateParams
-from .knowledge_update_params import KnowledgeUpdateParams as KnowledgeUpdateParams
-from .list_knowledge_response import ListKnowledgeResponse as ListKnowledgeResponse
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
-from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams
 from .access_key_retrieve_project_id_response import (
     AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
 )
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
deleted file mode 100644
index bfa7d278..00000000
--- a/src/codex/types/projects/entry.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-from datetime import datetime
-
-from ..._models import BaseModel
-
-__all__ = ["Entry"]
-
-
-class Entry(BaseModel):
-    id: str
-
-    created_at: datetime
-
-    question: str
-
-    answer: Optional[str] = None
-
-    answered_at: Optional[datetime] = None
diff --git a/src/codex/types/projects/knowledge_add_question_params.py b/src/codex/types/projects/knowledge_add_question_params.py
deleted file mode 100644
index 97115554..00000000
--- a/src/codex/types/projects/knowledge_add_question_params.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, TypedDict
-
-__all__ = ["KnowledgeAddQuestionParams"]
-
-
-class KnowledgeAddQuestionParams(TypedDict, total=False):
-    question: Required[str]
diff --git a/src/codex/types/projects/knowledge_create_params.py b/src/codex/types/projects/knowledge_create_params.py
deleted file mode 100644
index 9c544e6e..00000000
--- a/src/codex/types/projects/knowledge_create_params.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-__all__ = ["KnowledgeCreateParams"]
-
-
-class KnowledgeCreateParams(TypedDict, total=False):
-    question: Required[str]
-
-    answer: Optional[str]
diff --git a/src/codex/types/projects/knowledge_list_params.py b/src/codex/types/projects/knowledge_list_params.py
deleted file mode 100644
index 45aee13e..00000000
--- a/src/codex/types/projects/knowledge_list_params.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Literal, TypedDict
-
-__all__ = ["KnowledgeListParams"]
-
-
-class KnowledgeListParams(TypedDict, total=False):
-    answered_only: bool
-
-    limit: int
-
-    offset: int
-
-    order: Literal["asc", "desc"]
-
-    sort: Literal["created_at", "answered_at"]
-
-    unanswered_only: bool
diff --git a/src/codex/types/projects/knowledge_query_params.py b/src/codex/types/projects/knowledge_query_params.py
deleted file mode 100644
index 2c8097af..00000000
--- a/src/codex/types/projects/knowledge_query_params.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, TypedDict
-
-__all__ = ["KnowledgeQueryParams"]
-
-
-class KnowledgeQueryParams(TypedDict, total=False):
-    question: Required[str]
diff --git a/src/codex/types/projects/knowledge_update_params.py b/src/codex/types/projects/knowledge_update_params.py
deleted file mode 100644
index b5e22269..00000000
--- a/src/codex/types/projects/knowledge_update_params.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-__all__ = ["KnowledgeUpdateParams"]
-
-
-class KnowledgeUpdateParams(TypedDict, total=False):
-    project_id: Required[int]
-
-    answer: Optional[str]
-
-    question: Optional[str]
diff --git a/src/codex/types/projects/list_knowledge_response.py b/src/codex/types/projects/list_knowledge_response.py
deleted file mode 100644
index d32f4e38..00000000
--- a/src/codex/types/projects/list_knowledge_response.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .entry import Entry
-from ..._models import BaseModel
-
-__all__ = ["ListKnowledgeResponse"]
-
-
-class ListKnowledgeResponse(BaseModel):
-    entries: List[Entry]
-
-    total_count: int
diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py
deleted file mode 100644
index ac538743..00000000
--- a/tests/api_resources/projects/test_knowledge.py
+++ /dev/null
@@ -1,661 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, Optional, cast
-
-import pytest
-
-from codex import Codex, AsyncCodex
-from tests.utils import assert_matches_type
-from codex.types.projects import (
-    Entry,
-    ListKnowledgeResponse,
-)
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestKnowledge:
-    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_create(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.create(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_create_with_all_params(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.create(
-            project_id=0,
-            question="question",
-            answer="answer",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_create(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.create(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_create(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.create(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_retrieve(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.knowledge.with_raw_response.retrieve(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_update(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_update_with_all_params(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-            answer="answer",
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_update(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_update(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_update(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.knowledge.with_raw_response.update(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.list(
-            project_id=0,
-        )
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list_with_all_params(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.list(
-            project_id=0,
-            answered_only=True,
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            unanswered_only=True,
-        )
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.list(
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.list(
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_delete(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert knowledge is None
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_delete(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert knowledge is None
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_delete(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert knowledge is None
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_delete(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.knowledge.with_raw_response.delete(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_add_question(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.add_question(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_add_question(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.add_question(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_add_question(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.add_question(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_query(self, client: Codex) -> None:
-        knowledge = client.projects.knowledge.query(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_query(self, client: Codex) -> None:
-        response = client.projects.knowledge.with_raw_response.query(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = response.parse()
-        assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_query(self, client: Codex) -> None:
-        with client.projects.knowledge.with_streaming_response.query(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = response.parse()
-            assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncKnowledge:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_create(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.create(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.create(
-            project_id=0,
-            question="question",
-            answer="answer",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.create(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.create(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.knowledge.with_raw_response.retrieve(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_update(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-            answer="answer",
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.knowledge.with_raw_response.update(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.list(
-            project_id=0,
-        )
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.list(
-            project_id=0,
-            answered_only=True,
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            unanswered_only=True,
-        )
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.list(
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.list(
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_delete(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-        assert knowledge is None
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert knowledge is None
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert knowledge is None
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.knowledge.with_raw_response.delete(
-                entry_id="",
-                project_id=0,
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.add_question(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.add_question(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(Entry, knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.add_question(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(Entry, knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_query(self, async_client: AsyncCodex) -> None:
-        knowledge = await async_client.projects.knowledge.query(
-            project_id=0,
-            question="question",
-        )
-        assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.knowledge.with_raw_response.query(
-            project_id=0,
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        knowledge = await response.parse()
-        assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.knowledge.with_streaming_response.query(
-            project_id=0,
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            knowledge = await response.parse()
-            assert_matches_type(Optional[Entry], knowledge, path=["response"])
-
-        assert cast(Any, response.is_closed) is True

From c61fcce3f6e67b8032b58d62b4513d33628093af Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:14:04 +0000
Subject: [PATCH 013/227] feat: update openapi spec (#8)

---
 .stats.yml                                    |   2 +-
 api.md                                        |  14 +-
 src/codex/resources/projects/__init__.py      |  14 +
 src/codex/resources/projects/entries.py       | 738 ++++++++++++++++++
 src/codex/resources/projects/projects.py      |  32 +
 src/codex/types/projects/__init__.py          |   7 +
 src/codex/types/projects/entry.py             |  20 +
 .../projects/entry_add_question_params.py     |  11 +
 .../types/projects/entry_create_params.py     |  14 +
 src/codex/types/projects/entry_list_params.py |  21 +
 .../types/projects/entry_list_response.py     |  14 +
 .../types/projects/entry_query_params.py      |  11 +
 .../types/projects/entry_update_params.py     |  16 +
 tests/api_resources/projects/test_entries.py  | 661 ++++++++++++++++
 14 files changed, 1572 insertions(+), 3 deletions(-)
 create mode 100644 src/codex/resources/projects/entries.py
 create mode 100644 src/codex/types/projects/entry.py
 create mode 100644 src/codex/types/projects/entry_add_question_params.py
 create mode 100644 src/codex/types/projects/entry_create_params.py
 create mode 100644 src/codex/types/projects/entry_list_params.py
 create mode 100644 src/codex/types/projects/entry_list_response.py
 create mode 100644 src/codex/types/projects/entry_query_params.py
 create mode 100644 src/codex/types/projects/entry_update_params.py
 create mode 100644 tests/api_resources/projects/test_entries.py

diff --git a/.stats.yml b/.stats.yml
index f56fed6e..66033eb6 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 22
+configured_endpoints: 29
 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml
diff --git a/api.md b/api.md
index ff5b8fd3..3665b5ba 100644
--- a/api.md
+++ b/api.md
@@ -111,10 +111,20 @@ Methods:
 - <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> <a href="./src/codex/types/projects/access_key_retrieve_project_id_response.py">AccessKeyRetrieveProjectIDResponse</a></code>
 - <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
 
-## Knowledge
+## Entries
 
 Types:
 
 ```python
-from codex.types.projects import Entry
+from codex.types.projects import Entry, EntryListResponse
 ```
+
+Methods:
+
+- <code title="post /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/entry_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="get /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="put /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="get /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/entry_list_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_list_response.py">EntryListResponse</a></code>
+- <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
+- <code title="post /api/projects/{project_id}/entries/add_question">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/entry_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
+- <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
index 9c31620c..01599858 100644
--- a/src/codex/resources/projects/__init__.py
+++ b/src/codex/resources/projects/__init__.py
@@ -1,5 +1,13 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
+from .entries import (
+    EntriesResource,
+    AsyncEntriesResource,
+    EntriesResourceWithRawResponse,
+    AsyncEntriesResourceWithRawResponse,
+    EntriesResourceWithStreamingResponse,
+    AsyncEntriesResourceWithStreamingResponse,
+)
 from .projects import (
     ProjectsResource,
     AsyncProjectsResource,
@@ -24,6 +32,12 @@
     "AsyncAccessKeysResourceWithRawResponse",
     "AccessKeysResourceWithStreamingResponse",
     "AsyncAccessKeysResourceWithStreamingResponse",
+    "EntriesResource",
+    "AsyncEntriesResource",
+    "EntriesResourceWithRawResponse",
+    "AsyncEntriesResourceWithRawResponse",
+    "EntriesResourceWithStreamingResponse",
+    "AsyncEntriesResourceWithStreamingResponse",
     "ProjectsResource",
     "AsyncProjectsResource",
     "ProjectsResourceWithRawResponse",
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
new file mode 100644
index 00000000..6121842e
--- /dev/null
+++ b/src/codex/resources/projects/entries.py
@@ -0,0 +1,738 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
+from ..._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import (
+    entry_list_params,
+    entry_query_params,
+    entry_create_params,
+    entry_update_params,
+    entry_add_question_params,
+)
+from ...types.projects.entry import Entry
+from ...types.projects.entry_list_response import EntryListResponse
+
+__all__ = ["EntriesResource", "AsyncEntriesResource"]
+
+
+class EntriesResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> EntriesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return EntriesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EntriesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return EntriesResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Create a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/entries/",
+            body=maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                },
+                entry_create_params.EntryCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def retrieve(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Get a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def update(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        question: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Update a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            body=maybe_transform(
+                {
+                    "answer": answer,
+                    "question": question,
+                },
+                entry_update_params.EntryUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def list(
+        self,
+        project_id: int,
+        *,
+        answered_only: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        unanswered_only: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EntryListResponse:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._get(
+            f"/api/projects/{project_id}/entries/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "answered_only": answered_only,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "unanswered_only": unanswered_only,
+                    },
+                    entry_list_params.EntryListParams,
+                ),
+            ),
+            cast_to=EntryListResponse,
+        )
+
+    def delete(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._delete(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    def add_question(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Add a question to a project.
+
+        Returns: 201 Created if a new question was added 200 OK if the question already
+        existed
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/entries/add_question",
+            body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    def query(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[Entry]:
+        """
+        Query knowledge for a project.
+
+        Returns the matching entry if found and answered, otherwise returns None. This
+        allows the client to distinguish between: (1) no matching question found
+        (returns None), and (2) matching question found but not yet answered (returns
+        Entry with answer=None).
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            f"/api/projects/{project_id}/entries/query",
+            body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+
+class AsyncEntriesResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return the
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEntriesResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncEntriesResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Create a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/entries/",
+            body=await async_maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                },
+                entry_create_params.EntryCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def retrieve(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Get a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def update(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        question: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Update a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            body=await async_maybe_transform(
+                {
+                    "answer": answer,
+                    "question": question,
+                },
+                entry_update_params.EntryUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def list(
+        self,
+        project_id: int,
+        *,
+        answered_only: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        unanswered_only: bool | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EntryListResponse:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._get(
+            f"/api/projects/{project_id}/entries/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "answered_only": answered_only,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "unanswered_only": unanswered_only,
+                    },
+                    entry_list_params.EntryListParams,
+                ),
+            ),
+            cast_to=EntryListResponse,
+        )
+
+    async def delete(
+        self,
+        entry_id: str,
+        *,
+        project_id: int,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete a knowledge entry for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._delete(
+            f"/api/projects/{project_id}/entries/{entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    async def add_question(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """
+        Add a question to a project.
+
+        Returns: 201 Created if a new question was added 200 OK if the question already
+        existed
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/entries/add_question",
+            body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+    async def query(
+        self,
+        project_id: int,
+        *,
+        question: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[Entry]:
+        """
+        Query knowledge for a project.
+
+        Returns the matching entry if found and answered, otherwise returns None. This
+        allows the client to distinguish between: (1) no matching question found
+        (returns None), and (2) matching question found but not yet answered (returns
+        Entry with answer=None).
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            f"/api/projects/{project_id}/entries/query",
+            body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
+
+class EntriesResourceWithRawResponse:
+    def __init__(self, entries: EntriesResource) -> None:
+        self._entries = entries
+
+        self.create = to_raw_response_wrapper(
+            entries.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            entries.retrieve,
+        )
+        self.update = to_raw_response_wrapper(
+            entries.update,
+        )
+        self.list = to_raw_response_wrapper(
+            entries.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            entries.delete,
+        )
+        self.add_question = to_raw_response_wrapper(
+            entries.add_question,
+        )
+        self.query = to_raw_response_wrapper(
+            entries.query,
+        )
+
+
+class AsyncEntriesResourceWithRawResponse:
+    def __init__(self, entries: AsyncEntriesResource) -> None:
+        self._entries = entries
+
+        self.create = async_to_raw_response_wrapper(
+            entries.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            entries.retrieve,
+        )
+        self.update = async_to_raw_response_wrapper(
+            entries.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            entries.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            entries.delete,
+        )
+        self.add_question = async_to_raw_response_wrapper(
+            entries.add_question,
+        )
+        self.query = async_to_raw_response_wrapper(
+            entries.query,
+        )
+
+
+class EntriesResourceWithStreamingResponse:
+    def __init__(self, entries: EntriesResource) -> None:
+        self._entries = entries
+
+        self.create = to_streamed_response_wrapper(
+            entries.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            entries.retrieve,
+        )
+        self.update = to_streamed_response_wrapper(
+            entries.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            entries.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            entries.delete,
+        )
+        self.add_question = to_streamed_response_wrapper(
+            entries.add_question,
+        )
+        self.query = to_streamed_response_wrapper(
+            entries.query,
+        )
+
+
+class AsyncEntriesResourceWithStreamingResponse:
+    def __init__(self, entries: AsyncEntriesResource) -> None:
+        self._entries = entries
+
+        self.create = async_to_streamed_response_wrapper(
+            entries.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            entries.retrieve,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            entries.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            entries.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            entries.delete,
+        )
+        self.add_question = async_to_streamed_response_wrapper(
+            entries.add_question,
+        )
+        self.query = async_to_streamed_response_wrapper(
+            entries.query,
+        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index a7bea419..d77e63a1 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -7,6 +7,14 @@
 import httpx
 
 from ...types import project_list_params, project_create_params, project_update_params
+from .entries import (
+    EntriesResource,
+    AsyncEntriesResource,
+    EntriesResourceWithRawResponse,
+    AsyncEntriesResourceWithRawResponse,
+    EntriesResourceWithStreamingResponse,
+    AsyncEntriesResourceWithStreamingResponse,
+)
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
 from ..._utils import (
     maybe_transform,
@@ -40,6 +48,10 @@ class ProjectsResource(SyncAPIResource):
     def access_keys(self) -> AccessKeysResource:
         return AccessKeysResource(self._client)
 
+    @cached_property
+    def entries(self) -> EntriesResource:
+        return EntriesResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         """
@@ -279,6 +291,10 @@ class AsyncProjectsResource(AsyncAPIResource):
     def access_keys(self) -> AsyncAccessKeysResource:
         return AsyncAccessKeysResource(self._client)
 
+    @cached_property
+    def entries(self) -> AsyncEntriesResource:
+        return AsyncEntriesResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         """
@@ -542,6 +558,10 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
         return AccessKeysResourceWithRawResponse(self._projects.access_keys)
 
+    @cached_property
+    def entries(self) -> EntriesResourceWithRawResponse:
+        return EntriesResourceWithRawResponse(self._projects.entries)
+
 
 class AsyncProjectsResourceWithRawResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -570,6 +590,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
         return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys)
 
+    @cached_property
+    def entries(self) -> AsyncEntriesResourceWithRawResponse:
+        return AsyncEntriesResourceWithRawResponse(self._projects.entries)
+
 
 class ProjectsResourceWithStreamingResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -598,6 +622,10 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
         return AccessKeysResourceWithStreamingResponse(self._projects.access_keys)
 
+    @cached_property
+    def entries(self) -> EntriesResourceWithStreamingResponse:
+        return EntriesResourceWithStreamingResponse(self._projects.entries)
+
 
 class AsyncProjectsResourceWithStreamingResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -625,3 +653,7 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
         return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys)
+
+    @cached_property
+    def entries(self) -> AsyncEntriesResourceWithStreamingResponse:
+        return AsyncEntriesResourceWithStreamingResponse(self._projects.entries)
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 7aadc5f0..b79e23f2 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -2,10 +2,17 @@
 
 from __future__ import annotations
 
+from .entry import Entry as Entry
 from .access_key_schema import AccessKeySchema as AccessKeySchema
+from .entry_list_params import EntryListParams as EntryListParams
+from .entry_query_params import EntryQueryParams as EntryQueryParams
+from .entry_create_params import EntryCreateParams as EntryCreateParams
+from .entry_list_response import EntryListResponse as EntryListResponse
+from .entry_update_params import EntryUpdateParams as EntryUpdateParams
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
+from .entry_add_question_params import EntryAddQuestionParams as EntryAddQuestionParams
 from .access_key_retrieve_project_id_response import (
     AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
 )
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
new file mode 100644
index 00000000..bfa7d278
--- /dev/null
+++ b/src/codex/types/projects/entry.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["Entry"]
+
+
+class Entry(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    question: str
+
+    answer: Optional[str] = None
+
+    answered_at: Optional[datetime] = None
diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py
new file mode 100644
index 00000000..e2d009b4
--- /dev/null
+++ b/src/codex/types/projects/entry_add_question_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["EntryAddQuestionParams"]
+
+
+class EntryAddQuestionParams(TypedDict, total=False):
+    question: Required[str]
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
new file mode 100644
index 00000000..1ac23dd4
--- /dev/null
+++ b/src/codex/types/projects/entry_create_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["EntryCreateParams"]
+
+
+class EntryCreateParams(TypedDict, total=False):
+    question: Required[str]
+
+    answer: Optional[str]
diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/entry_list_params.py
new file mode 100644
index 00000000..b50181f7
--- /dev/null
+++ b/src/codex/types/projects/entry_list_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["EntryListParams"]
+
+
+class EntryListParams(TypedDict, total=False):
+    answered_only: bool
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    sort: Literal["created_at", "answered_at"]
+
+    unanswered_only: bool
diff --git a/src/codex/types/projects/entry_list_response.py b/src/codex/types/projects/entry_list_response.py
new file mode 100644
index 00000000..f415058e
--- /dev/null
+++ b/src/codex/types/projects/entry_list_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .entry import Entry
+from ..._models import BaseModel
+
+__all__ = ["EntryListResponse"]
+
+
+class EntryListResponse(BaseModel):
+    entries: List[Entry]
+
+    total_count: int
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
new file mode 100644
index 00000000..b6fbc437
--- /dev/null
+++ b/src/codex/types/projects/entry_query_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["EntryQueryParams"]
+
+
+class EntryQueryParams(TypedDict, total=False):
+    question: Required[str]
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
new file mode 100644
index 00000000..4a7c6527
--- /dev/null
+++ b/src/codex/types/projects/entry_update_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["EntryUpdateParams"]
+
+
+class EntryUpdateParams(TypedDict, total=False):
+    project_id: Required[int]
+
+    answer: Optional[str]
+
+    question: Optional[str]
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
new file mode 100644
index 00000000..32f55fd2
--- /dev/null
+++ b/tests/api_resources/projects/test_entries.py
@@ -0,0 +1,661 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.projects import (
+    Entry,
+    EntryListResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEntries:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        entry = client.projects.entries.create(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.create(
+            project_id=0,
+            question="question",
+            answer="answer",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.create(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.create(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        entry = client.projects.entries.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.retrieve(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update(self, client: Codex) -> None:
+        entry = client.projects.entries.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+            answer="answer",
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_update(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.update(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        entry = client.projects.entries.list(
+            project_id=0,
+        )
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.list(
+            project_id=0,
+            answered_only=True,
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            unanswered_only=True,
+        )
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.list(
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.list(
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(EntryListResponse, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        entry = client.projects.entries.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert entry is None
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert entry is None
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert entry is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.delete(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_add_question(self, client: Codex) -> None:
+        entry = client.projects.entries.add_question(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_add_question(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.add_question(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_add_question(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.add_question(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_query(self, client: Codex) -> None:
+        entry = client.projects.entries.query(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_query(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.query(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_query(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.query(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Optional[Entry], entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEntries:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.create(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.create(
+            project_id=0,
+            question="question",
+            answer="answer",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.create(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.create(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.retrieve(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.retrieve(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+            answer="answer",
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.update(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.update(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.list(
+            project_id=0,
+        )
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.list(
+            project_id=0,
+            answered_only=True,
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            unanswered_only=True,
+        )
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.list(
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(EntryListResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.list(
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(EntryListResponse, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+        assert entry is None
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert entry is None
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.delete(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id=0,
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert entry is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.delete(
+                entry_id="",
+                project_id=0,
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.add_question(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.add_question(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.add_question(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_query(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.query(
+            project_id=0,
+            question="question",
+        )
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.query(
+            project_id=0,
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.query(
+            project_id=0,
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Optional[Entry], entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True

From c1ac8644d17591b4230981ea10ebe92abd891f33 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:19:48 +0000
Subject: [PATCH 014/227] fix: update pagination scheme (#9)

---
 src/codex/pagination.py | 26 +-------------------------
 1 file changed, 1 insertion(+), 25 deletions(-)

diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 963a4310..4798baae 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -1,18 +1,12 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
+from typing import List, Generic, TypeVar, Optional
 from typing_extensions import override
 
-from httpx import Response
-
-from ._utils import is_mapping
-from ._models import BaseModel
 from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
 
 __all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"]
 
-_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
-
 _T = TypeVar("_T")
 
 
@@ -37,15 +31,6 @@ def next_page_info(self) -> Optional[PageInfo]:
 
         return PageInfo(params={"offset": current_count})
 
-    @classmethod
-    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
-        return cls.construct(
-            None,
-            **{
-                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
-            },
-        )
-
 
 class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
     items: List[_T]
@@ -67,12 +52,3 @@ def next_page_info(self) -> Optional[PageInfo]:
         current_count = offset + length
 
         return PageInfo(params={"offset": current_count})
-
-    @classmethod
-    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
-        return cls.construct(
-            None,
-            **{
-                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
-            },
-        )

From 00670d5c64a81cf4c5719cd65f9ea2b460de3f61 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:25:27 +0000
Subject: [PATCH 015/227] fix: update pagination scheme (#10)

---
 src/codex/pagination.py | 115 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 111 insertions(+), 4 deletions(-)

diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 4798baae..b38b5f45 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -1,16 +1,27 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import List, Generic, TypeVar, Optional
+from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
 from typing_extensions import override
 
+from httpx import Response
+
+from ._utils import is_mapping
+from ._models import BaseModel
 from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
 
-__all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"]
+__all__ = [
+    "SyncMyOffsetPageTopLevelArray",
+    "AsyncMyOffsetPageTopLevelArray",
+    "SyncOffsetPageEntries",
+    "AsyncOffsetPageEntries",
+]
+
+_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
 
 _T = TypeVar("_T")
 
 
-class SyncMyOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+class SyncMyOffsetPageTopLevelArray(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
     items: List[_T]
 
     @override
@@ -31,8 +42,17 @@ def next_page_info(self) -> Optional[PageInfo]:
 
         return PageInfo(params={"offset": current_count})
 
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+            },
+        )
+
 
-class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+class AsyncMyOffsetPageTopLevelArray(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
     items: List[_T]
 
     @override
@@ -52,3 +72,90 @@ def next_page_info(self) -> Optional[PageInfo]:
         current_count = offset + length
 
         return PageInfo(params={"offset": current_count})
+
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+            },
+        )
+
+
+class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    entries: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        entries = self.entries
+        if not entries:
+            return []
+        return entries
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}),
+            },
+        )
+
+
+class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    entries: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        entries = self.entries
+        if not entries:
+            return []
+        return entries
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+    @classmethod
+    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
+        return cls.construct(
+            None,
+            **{
+                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}),
+            },
+        )

From f95537f933a9305609d85335f638c4a29c1d20e0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:26:43 +0000
Subject: [PATCH 016/227] fix: update pagination scheme (#11)

---
 api.md                                        |  4 ++--
 src/codex/pagination.py                       | 18 ---------------
 src/codex/resources/projects/entries.py       | 22 ++++++++++---------
 src/codex/types/projects/__init__.py          |  1 -
 .../types/projects/entry_list_response.py     | 14 ------------
 tests/api_resources/projects/test_entries.py  | 18 +++++++--------
 6 files changed, 23 insertions(+), 54 deletions(-)
 delete mode 100644 src/codex/types/projects/entry_list_response.py

diff --git a/api.md b/api.md
index 3665b5ba..d4720cc2 100644
--- a/api.md
+++ b/api.md
@@ -116,7 +116,7 @@ Methods:
 Types:
 
 ```python
-from codex.types.projects import Entry, EntryListResponse
+from codex.types.projects import Entry
 ```
 
 Methods:
@@ -124,7 +124,7 @@ Methods:
 - <code title="post /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/entry_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="get /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="put /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="get /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/entry_list_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_list_response.py">EntryListResponse</a></code>
+- <code title="get /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/entry_list_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">SyncOffsetPageEntries[Entry]</a></code>
 - <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
 - <code title="post /api/projects/{project_id}/entries/add_question">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/entry_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index b38b5f45..48ed5018 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -112,15 +112,6 @@ def next_page_info(self) -> Optional[PageInfo]:
 
         return None
 
-    @classmethod
-    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
-        return cls.construct(
-            None,
-            **{
-                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}),
-            },
-        )
-
 
 class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
     entries: List[_T]
@@ -150,12 +141,3 @@ def next_page_info(self) -> Optional[PageInfo]:
             return PageInfo(params={"offset": current_count})
 
         return None
-
-    @classmethod
-    def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT:  # noqa: ARG003
-        return cls.construct(
-            None,
-            **{
-                **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}),
-            },
-        )
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 6121842e..59bc9f7f 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -20,7 +20,8 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ..._base_client import make_request_options
+from ...pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries
+from ..._base_client import AsyncPaginator, make_request_options
 from ...types.projects import (
     entry_list_params,
     entry_query_params,
@@ -29,7 +30,6 @@
     entry_add_question_params,
 )
 from ...types.projects.entry import Entry
-from ...types.projects.entry_list_response import EntryListResponse
 
 __all__ = ["EntriesResource", "AsyncEntriesResource"]
 
@@ -187,7 +187,7 @@ def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryListResponse:
+    ) -> SyncOffsetPageEntries[Entry]:
         """
         List knowledge entries for a project.
 
@@ -200,8 +200,9 @@ def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
-        return self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/entries/",
+            page=SyncOffsetPageEntries[Entry],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
@@ -219,7 +220,7 @@ def list(
                     entry_list_params.EntryListParams,
                 ),
             ),
-            cast_to=EntryListResponse,
+            model=Entry,
         )
 
     def delete(
@@ -469,7 +470,7 @@ async def update(
             cast_to=Entry,
         )
 
-    async def list(
+    def list(
         self,
         project_id: int,
         *,
@@ -485,7 +486,7 @@ async def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryListResponse:
+    ) -> AsyncPaginator[Entry, AsyncOffsetPageEntries[Entry]]:
         """
         List knowledge entries for a project.
 
@@ -498,14 +499,15 @@ async def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
-        return await self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/entries/",
+            page=AsyncOffsetPageEntries[Entry],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
                 extra_body=extra_body,
                 timeout=timeout,
-                query=await async_maybe_transform(
+                query=maybe_transform(
                     {
                         "answered_only": answered_only,
                         "limit": limit,
@@ -517,7 +519,7 @@ async def list(
                     entry_list_params.EntryListParams,
                 ),
             ),
-            cast_to=EntryListResponse,
+            model=Entry,
         )
 
     async def delete(
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index b79e23f2..44c304f5 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -7,7 +7,6 @@
 from .entry_list_params import EntryListParams as EntryListParams
 from .entry_query_params import EntryQueryParams as EntryQueryParams
 from .entry_create_params import EntryCreateParams as EntryCreateParams
-from .entry_list_response import EntryListResponse as EntryListResponse
 from .entry_update_params import EntryUpdateParams as EntryUpdateParams
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
diff --git a/src/codex/types/projects/entry_list_response.py b/src/codex/types/projects/entry_list_response.py
deleted file mode 100644
index f415058e..00000000
--- a/src/codex/types/projects/entry_list_response.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .entry import Entry
-from ..._models import BaseModel
-
-__all__ = ["EntryListResponse"]
-
-
-class EntryListResponse(BaseModel):
-    entries: List[Entry]
-
-    total_count: int
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 32f55fd2..e1c36e3b 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -9,9 +9,9 @@
 
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
+from codex.pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries
 from codex.types.projects import (
     Entry,
-    EntryListResponse,
 )
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -176,7 +176,7 @@ def test_method_list(self, client: Codex) -> None:
         entry = client.projects.entries.list(
             project_id=0,
         )
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -190,7 +190,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             sort="created_at",
             unanswered_only=True,
         )
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -202,7 +202,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         entry = response.parse()
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -214,7 +214,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             entry = response.parse()
-            assert_matches_type(EntryListResponse, entry, path=["response"])
+            assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -498,7 +498,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.list(
             project_id=0,
         )
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -512,7 +512,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             sort="created_at",
             unanswered_only=True,
         )
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -524,7 +524,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         entry = await response.parse()
-        assert_matches_type(EntryListResponse, entry, path=["response"])
+        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -536,7 +536,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             entry = await response.parse()
-            assert_matches_type(EntryListResponse, entry, path=["response"])
+            assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 

From 2af5cb93495a2c0a3c31bee08190879d2e2760ab Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:34:49 +0000
Subject: [PATCH 017/227] fix: update paginated endpoints (#12)

---
 README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/README.md b/README.md
index 634367e0..fd13a0ba 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,75 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
 
 Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
 
+## Pagination
+
+List methods in the Codex API are paginated.
+
+This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
+
+```python
+from codex import Codex
+
+client = Codex()
+
+all_entries = []
+# Automatically fetches more pages as needed.
+for entry in client.projects.entries.list(
+    project_id=0,
+):
+    # Do something with entry here
+    all_entries.append(entry)
+print(all_entries)
+```
+
+Or, asynchronously:
+
+```python
+import asyncio
+from codex import AsyncCodex
+
+client = AsyncCodex()
+
+
+async def main() -> None:
+    all_entries = []
+    # Iterate through items across all pages, issuing requests as needed.
+    async for entry in client.projects.entries.list(
+        project_id=0,
+    ):
+        all_entries.append(entry)
+    print(all_entries)
+
+
+asyncio.run(main())
+```
+
+Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
+
+```python
+first_page = await client.projects.entries.list(
+    project_id=0,
+)
+if first_page.has_next_page():
+    print(f"will fetch next page using these details: {first_page.next_page_info()}")
+    next_page = await first_page.get_next_page()
+    print(f"number of items we just fetched: {len(next_page.entries)}")
+
+# Remove `await` for non-async usage.
+```
+
+Or just work directly with the returned data:
+
+```python
+first_page = await client.projects.entries.list(
+    project_id=0,
+)
+for entry in first_page.entries:
+    print(entry.id)
+
+# Remove `await` for non-async usage.
+```
+
 ## Handling errors
 
 When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised.

From 39cd689f2fe52b141dfd660c83435b5487148f55 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:36:23 +0000
Subject: [PATCH 018/227] fix: don't publish for now (#13)

---
 README.md | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index fd13a0ba..7a8ff536 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,13 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean
 ## Installation
 
 ```sh
-# install from PyPI
-pip install --pre codex-sdk
+# install from the production repo
+pip install git+ssh://git@github.com/cleanlab/codex-python.git
 ```
 
+> [!NOTE]
+> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex-sdk`
+
 ## Usage
 
 The full API of this library can be found in [api.md](api.md).

From 2a9e03778587edf310c16d2e2d026d156a9fa55e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:38:30 +0000
Subject: [PATCH 019/227] fix: switch to MIT license (#14)

---
 LICENSE        | 202 +------------------------------------------------
 pyproject.toml |   4 +-
 2 files changed, 6 insertions(+), 200 deletions(-)

diff --git a/LICENSE b/LICENSE
index f00d3a6a..56f06ba4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,7 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
+Copyright 2025 Codex
 
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
-   1. Definitions.
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2025 Codex
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pyproject.toml b/pyproject.toml
index 647b75c0..30a3e081 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ name = "codex-sdk"
 version = "0.0.1-alpha.0"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
-license = "Apache-2.0"
+license = "MIT"
 authors = [
 { name = "Codex", email = "team@cleanlab.ai" },
 ]
@@ -30,7 +30,7 @@ classifiers = [
   "Operating System :: POSIX :: Linux",
   "Operating System :: Microsoft :: Windows",
   "Topic :: Software Development :: Libraries :: Python Modules",
-  "License :: OSI Approved :: Apache Software License"
+  "License :: OSI Approved :: MIT License"
 ]
 
 [project.urls]

From fa9fd48bbebcbfe150d64612bfb461ff25141897 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:43:08 +0000
Subject: [PATCH 020/227] fix: updload_spec = false (#15)

---
 .github/workflows/ci.yml | 19 -------------------
 .stats.yml               |  1 -
 2 files changed, 20 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 40293964..565ec95e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,24 +30,5 @@ jobs:
 
       - name: Run lints
         run: ./scripts/lint
-  test:
-    name: test
-    runs-on: ubuntu-latest
-
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Install Rye
-        run: |
-          curl -sSf https://rye.astral.sh/get | bash
-          echo "$HOME/.rye/shims" >> $GITHUB_PATH
-        env:
-          RYE_VERSION: '0.35.0'
-          RYE_INSTALL_OPTION: '--yes'
-
-      - name: Bootstrap
-        run: ./scripts/bootstrap
 
-      - name: Run tests
-        run: ./scripts/test
 
diff --git a/.stats.yml b/.stats.yml
index 66033eb6..3a1d4dfc 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1 @@
 configured_endpoints: 29
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml

From 2d481364b5f40a65ce4d2ace13d515927dbcf07c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Jan 2025 21:46:12 +0000
Subject: [PATCH 021/227] chore(internal): version bump (#16)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index c4762802..ba6c3483 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.0.1-alpha.0"
+  ".": "0.1.0-alpha.1"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 30a3e081..81892d12 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.0.1-alpha.0"
+version = "0.1.0-alpha.1"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index fd0542b8..96b3eab2 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.0.1-alpha.0"  # x-release-please-version
+__version__ = "0.1.0-alpha.1"  # x-release-please-version

From ff9efe547af75dda3054cea763c82287612a0982 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 16 Jan 2025 21:16:42 +0000
Subject: [PATCH 022/227] feat(api): api update (#17)

---
 README.md                                     |   8 +-
 api.md                                        |   2 +-
 src/codex/resources/projects/access_keys.py   |  89 +++--
 src/codex/resources/projects/entries.py       |  56 +++-
 src/codex/resources/projects/projects.py      |  32 +-
 src/codex/types/organization_schema_public.py |   3 +
 src/codex/types/project_return_schema.py      |   2 +-
 ...access_key_retrieve_project_id_response.py |   2 +-
 src/codex/types/projects/access_key_schema.py |   4 +-
 .../projects/access_key_update_params.py      |   2 +-
 .../types/projects/entry_update_params.py     |   2 +-
 .../projects/test_access_keys.py              | 303 +++++++++++++-----
 tests/api_resources/projects/test_entries.py  | 214 +++++++++----
 tests/api_resources/test_projects.py          | 120 +++++--
 14 files changed, 628 insertions(+), 211 deletions(-)

diff --git a/README.md b/README.md
index 7a8ff536..7b5fda1d 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ client = Codex()
 all_entries = []
 # Automatically fetches more pages as needed.
 for entry in client.projects.entries.list(
-    project_id=0,
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 ):
     # Do something with entry here
     all_entries.append(entry)
@@ -118,7 +118,7 @@ async def main() -> None:
     all_entries = []
     # Iterate through items across all pages, issuing requests as needed.
     async for entry in client.projects.entries.list(
-        project_id=0,
+        project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
     ):
         all_entries.append(entry)
     print(all_entries)
@@ -131,7 +131,7 @@ Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get
 
 ```python
 first_page = await client.projects.entries.list(
-    project_id=0,
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )
 if first_page.has_next_page():
     print(f"will fetch next page using these details: {first_page.next_page_info()}")
@@ -145,7 +145,7 @@ Or just work directly with the returned data:
 
 ```python
 first_page = await client.projects.entries.list(
-    project_id=0,
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )
 for entry in first_page.entries:
     print(entry.id)
diff --git a/api.md b/api.md
index d4720cc2..48af8b68 100644
--- a/api.md
+++ b/api.md
@@ -108,7 +108,7 @@ Methods:
 - <code title="put /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">update</a>(access_key_id, \*, project_id, \*\*<a href="src/codex/types/projects/access_key_update_params.py">params</a>) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
 - <code title="get /api/projects/{project_id}/access_keys/">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">list</a>(project_id) -> <a href="./src/codex/types/projects/access_key_list_response.py">AccessKeyListResponse</a></code>
 - <code title="delete /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">delete</a>(access_key_id, \*, project_id) -> None</code>
-- <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> <a href="./src/codex/types/projects/access_key_retrieve_project_id_response.py">AccessKeyRetrieveProjectIDResponse</a></code>
+- <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> str</code>
 - <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
 
 ## Entries
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index e282ad4f..8a580f49 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -24,7 +24,6 @@
 from ...types.projects import access_key_create_params, access_key_update_params
 from ...types.projects.access_key_schema import AccessKeySchema
 from ...types.projects.access_key_list_response import AccessKeyListResponse
-from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse
 
 __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"]
 
@@ -51,7 +50,7 @@ def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse:
 
     def create(
         self,
-        project_id: int,
+        project_id: str,
         *,
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
@@ -75,6 +74,8 @@ def create(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._post(
             f"/api/projects/{project_id}/access_keys/",
             body=maybe_transform(
@@ -93,9 +94,9 @@ def create(
 
     def retrieve(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -115,6 +116,10 @@ def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         return self._get(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
             options=make_request_options(
@@ -125,9 +130,9 @@ def retrieve(
 
     def update(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
@@ -150,6 +155,10 @@ def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         return self._put(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
             body=maybe_transform(
@@ -168,7 +177,7 @@ def update(
 
     def list(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -189,6 +198,8 @@ def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._get(
             f"/api/projects/{project_id}/access_keys/",
             options=make_request_options(
@@ -199,9 +210,9 @@ def list(
 
     def delete(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -221,6 +232,10 @@ def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return self._delete(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
@@ -239,21 +254,21 @@ def retrieve_project_id(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> AccessKeyRetrieveProjectIDResponse:
+    ) -> str:
         """Get the project ID from an access key."""
         return self._get(
             "/api/projects/id_from_access_key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=int,
+            cast_to=str,
         )
 
     def revoke(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -273,6 +288,10 @@ def revoke(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return self._post(
             f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke",
@@ -305,7 +324,7 @@ def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingRespons
 
     async def create(
         self,
-        project_id: int,
+        project_id: str,
         *,
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
@@ -329,6 +348,8 @@ async def create(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._post(
             f"/api/projects/{project_id}/access_keys/",
             body=await async_maybe_transform(
@@ -347,9 +368,9 @@ async def create(
 
     async def retrieve(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -369,6 +390,10 @@ async def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         return await self._get(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
             options=make_request_options(
@@ -379,9 +404,9 @@ async def retrieve(
 
     async def update(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
@@ -404,6 +429,10 @@ async def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         return await self._put(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
             body=await async_maybe_transform(
@@ -422,7 +451,7 @@ async def update(
 
     async def list(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -443,6 +472,8 @@ async def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._get(
             f"/api/projects/{project_id}/access_keys/",
             options=make_request_options(
@@ -453,9 +484,9 @@ async def list(
 
     async def delete(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -475,6 +506,10 @@ async def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return await self._delete(
             f"/api/projects/{project_id}/access_keys/{access_key_id}",
@@ -493,21 +528,21 @@ async def retrieve_project_id(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> AccessKeyRetrieveProjectIDResponse:
+    ) -> str:
         """Get the project ID from an access key."""
         return await self._get(
             "/api/projects/id_from_access_key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=int,
+            cast_to=str,
         )
 
     async def revoke(
         self,
-        access_key_id: int,
+        access_key_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -527,6 +562,10 @@ async def revoke(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not access_key_id:
+            raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return await self._post(
             f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke",
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 59bc9f7f..56c893c0 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -56,7 +56,7 @@ def with_streaming_response(self) -> EntriesResourceWithStreamingResponse:
 
     def create(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
@@ -79,6 +79,8 @@ def create(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._post(
             f"/api/projects/{project_id}/entries/",
             body=maybe_transform(
@@ -98,7 +100,7 @@ def retrieve(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -118,6 +120,8 @@ def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         return self._get(
@@ -132,7 +136,7 @@ def update(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -154,6 +158,8 @@ def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         return self._put(
@@ -173,7 +179,7 @@ def update(
 
     def list(
         self,
-        project_id: int,
+        project_id: str,
         *,
         answered_only: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
@@ -200,6 +206,8 @@ def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._get_api_list(
             f"/api/projects/{project_id}/entries/",
             page=SyncOffsetPageEntries[Entry],
@@ -227,7 +235,7 @@ def delete(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -247,6 +255,8 @@ def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
@@ -260,7 +270,7 @@ def delete(
 
     def add_question(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -285,6 +295,8 @@ def add_question(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._post(
             f"/api/projects/{project_id}/entries/add_question",
             body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
@@ -296,7 +308,7 @@ def add_question(
 
     def query(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -323,6 +335,8 @@ def query(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._post(
             f"/api/projects/{project_id}/entries/query",
             body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
@@ -355,7 +369,7 @@ def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse:
 
     async def create(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
@@ -378,6 +392,8 @@ async def create(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._post(
             f"/api/projects/{project_id}/entries/",
             body=await async_maybe_transform(
@@ -397,7 +413,7 @@ async def retrieve(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -417,6 +433,8 @@ async def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         return await self._get(
@@ -431,7 +449,7 @@ async def update(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -453,6 +471,8 @@ async def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         return await self._put(
@@ -472,7 +492,7 @@ async def update(
 
     def list(
         self,
-        project_id: int,
+        project_id: str,
         *,
         answered_only: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
@@ -499,6 +519,8 @@ def list(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._get_api_list(
             f"/api/projects/{project_id}/entries/",
             page=AsyncOffsetPageEntries[Entry],
@@ -526,7 +548,7 @@ async def delete(
         self,
         entry_id: str,
         *,
-        project_id: int,
+        project_id: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -546,6 +568,8 @@ async def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         if not entry_id:
             raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
@@ -559,7 +583,7 @@ async def delete(
 
     async def add_question(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -584,6 +608,8 @@ async def add_question(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._post(
             f"/api/projects/{project_id}/entries/add_question",
             body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
@@ -595,7 +621,7 @@ async def add_question(
 
     async def query(
         self,
-        project_id: int,
+        project_id: str,
         *,
         question: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -622,6 +648,8 @@ async def query(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._post(
             f"/api/projects/{project_id}/entries/query",
             body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index d77e63a1..8a5f35a1 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -116,7 +116,7 @@ def create(
 
     def retrieve(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -137,6 +137,8 @@ def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._get(
             f"/api/projects/{project_id}",
             options=make_request_options(
@@ -147,7 +149,7 @@ def retrieve(
 
     def update(
         self,
-        project_id: int,
+        project_id: str,
         *,
         config: project_update_params.Config,
         name: str,
@@ -171,6 +173,8 @@ def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._put(
             f"/api/projects/{project_id}",
             body=maybe_transform(
@@ -224,7 +228,7 @@ def list(
 
     def delete(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -245,6 +249,8 @@ def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return self._delete(
             f"/api/projects/{project_id}",
@@ -256,7 +262,7 @@ def delete(
 
     def export(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -277,6 +283,8 @@ def export(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return self._get(
             f"/api/projects/{project_id}/export",
             options=make_request_options(
@@ -359,7 +367,7 @@ async def create(
 
     async def retrieve(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -380,6 +388,8 @@ async def retrieve(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._get(
             f"/api/projects/{project_id}",
             options=make_request_options(
@@ -390,7 +400,7 @@ async def retrieve(
 
     async def update(
         self,
-        project_id: int,
+        project_id: str,
         *,
         config: project_update_params.Config,
         name: str,
@@ -414,6 +424,8 @@ async def update(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._put(
             f"/api/projects/{project_id}",
             body=await async_maybe_transform(
@@ -469,7 +481,7 @@ async def list(
 
     async def delete(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -490,6 +502,8 @@ async def delete(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         extra_headers = {"Accept": "*/*", **(extra_headers or {})}
         return await self._delete(
             f"/api/projects/{project_id}",
@@ -501,7 +515,7 @@ async def delete(
 
     async def export(
         self,
-        project_id: int,
+        project_id: str,
         *,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -522,6 +536,8 @@ async def export(
 
           timeout: Override the client-level default timeout for this request, in seconds
         """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
         return await self._get(
             f"/api/projects/{project_id}/export",
             options=make_request_options(
diff --git a/src/codex/types/organization_schema_public.py b/src/codex/types/organization_schema_public.py
index ed245c50..decc8559 100644
--- a/src/codex/types/organization_schema_public.py
+++ b/src/codex/types/organization_schema_public.py
@@ -1,6 +1,7 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 from datetime import datetime
+from typing_extensions import Literal
 
 from .._models import BaseModel
 
@@ -14,4 +15,6 @@ class OrganizationSchemaPublic(BaseModel):
 
     name: str
 
+    payment_status: Literal["NULL", "FIRST_OVERAGE_LENIENT", "SECOND_OVERAGE_USAGE_BLOCKED"]
+
     updated_at: datetime
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 7f6b06a2..85312729 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -13,7 +13,7 @@ class Config(BaseModel):
 
 
 class ProjectReturnSchema(BaseModel):
-    id: int
+    id: str
 
     config: Config
 
diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py
index 5659cd21..b7b53137 100644
--- a/src/codex/types/projects/access_key_retrieve_project_id_response.py
+++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py
@@ -4,4 +4,4 @@
 
 __all__ = ["AccessKeyRetrieveProjectIDResponse"]
 
-AccessKeyRetrieveProjectIDResponse: TypeAlias = int
+AccessKeyRetrieveProjectIDResponse: TypeAlias = str
diff --git a/src/codex/types/projects/access_key_schema.py b/src/codex/types/projects/access_key_schema.py
index 3e023aae..aaf5d0fe 100644
--- a/src/codex/types/projects/access_key_schema.py
+++ b/src/codex/types/projects/access_key_schema.py
@@ -10,7 +10,7 @@
 
 
 class AccessKeySchema(BaseModel):
-    id: int
+    id: str
 
     token: str
 
@@ -20,7 +20,7 @@ class AccessKeySchema(BaseModel):
 
     name: str
 
-    project_id: int
+    project_id: str
 
     status: Literal["active", "expired", "revoked"]
 
diff --git a/src/codex/types/projects/access_key_update_params.py b/src/codex/types/projects/access_key_update_params.py
index 2f40f335..f11e825f 100644
--- a/src/codex/types/projects/access_key_update_params.py
+++ b/src/codex/types/projects/access_key_update_params.py
@@ -12,7 +12,7 @@
 
 
 class AccessKeyUpdateParams(TypedDict, total=False):
-    project_id: Required[int]
+    project_id: Required[str]
 
     name: Required[str]
 
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
index 4a7c6527..0a676f31 100644
--- a/src/codex/types/projects/entry_update_params.py
+++ b/src/codex/types/projects/entry_update_params.py
@@ -9,7 +9,7 @@
 
 
 class EntryUpdateParams(TypedDict, total=False):
-    project_id: Required[int]
+    project_id: Required[str]
 
     answer: Optional[str]
 
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index cdb893e6..6ec8ef9b 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -13,7 +13,6 @@
 from codex.types.projects import (
     AccessKeySchema,
     AccessKeyListResponse,
-    AccessKeyRetrieveProjectIDResponse,
 )
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -26,7 +25,7 @@ class TestAccessKeys:
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
@@ -35,7 +34,7 @@ def test_method_create(self, client: Codex) -> None:
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
@@ -46,7 +45,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
 
@@ -59,7 +58,7 @@ def test_raw_response_create(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         ) as response:
             assert not response.is_closed
@@ -70,12 +69,21 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_create(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.create(
+                project_id="",
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
@@ -83,8 +91,8 @@ def test_method_retrieve(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -96,8 +104,8 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -107,12 +115,27 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.retrieve(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            client.projects.access_keys.with_raw_response.retrieve(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
@@ -121,8 +144,8 @@ def test_method_update(self, client: Codex) -> None:
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
@@ -133,8 +156,8 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
 
@@ -147,8 +170,8 @@ def test_raw_response_update(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         ) as response:
             assert not response.is_closed
@@ -159,11 +182,28 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_update(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.update(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                name="name",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            client.projects.access_keys.with_raw_response.update(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         access_key = client.projects.access_keys.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
@@ -171,7 +211,7 @@ def test_method_list(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -183,7 +223,7 @@ def test_raw_response_list(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -193,12 +233,20 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.list(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         access_key = client.projects.access_keys.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert access_key is None
 
@@ -206,8 +254,8 @@ def test_method_delete(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -219,8 +267,8 @@ def test_raw_response_delete(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -230,11 +278,26 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.delete(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            client.projects.access_keys.with_raw_response.delete(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_retrieve_project_id(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve_project_id()
-        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+        assert_matches_type(str, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -244,7 +307,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         access_key = response.parse()
-        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+        assert_matches_type(str, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -254,7 +317,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             access_key = response.parse()
-            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+            assert_matches_type(str, access_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -262,8 +325,8 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
     @parametrize
     def test_method_revoke(self, client: Codex) -> None:
         access_key = client.projects.access_keys.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert access_key is None
 
@@ -271,8 +334,8 @@ def test_method_revoke(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_revoke(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -284,8 +347,8 @@ def test_raw_response_revoke(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_revoke(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -295,6 +358,21 @@ def test_streaming_response_revoke(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_revoke(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.access_keys.with_raw_response.revoke(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            client.projects.access_keys.with_raw_response.revoke(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
 
 class TestAsyncAccessKeys:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -303,7 +381,7 @@ class TestAsyncAccessKeys:
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
@@ -312,7 +390,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
@@ -323,7 +401,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
 
@@ -336,7 +414,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         ) as response:
             assert not response.is_closed
@@ -347,12 +425,21 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.create(
+                project_id="",
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
@@ -360,8 +447,8 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -373,8 +460,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -384,12 +471,27 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.retrieve(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.retrieve(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
@@ -398,8 +500,8 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
@@ -410,8 +512,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         )
 
@@ -424,8 +526,8 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             name="name",
         ) as response:
             assert not response.is_closed
@@ -436,11 +538,28 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.update(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                name="name",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.update(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
@@ -448,7 +567,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -460,7 +579,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -470,12 +589,20 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.list(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert access_key is None
 
@@ -483,8 +610,8 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -496,8 +623,8 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -507,11 +634,26 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.delete(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.delete(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve_project_id()
-        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+        assert_matches_type(str, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -521,7 +663,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex)
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         access_key = await response.parse()
-        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+        assert_matches_type(str, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -531,7 +673,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             access_key = await response.parse()
-            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
+            assert_matches_type(str, access_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -539,8 +681,8 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC
     @parametrize
     async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert access_key is None
 
@@ -548,8 +690,8 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -561,8 +703,8 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
-            access_key_id=0,
-            project_id=0,
+            access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -571,3 +713,18 @@ async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None
             assert access_key is None
 
         assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_revoke(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.revoke(
+                access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"):
+            await async_client.projects.access_keys.with_raw_response.revoke(
+                access_key_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index e1c36e3b..5b51ec12 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -24,7 +24,7 @@ class TestEntries:
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         entry = client.projects.entries.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -33,7 +33,7 @@ def test_method_create(self, client: Codex) -> None:
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         entry = client.projects.entries.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
         )
@@ -43,7 +43,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -56,7 +56,7 @@ def test_raw_response_create(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -67,12 +67,21 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_create(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.create(
+                project_id="",
+                question="question",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         entry = client.projects.entries.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -81,7 +90,7 @@ def test_method_retrieve(self, client: Codex) -> None:
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -94,7 +103,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -107,10 +116,16 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.retrieve(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.entries.with_raw_response.retrieve(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
@@ -118,7 +133,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
     def test_method_update(self, client: Codex) -> None:
         entry = client.projects.entries.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -127,7 +142,7 @@ def test_method_update(self, client: Codex) -> None:
     def test_method_update_with_all_params(self, client: Codex) -> None:
         entry = client.projects.entries.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
             question="question",
         )
@@ -138,7 +153,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -151,7 +166,7 @@ def test_raw_response_update(self, client: Codex) -> None:
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -164,17 +179,23 @@ def test_streaming_response_update(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_path_params_update(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.update(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.entries.with_raw_response.update(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         entry = client.projects.entries.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
 
@@ -182,7 +203,7 @@ def test_method_list(self, client: Codex) -> None:
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         entry = client.projects.entries.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answered_only=True,
             limit=1,
             offset=0,
@@ -196,7 +217,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -208,7 +229,7 @@ def test_raw_response_list(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -218,12 +239,20 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.list(
+                project_id="",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         entry = client.projects.entries.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert entry is None
 
@@ -232,7 +261,7 @@ def test_method_delete(self, client: Codex) -> None:
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -245,7 +274,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -258,17 +287,23 @@ def test_streaming_response_delete(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.delete(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             client.projects.entries.with_raw_response.delete(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
     @parametrize
     def test_method_add_question(self, client: Codex) -> None:
         entry = client.projects.entries.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -277,7 +312,7 @@ def test_method_add_question(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_add_question(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -290,7 +325,7 @@ def test_raw_response_add_question(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_add_question(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -301,11 +336,20 @@ def test_streaming_response_add_question(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_add_question(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.add_question(
+                project_id="",
+                question="question",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_query(self, client: Codex) -> None:
         entry = client.projects.entries.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Optional[Entry], entry, path=["response"])
@@ -314,7 +358,7 @@ def test_method_query(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_query(self, client: Codex) -> None:
         response = client.projects.entries.with_raw_response.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -327,7 +371,7 @@ def test_raw_response_query(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_query(self, client: Codex) -> None:
         with client.projects.entries.with_streaming_response.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -338,6 +382,15 @@ def test_streaming_response_query(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_query(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.query(
+                project_id="",
+                question="question",
+            )
+
 
 class TestAsyncEntries:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -346,7 +399,7 @@ class TestAsyncEntries:
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -355,7 +408,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
         )
@@ -365,7 +418,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -378,7 +431,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.create(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -389,12 +442,21 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.create(
+                project_id="",
+                question="question",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -403,7 +465,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -416,7 +478,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.retrieve(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -429,10 +491,16 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
     @pytest.mark.skip()
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.retrieve(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.entries.with_raw_response.retrieve(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
@@ -440,7 +508,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -449,7 +517,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
             question="question",
         )
@@ -460,7 +528,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -473,7 +541,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.update(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -486,17 +554,23 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
     @pytest.mark.skip()
     @parametrize
     async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.update(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.entries.with_raw_response.update(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
 
@@ -504,7 +578,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answered_only=True,
             limit=1,
             offset=0,
@@ -518,7 +592,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -530,7 +604,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.list(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -540,12 +614,20 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.list(
+                project_id="",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert entry is None
 
@@ -554,7 +636,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -567,7 +649,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.delete(
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -580,17 +662,23 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
     @pytest.mark.skip()
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.delete(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
             await async_client.projects.entries.with_raw_response.delete(
                 entry_id="",
-                project_id=0,
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -599,7 +687,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -612,7 +700,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None
     @parametrize
     async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.add_question(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -623,11 +711,20 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) -
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_add_question(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.add_question(
+                project_id="",
+                question="question",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_query(self, async_client: AsyncCodex) -> None:
         entry = await async_client.projects.entries.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
         assert_matches_type(Optional[Entry], entry, path=["response"])
@@ -636,7 +733,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.entries.with_raw_response.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
 
@@ -649,7 +746,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.entries.with_streaming_response.query(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         ) as response:
             assert not response.is_closed
@@ -659,3 +756,12 @@ async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
             assert_matches_type(Optional[Entry], entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_query(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.query(
+                project_id="",
+                question="question",
+            )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 388601d2..1f1f707b 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -75,7 +75,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         project = client.projects.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -83,7 +83,7 @@ def test_method_retrieve(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.with_raw_response.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -95,7 +95,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.with_streaming_response.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -105,11 +105,19 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.retrieve(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         project = client.projects.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         )
@@ -119,7 +127,7 @@ def test_method_update(self, client: Codex) -> None:
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={"max_distance": 0},
             name="name",
             description="description",
@@ -130,7 +138,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.with_raw_response.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         )
@@ -144,7 +152,7 @@ def test_raw_response_update(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.with_streaming_response.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         ) as response:
@@ -156,6 +164,16 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_update(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.update(
+                project_id="",
+                config={},
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
@@ -194,7 +212,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         project = client.projects.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert project is None
 
@@ -202,7 +220,7 @@ def test_method_delete(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.with_raw_response.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -214,7 +232,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.with_streaming_response.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -224,11 +242,19 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.delete(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_export(self, client: Codex) -> None:
         project = client.projects.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(object, project, path=["response"])
 
@@ -236,7 +262,7 @@ def test_method_export(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_export(self, client: Codex) -> None:
         response = client.projects.with_raw_response.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -248,7 +274,7 @@ def test_raw_response_export(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_export(self, client: Codex) -> None:
         with client.projects.with_streaming_response.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -258,6 +284,14 @@ def test_streaming_response_export(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_export(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.export(
+                "",
+            )
+
 
 class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -317,7 +351,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -325,7 +359,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -337,7 +371,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -347,11 +381,19 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.retrieve(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         )
@@ -361,7 +403,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={"max_distance": 0},
             name="name",
             description="description",
@@ -372,7 +414,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         )
@@ -386,7 +428,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
-            project_id=0,
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={},
             name="name",
         ) as response:
@@ -398,6 +440,16 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.update(
+                project_id="",
+                config={},
+                name="name",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
@@ -436,7 +488,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert project is None
 
@@ -444,7 +496,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -456,7 +508,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.delete(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -466,11 +518,19 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.delete(
+                "",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_export(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
         assert_matches_type(object, project, path=["response"])
 
@@ -478,7 +538,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -490,7 +550,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.export(
-            0,
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -499,3 +559,11 @@ async def test_streaming_response_export(self, async_client: AsyncCodex) -> None
             assert_matches_type(object, project, path=["response"])
 
         assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_export(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.export(
+                "",
+            )

From d20c3a25adb24c479a031928ef8e05c0997e0322 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 16 Jan 2025 21:18:25 +0000
Subject: [PATCH 023/227] chore(internal): version bump (#19)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index ba6c3483..f14b480a 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.1"
+  ".": "0.1.0-alpha.2"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 81892d12..8411c1eb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.1"
+version = "0.1.0-alpha.2"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 96b3eab2..0ebe32a7 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.1"  # x-release-please-version
+__version__ = "0.1.0-alpha.2"  # x-release-please-version

From b2c701a35df704f4c627adf497ef91473390e7c0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 17 Jan 2025 10:15:53 +0000
Subject: [PATCH 024/227] chore(internal): codegen related update (#20)

---
 mypy.ini               | 2 +-
 requirements-dev.lock  | 4 ++--
 src/codex/_response.py | 8 +++++++-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/mypy.ini b/mypy.ini
index 88ad64a6..92f9243b 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -41,7 +41,7 @@ cache_fine_grained = True
 # ```
 # Changing this codegen to make mypy happy would increase complexity
 # and would not be worth it.
-disable_error_code = func-returns-value
+disable_error_code = func-returns-value,overload-cannot-match
 
 # https://github.com/python/mypy/issues/12162
 [mypy.overrides]
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 47eb5e34..ef078719 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -48,7 +48,7 @@ markdown-it-py==3.0.0
     # via rich
 mdurl==0.1.2
     # via markdown-it-py
-mypy==1.13.0
+mypy==1.14.1
 mypy-extensions==1.0.0
     # via mypy
 nest-asyncio==1.6.0
@@ -68,7 +68,7 @@ pydantic-core==2.27.1
     # via pydantic
 pygments==2.18.0
     # via rich
-pyright==1.1.390
+pyright==1.1.392.post0
 pytest==8.3.3
     # via pytest-asyncio
 pytest-asyncio==0.24.0
diff --git a/src/codex/_response.py b/src/codex/_response.py
index b63926f4..174baacd 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -210,7 +210,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
                 raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
             return cast(R, response)
 
-        if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel):
+        if (
+            inspect.isclass(
+                origin  # pyright: ignore[reportUnknownArgumentType]
+            )
+            and not issubclass(origin, BaseModel)
+            and issubclass(origin, pydantic.BaseModel)
+        ):
             raise TypeError("Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`")
 
         if (

From c42ba57e709b945fde0fc1031ef2e9bd4167e98f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 17 Jan 2025 21:47:24 +0000
Subject: [PATCH 025/227] feat(api): api update (#22)

---
 src/codex/resources/projects/projects.py    | 37 ++++++++++++++++++--
 src/codex/types/project_list_params.py      | 15 +++++++-
 src/codex/types/project_list_response.py    | 38 ++++++++++++++++++---
 src/codex/types/projects/entry.py           |  4 +--
 src/codex/types/users/user_schema_public.py |  2 ++
 tests/api_resources/test_projects.py        | 28 +++++++++++++++
 6 files changed, 114 insertions(+), 10 deletions(-)

diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 8a5f35a1..de366ee8 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 from typing import Optional
+from typing_extensions import Literal
 
 import httpx
 
@@ -195,6 +196,12 @@ def list(
         self,
         *,
         organization_id: str,
+        include_entry_counts: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        query: Optional[str] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -221,7 +228,18 @@ def list(
                 extra_query=extra_query,
                 extra_body=extra_body,
                 timeout=timeout,
-                query=maybe_transform({"organization_id": organization_id}, project_list_params.ProjectListParams),
+                query=maybe_transform(
+                    {
+                        "organization_id": organization_id,
+                        "include_entry_counts": include_entry_counts,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "query": query,
+                        "sort": sort,
+                    },
+                    project_list_params.ProjectListParams,
+                ),
             ),
             cast_to=ProjectListResponse,
         )
@@ -446,6 +464,12 @@ async def list(
         self,
         *,
         organization_id: str,
+        include_entry_counts: bool | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        query: Optional[str] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -473,7 +497,16 @@ async def list(
                 extra_body=extra_body,
                 timeout=timeout,
                 query=await async_maybe_transform(
-                    {"organization_id": organization_id}, project_list_params.ProjectListParams
+                    {
+                        "organization_id": organization_id,
+                        "include_entry_counts": include_entry_counts,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "query": query,
+                        "sort": sort,
+                    },
+                    project_list_params.ProjectListParams,
                 ),
             ),
             cast_to=ProjectListResponse,
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
index 5ca07a82..c2589598 100644
--- a/src/codex/types/project_list_params.py
+++ b/src/codex/types/project_list_params.py
@@ -2,10 +2,23 @@
 
 from __future__ import annotations
 
-from typing_extensions import Required, TypedDict
+from typing import Optional
+from typing_extensions import Literal, Required, TypedDict
 
 __all__ = ["ProjectListParams"]
 
 
 class ProjectListParams(TypedDict, total=False):
     organization_id: Required[str]
+
+    include_entry_counts: bool
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    query: Optional[str]
+
+    sort: Literal["created_at", "updated_at"]
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 9667cf81..84aafa98 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -1,10 +1,38 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import List
-from typing_extensions import TypeAlias
+from typing import List, Optional
+from datetime import datetime
 
-from .project_return_schema import ProjectReturnSchema
+from .._models import BaseModel
 
-__all__ = ["ProjectListResponse"]
+__all__ = ["ProjectListResponse", "Project", "ProjectConfig"]
 
-ProjectListResponse: TypeAlias = List[ProjectReturnSchema]
+
+class ProjectConfig(BaseModel):
+    max_distance: Optional[float] = None
+
+
+class Project(BaseModel):
+    id: str
+
+    config: ProjectConfig
+
+    created_at: datetime
+
+    created_by_user_id: str
+
+    name: str
+
+    organization_id: str
+
+    updated_at: datetime
+
+    description: Optional[str] = None
+
+    unanswered_entries_count: Optional[int] = None
+
+
+class ProjectListResponse(BaseModel):
+    projects: List[Project]
+
+    total_count: int
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index bfa7d278..d3e1fc5f 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -9,12 +9,12 @@
 
 
 class Entry(BaseModel):
-    id: str
-
     created_at: datetime
 
     question: str
 
+    id: Optional[str] = None
+
     answer: Optional[str] = None
 
     answered_at: Optional[datetime] = None
diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py
index 9becf37d..6d35550b 100644
--- a/src/codex/types/users/user_schema_public.py
+++ b/src/codex/types/users/user_schema_public.py
@@ -15,3 +15,5 @@ class UserSchemaPublic(BaseModel):
     email: str
 
     name: Optional[str] = None
+
+    email_verified: Optional[bool] = None
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 1f1f707b..10d23461 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -182,6 +182,20 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        project = client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            include_entry_counts=True,
+            limit=0,
+            offset=0,
+            order="asc",
+            query="query",
+            sort="created_at",
+        )
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
@@ -458,6 +472,20 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            include_entry_counts=True,
+            limit=0,
+            offset=0,
+            order="asc",
+            query="query",
+            sort="created_at",
+        )
+        assert_matches_type(ProjectListResponse, project, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:

From 0fcb67db1b40338842bf2e9767a831df210185b3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 17 Jan 2025 21:53:08 +0000
Subject: [PATCH 026/227] chore(internal): version bump (#23)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f14b480a..aaf968a1 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.2"
+  ".": "0.1.0-alpha.3"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 8411c1eb..e3806276 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.2"
+version = "0.1.0-alpha.3"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 0ebe32a7..4d9fa623 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.2"  # x-release-please-version
+__version__ = "0.1.0-alpha.3"  # x-release-please-version

From be9461a864530f2ec435037e02917ed988ce9ad7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 10:17:53 +0000
Subject: [PATCH 027/227] docs(raw responses): fix duplicate `the` (#24)

---
 src/codex/resources/health.py                      | 4 ++--
 src/codex/resources/organizations/billing.py       | 4 ++--
 src/codex/resources/organizations/organizations.py | 4 ++--
 src/codex/resources/projects/access_keys.py        | 4 ++--
 src/codex/resources/projects/entries.py            | 4 ++--
 src/codex/resources/projects/projects.py           | 4 ++--
 src/codex/resources/users/myself/api_key.py        | 4 ++--
 src/codex/resources/users/myself/myself.py         | 4 ++--
 src/codex/resources/users/myself/organizations.py  | 4 ++--
 src/codex/resources/users/users.py                 | 4 ++--
 10 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py
index b50884e0..6a777c63 100644
--- a/src/codex/resources/health.py
+++ b/src/codex/resources/health.py
@@ -23,7 +23,7 @@ class HealthResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> HealthResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -101,7 +101,7 @@ class AsyncHealthResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncHealthResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py
index 9c916d83..57424963 100644
--- a/src/codex/resources/organizations/billing.py
+++ b/src/codex/resources/organizations/billing.py
@@ -24,7 +24,7 @@ class BillingResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> BillingResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -111,7 +111,7 @@ class AsyncBillingResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncBillingResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py
index e91099e3..73c2542a 100644
--- a/src/codex/resources/organizations/organizations.py
+++ b/src/codex/resources/organizations/organizations.py
@@ -35,7 +35,7 @@ def billing(self) -> BillingResource:
     @cached_property
     def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -93,7 +93,7 @@ def billing(self) -> AsyncBillingResource:
     @cached_property
     def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 8a580f49..bb0dc2ff 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -32,7 +32,7 @@ class AccessKeysResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AccessKeysResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -306,7 +306,7 @@ class AsyncAccessKeysResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 56c893c0..d307d3d0 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -38,7 +38,7 @@ class EntriesResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> EntriesResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -351,7 +351,7 @@ class AsyncEntriesResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index de366ee8..38d7b036 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -56,7 +56,7 @@ def entries(self) -> EntriesResource:
     @cached_property
     def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -324,7 +324,7 @@ def entries(self) -> AsyncEntriesResource:
     @cached_property
     def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py
index c2784b81..d39b124a 100644
--- a/src/codex/resources/users/myself/api_key.py
+++ b/src/codex/resources/users/myself/api_key.py
@@ -23,7 +23,7 @@ class APIKeyResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> APIKeyResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -63,7 +63,7 @@ class AsyncAPIKeyResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py
index ba4548e5..3ee27229 100644
--- a/src/codex/resources/users/myself/myself.py
+++ b/src/codex/resources/users/myself/myself.py
@@ -47,7 +47,7 @@ def organizations(self) -> OrganizationsResource:
     @cached_property
     def with_raw_response(self) -> MyselfResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -95,7 +95,7 @@ def organizations(self) -> AsyncOrganizationsResource:
     @cached_property
     def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py
index 12e799aa..2d5b7127 100644
--- a/src/codex/resources/users/myself/organizations.py
+++ b/src/codex/resources/users/myself/organizations.py
@@ -23,7 +23,7 @@ class OrganizationsResource(SyncAPIResource):
     @cached_property
     def with_raw_response(self) -> OrganizationsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -63,7 +63,7 @@ class AsyncOrganizationsResource(AsyncAPIResource):
     @cached_property
     def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index ea5019c2..8a73b474 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -24,7 +24,7 @@ def myself(self) -> MyselfResource:
     @cached_property
     def with_raw_response(self) -> UsersResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
@@ -49,7 +49,7 @@ def myself(self) -> AsyncMyselfResource:
     @cached_property
     def with_raw_response(self) -> AsyncUsersResourceWithRawResponse:
         """
-        This property can be used as a prefix for any HTTP method call to return the
+        This property can be used as a prefix for any HTTP method call to return
         the raw response object instead of the parsed content.
 
         For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers

From 6ca3c6e0f2b59d9caa2b56d72ca3ad0f89db84be Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 10:18:31 +0000
Subject: [PATCH 028/227] fix(tests): make test_get_platform less flaky (#26)

---
 tests/test_client.py | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index 4e76d238..d000ab1c 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -6,6 +6,7 @@
 import os
 import sys
 import json
+import time
 import asyncio
 import inspect
 import subprocess
@@ -1565,10 +1566,20 @@ async def test_main() -> None:
             [sys.executable, "-c", test_code],
             text=True,
         ) as process:
-            try:
-                process.wait(2)
-                if process.returncode:
-                    raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
-            except subprocess.TimeoutExpired as e:
-                process.kill()
-                raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e
+            timeout = 10  # seconds
+
+            start_time = time.monotonic()
+            while True:
+                return_code = process.poll()
+                if return_code is not None:
+                    if return_code != 0:
+                        raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
+
+                    # success
+                    break
+
+                if time.monotonic() - start_time > timeout:
+                    process.kill()
+                    raise AssertionError("calling get_platform using asyncify resulted in a hung process")
+
+                time.sleep(0.1)

From 20a2d81944dbe2068f9950ce8d30dc17844bcc5a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 10:19:07 +0000
Subject: [PATCH 029/227] chore(internal): avoid pytest-asyncio deprecation
 warning (#27)

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index e3806276..5432262a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -129,6 +129,7 @@ testpaths = ["tests"]
 addopts = "--tb=short"
 xfail_strict = true
 asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "session"
 filterwarnings = [
   "error"
 ]

From 2c7d26367c0fbae4a220192e1c89dbf97f8e1bf5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 17:15:44 +0000
Subject: [PATCH 030/227] feat(api): update api base url (#28)

---
 src/codex/_client.py | 4 ++--
 tests/test_client.py | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index d9e44320..884d2f3d 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -50,8 +50,8 @@
 ]
 
 ENVIRONMENTS: Dict[str, str] = {
-    "production": "https://api-alpha-o3gxj3oajfu.cleanlab.ai",
-    "staging": "https://api-alpha-staging-o3gxj3oajfu.cleanlab.ai",
+    "production": "https://api-codex-o3gxj3oajfu.cleanlab.ai",
+    "staging": "https://api-codex-staging-o3gxj3oajfu.cleanlab.ai",
     "local": "http://localhost:8080",
 }
 
diff --git a/tests/test_client.py b/tests/test_client.py
index d000ab1c..3eaa6536 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -522,7 +522,7 @@ def test_base_url_env(self) -> None:
                 Codex(_strict_response_validation=True, environment="production")
 
             client = Codex(base_url=None, _strict_response_validation=True, environment="production")
-            assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
+            assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
@@ -1263,7 +1263,7 @@ def test_base_url_env(self) -> None:
                 AsyncCodex(_strict_response_validation=True, environment="production")
 
             client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
-            assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai")
+            assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",

From b4cb80f3c77dec784ab7bc1d07c5a0a792e9b640 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jan 2025 17:54:27 +0000
Subject: [PATCH 031/227] chore(internal): version bump (#29)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index aaf968a1..b56c3d0b 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.3"
+  ".": "0.1.0-alpha.4"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 5432262a..a4e19e5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.3"
+version = "0.1.0-alpha.4"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 4d9fa623..751ac5dd 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.3"  # x-release-please-version
+__version__ = "0.1.0-alpha.4"  # x-release-please-version

From da6fd17de98a6b7d6bfaf351897af5650b730e72 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 01:25:08 +0000
Subject: [PATCH 032/227] feat(api): api update (#30)

---
 src/codex/types/users/user_schema.py        | 16 +++++++++++++++-
 src/codex/types/users/user_schema_public.py | 10 +++++++++-
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py
index ec6ad541..b1665f21 100644
--- a/src/codex/types/users/user_schema.py
+++ b/src/codex/types/users/user_schema.py
@@ -11,14 +11,28 @@
 class UserSchema(BaseModel):
     id: str
 
+    account_activated_at: Optional[datetime] = None
+
     api_key: str
 
     api_key_timestamp: datetime
 
     created_at: datetime
 
+    discovery_source: Optional[str] = None
+
     email: str
 
+    updated_at: datetime
+
+    user_provided_company_name: Optional[str] = None
+
+    first_name: Optional[str] = None
+
+    is_account_activated: Optional[bool] = None
+
+    last_name: Optional[str] = None
+
     name: Optional[str] = None
 
-    updated_at: datetime
+    phone_number: Optional[str] = None
diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py
index 6d35550b..181113b0 100644
--- a/src/codex/types/users/user_schema_public.py
+++ b/src/codex/types/users/user_schema_public.py
@@ -14,6 +14,14 @@ class UserSchemaPublic(BaseModel):
 
     email: str
 
+    email_verified: Optional[bool] = None
+
+    first_name: Optional[str] = None
+
+    is_account_activated: Optional[bool] = None
+
+    last_name: Optional[str] = None
+
     name: Optional[str] = None
 
-    email_verified: Optional[bool] = None
+    phone_number: Optional[str] = None

From 31b206bb301bd5c844f3c5affafc24742b204a8b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 01:27:40 +0000
Subject: [PATCH 033/227] feat(api): update prod api url (#32)

---
 src/codex/_client.py | 2 +-
 tests/test_client.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index 884d2f3d..74f0c871 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -50,7 +50,7 @@
 ]
 
 ENVIRONMENTS: Dict[str, str] = {
-    "production": "https://api-codex-o3gxj3oajfu.cleanlab.ai",
+    "production": "https://api-codex.cleanlab.ai",
     "staging": "https://api-codex-staging-o3gxj3oajfu.cleanlab.ai",
     "local": "http://localhost:8080",
 }
diff --git a/tests/test_client.py b/tests/test_client.py
index 3eaa6536..b421541f 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -522,7 +522,7 @@ def test_base_url_env(self) -> None:
                 Codex(_strict_response_validation=True, environment="production")
 
             client = Codex(base_url=None, _strict_response_validation=True, environment="production")
-            assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai")
+            assert str(client.base_url).startswith("https://api-codex.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
@@ -1263,7 +1263,7 @@ def test_base_url_env(self) -> None:
                 AsyncCodex(_strict_response_validation=True, environment="production")
 
             client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
-            assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai")
+            assert str(client.base_url).startswith("https://api-codex.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",

From 2327c11e83028eaca6acc0582a88199962a4524a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 01:35:54 +0000
Subject: [PATCH 034/227] feat(api): api update (#33)

---
 api.md                                              |  2 +-
 src/codex/resources/projects/access_keys.py         |  9 +++++----
 .../access_key_retrieve_project_id_response.py      |  7 +++++--
 tests/api_resources/projects/test_access_keys.py    | 13 +++++++------
 4 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/api.md b/api.md
index 48af8b68..d4720cc2 100644
--- a/api.md
+++ b/api.md
@@ -108,7 +108,7 @@ Methods:
 - <code title="put /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">update</a>(access_key_id, \*, project_id, \*\*<a href="src/codex/types/projects/access_key_update_params.py">params</a>) -> <a href="./src/codex/types/projects/access_key_schema.py">AccessKeySchema</a></code>
 - <code title="get /api/projects/{project_id}/access_keys/">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">list</a>(project_id) -> <a href="./src/codex/types/projects/access_key_list_response.py">AccessKeyListResponse</a></code>
 - <code title="delete /api/projects/{project_id}/access_keys/{access_key_id}">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">delete</a>(access_key_id, \*, project_id) -> None</code>
-- <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> str</code>
+- <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> <a href="./src/codex/types/projects/access_key_retrieve_project_id_response.py">AccessKeyRetrieveProjectIDResponse</a></code>
 - <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
 
 ## Entries
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index bb0dc2ff..d375dbca 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -24,6 +24,7 @@
 from ...types.projects import access_key_create_params, access_key_update_params
 from ...types.projects.access_key_schema import AccessKeySchema
 from ...types.projects.access_key_list_response import AccessKeyListResponse
+from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse
 
 __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"]
 
@@ -254,14 +255,14 @@ def retrieve_project_id(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> str:
+    ) -> AccessKeyRetrieveProjectIDResponse:
         """Get the project ID from an access key."""
         return self._get(
             "/api/projects/id_from_access_key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=str,
+            cast_to=AccessKeyRetrieveProjectIDResponse,
         )
 
     def revoke(
@@ -528,14 +529,14 @@ async def retrieve_project_id(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> str:
+    ) -> AccessKeyRetrieveProjectIDResponse:
         """Get the project ID from an access key."""
         return await self._get(
             "/api/projects/id_from_access_key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=str,
+            cast_to=AccessKeyRetrieveProjectIDResponse,
         )
 
     async def revoke(
diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py
index b7b53137..22775083 100644
--- a/src/codex/types/projects/access_key_retrieve_project_id_response.py
+++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py
@@ -1,7 +1,10 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing_extensions import TypeAlias
+
+from ..._models import BaseModel
 
 __all__ = ["AccessKeyRetrieveProjectIDResponse"]
 
-AccessKeyRetrieveProjectIDResponse: TypeAlias = str
+
+class AccessKeyRetrieveProjectIDResponse(BaseModel):
+    project_id: str
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 6ec8ef9b..240c31e1 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -13,6 +13,7 @@
 from codex.types.projects import (
     AccessKeySchema,
     AccessKeyListResponse,
+    AccessKeyRetrieveProjectIDResponse,
 )
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -297,7 +298,7 @@ def test_path_params_delete(self, client: Codex) -> None:
     @parametrize
     def test_method_retrieve_project_id(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve_project_id()
-        assert_matches_type(str, access_key, path=["response"])
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -307,7 +308,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         access_key = response.parse()
-        assert_matches_type(str, access_key, path=["response"])
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -317,7 +318,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             access_key = response.parse()
-            assert_matches_type(str, access_key, path=["response"])
+            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -653,7 +654,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve_project_id()
-        assert_matches_type(str, access_key, path=["response"])
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -663,7 +664,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex)
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         access_key = await response.parse()
-        assert_matches_type(str, access_key, path=["response"])
+        assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -673,7 +674,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             access_key = await response.parse()
-            assert_matches_type(str, access_key, path=["response"])
+            assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 

From 65402e100d0af03996a1b5b8316f7b25de624d44 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 01:38:06 +0000
Subject: [PATCH 035/227] chore(internal): version bump (#34)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b56c3d0b..e8285b71 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.4"
+  ".": "0.1.0-alpha.5"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index a4e19e5d..3c92ef48 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.4"
+version = "0.1.0-alpha.5"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 751ac5dd..134b2cab 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.4"  # x-release-please-version
+__version__ = "0.1.0-alpha.5"  # x-release-please-version

From 5168c56d2221cde28945e6b55da5483b3a028a33 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 10:17:53 +0000
Subject: [PATCH 036/227] chore(internal): minor style changes (#35)

---
 src/codex/_response.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/codex/_response.py b/src/codex/_response.py
index 174baacd..6e7c1cdf 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -136,6 +136,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
         if cast_to and is_annotated_type(cast_to):
             cast_to = extract_type_arg(cast_to, 0)
 
+        origin = get_origin(cast_to) or cast_to
+
         if self._is_sse_stream:
             if to:
                 if not is_stream_class_type(to):
@@ -195,8 +197,6 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
         if cast_to == bool:
             return cast(R, response.text.lower() == "true")
 
-        origin = get_origin(cast_to) or cast_to
-
         if origin == APIResponse:
             raise RuntimeError("Unexpected state - cast_to is `APIResponse`")
 

From 3c7a2493ba505d3faed7473df43a121abb933735 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 03:29:38 +0000
Subject: [PATCH 037/227] chore(internal): minor formatting changes (#37)

---
 .github/workflows/ci.yml | 3 ---
 scripts/bootstrap        | 2 +-
 scripts/lint             | 1 -
 3 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 565ec95e..e503784c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,6 @@ jobs:
   lint:
     name: lint
     runs-on: ubuntu-latest
-    
 
     steps:
       - uses: actions/checkout@v4
@@ -30,5 +29,3 @@ jobs:
 
       - name: Run lints
         run: ./scripts/lint
-
-
diff --git a/scripts/bootstrap b/scripts/bootstrap
index 8c5c60eb..e84fe62c 100755
--- a/scripts/bootstrap
+++ b/scripts/bootstrap
@@ -4,7 +4,7 @@ set -e
 
 cd "$(dirname "$0")/.."
 
-if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
+if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
   brew bundle check >/dev/null 2>&1 || {
     echo "==> Installing Homebrew dependencies…"
     brew bundle
diff --git a/scripts/lint b/scripts/lint
index 6f2bb0d0..adfc6acf 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -9,4 +9,3 @@ rye run lint
 
 echo "==> Making sure it imports"
 rye run python -c 'import codex'
-

From b2ec929247854ae239eebebf1055d9442f13cdd1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 18:44:47 +0000
Subject: [PATCH 038/227] feat(api): manual updates (#38)

---
 README.md            |  2 +-
 src/codex/_client.py | 24 ++++++++++++------------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 7b5fda1d..56bc216a 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ print(project_return_schema.id)
 
 While you can provide a `bearer_token` keyword argument,
 we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
-to add `BEARER_TOKEN="My Bearer Token"` to your `.env` file
+to add `CODEX_BEARER_TOKEN="My Bearer Token"` to your `.env` file
 so that your Bearer Token is not stored in source control.
 
 ## Async usage
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 74f0c871..fc0c27bf 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -100,20 +100,20 @@ def __init__(
         """Construct a new synchronous Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `bearer_token` from `BEARER_TOKEN`
-        - `api_key` from `AUTHENTICATED_API_KEY`
-        - `access_key` from `PUBLIC_ACCESS_KEY`
+        - `bearer_token` from `CODEX_BEARER_TOKEN`
+        - `api_key` from `CODEX_API_KEY`
+        - `access_key` from `CODEX_ACCESS_KEY`
         """
         if bearer_token is None:
-            bearer_token = os.environ.get("BEARER_TOKEN")
+            bearer_token = os.environ.get("CODEX_BEARER_TOKEN")
         self.bearer_token = bearer_token
 
         if api_key is None:
-            api_key = os.environ.get("AUTHENTICATED_API_KEY")
+            api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
 
         if access_key is None:
-            access_key = os.environ.get("PUBLIC_ACCESS_KEY")
+            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment
@@ -362,20 +362,20 @@ def __init__(
         """Construct a new async Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `bearer_token` from `BEARER_TOKEN`
-        - `api_key` from `AUTHENTICATED_API_KEY`
-        - `access_key` from `PUBLIC_ACCESS_KEY`
+        - `bearer_token` from `CODEX_BEARER_TOKEN`
+        - `api_key` from `CODEX_API_KEY`
+        - `access_key` from `CODEX_ACCESS_KEY`
         """
         if bearer_token is None:
-            bearer_token = os.environ.get("BEARER_TOKEN")
+            bearer_token = os.environ.get("CODEX_BEARER_TOKEN")
         self.bearer_token = bearer_token
 
         if api_key is None:
-            api_key = os.environ.get("AUTHENTICATED_API_KEY")
+            api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
 
         if access_key is None:
-            access_key = os.environ.get("PUBLIC_ACCESS_KEY")
+            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment

From 0d6f590e6d3ba7ac5256fbabd8ca372f0756d043 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 24 Jan 2025 19:35:19 +0000
Subject: [PATCH 039/227] chore(internal): version bump (#39)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index e8285b71..4f9005ea 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.5"
+  ".": "0.1.0-alpha.6"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 3c92ef48..0310d2c1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.5"
+version = "0.1.0-alpha.6"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 134b2cab..559cb92d 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.5"  # x-release-please-version
+__version__ = "0.1.0-alpha.6"  # x-release-please-version

From 5a94c5f64dfa44a7151e74483be76997385e6d60 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 27 Jan 2025 22:57:11 +0000
Subject: [PATCH 040/227] feat(api): publish to PyPI (#40)

---
 README.md | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 56bc216a..42ef0af5 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,10 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean
 ## Installation
 
 ```sh
-# install from the production repo
-pip install git+ssh://git@github.com/cleanlab/codex-python.git
+# install from PyPI
+pip install --pre codex-sdk
 ```
 
-> [!NOTE]
-> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex-sdk`
-
 ## Usage
 
 The full API of this library can be found in [api.md](api.md).

From a807e3a30547b1059b87afcf5c83d1a475f09047 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 27 Jan 2025 22:59:01 +0000
Subject: [PATCH 041/227] chore(internal): version bump (#42)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 4f9005ea..b5db7ce1 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.6"
+  ".": "0.1.0-alpha.7"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 0310d2c1..6aa0de59 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.6"
+version = "0.1.0-alpha.7"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 559cb92d..4fb336bd 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.6"  # x-release-please-version
+__version__ = "0.1.0-alpha.7"  # x-release-please-version

From 47f0f2d82f5f204a9607adff304b947c76a87ee2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 28 Jan 2025 00:47:23 +0000
Subject: [PATCH 042/227] feat(api): add new endpoints (#43)

---
 .stats.yml                                    |   2 +-
 api.md                                        |  53 +++++-
 .../organizations/billing/__init__.py         |  61 +++++++
 .../organizations/{ => billing}/billing.py    | 110 +++++++++++-
 .../organizations/billing/card_details.py     | 165 +++++++++++++++++
 .../organizations/billing/plan_details.py     | 167 ++++++++++++++++++
 .../organizations/billing/setup_intent.py     | 163 +++++++++++++++++
 .../resources/organizations/organizations.py  |  16 +-
 src/codex/resources/users/myself/api_key.py   |  51 ++++++
 src/codex/resources/users/myself/myself.py    |  10 +-
 src/codex/resources/users/users.py            | 133 ++++++++++++++
 src/codex/types/__init__.py                   |   2 +
 .../types/organizations/billing/__init__.py   |   7 +
 .../organization_billing_card_details.py      |  39 ++++
 .../organization_billing_plan_details.py      |  30 ++++
 .../organization_billing_setup_intent.py      |  12 ++
 .../{users/user_schema_public.py => user.py}  |   6 +-
 .../types/user_activate_account_params.py     |  27 +++
 src/codex/types/users/__init__.py             |   1 -
 .../organizations/billing/__init__.py         |   1 +
 .../billing/test_card_details.py              | 106 +++++++++++
 .../billing/test_plan_details.py              | 106 +++++++++++
 .../billing/test_setup_intent.py              | 106 +++++++++++
 tests/api_resources/test_users.py             | 125 +++++++++++++
 .../users/myself/test_api_key.py              |  57 ++++++
 tests/api_resources/users/test_myself.py      |  14 +-
 26 files changed, 1535 insertions(+), 35 deletions(-)
 create mode 100644 src/codex/resources/organizations/billing/__init__.py
 rename src/codex/resources/organizations/{ => billing}/billing.py (68%)
 create mode 100644 src/codex/resources/organizations/billing/card_details.py
 create mode 100644 src/codex/resources/organizations/billing/plan_details.py
 create mode 100644 src/codex/resources/organizations/billing/setup_intent.py
 create mode 100644 src/codex/types/organizations/billing/__init__.py
 create mode 100644 src/codex/types/organizations/billing/organization_billing_card_details.py
 create mode 100644 src/codex/types/organizations/billing/organization_billing_plan_details.py
 create mode 100644 src/codex/types/organizations/billing/organization_billing_setup_intent.py
 rename src/codex/types/{users/user_schema_public.py => user.py} (80%)
 create mode 100644 src/codex/types/user_activate_account_params.py
 create mode 100644 tests/api_resources/organizations/billing/__init__.py
 create mode 100644 tests/api_resources/organizations/billing/test_card_details.py
 create mode 100644 tests/api_resources/organizations/billing/test_plan_details.py
 create mode 100644 tests/api_resources/organizations/billing/test_setup_intent.py
 create mode 100644 tests/api_resources/test_users.py

diff --git a/.stats.yml b/.stats.yml
index 3a1d4dfc..966331a1 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 29
+configured_endpoints: 34
diff --git a/api.md b/api.md
index d4720cc2..b2fc2f00 100644
--- a/api.md
+++ b/api.md
@@ -37,11 +37,57 @@ from codex.types.organizations import (
 
 Methods:
 
-- <code title="get /api/organizations/{organization_id}/billing/invoices">client.organizations.billing.<a href="./src/codex/resources/organizations/billing.py">invoices</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_invoices_schema.py">OrganizationBillingInvoicesSchema</a></code>
-- <code title="get /api/organizations/{organization_id}/billing/usage">client.organizations.billing.<a href="./src/codex/resources/organizations/billing.py">usage</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_usage_schema.py">OrganizationBillingUsageSchema</a></code>
+- <code title="get /api/organizations/{organization_id}/billing/invoices">client.organizations.billing.<a href="./src/codex/resources/organizations/billing/billing.py">invoices</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_invoices_schema.py">OrganizationBillingInvoicesSchema</a></code>
+- <code title="get /api/organizations/{organization_id}/billing/usage">client.organizations.billing.<a href="./src/codex/resources/organizations/billing/billing.py">usage</a>(organization_id) -> <a href="./src/codex/types/organizations/organization_billing_usage_schema.py">OrganizationBillingUsageSchema</a></code>
+
+### CardDetails
+
+Types:
+
+```python
+from codex.types.organizations.billing import OrganizationBillingCardDetails
+```
+
+Methods:
+
+- <code title="get /api/organizations/{organization_id}/billing/card-details">client.organizations.billing.card_details.<a href="./src/codex/resources/organizations/billing/card_details.py">retrieve</a>(organization_id) -> <a href="./src/codex/types/organizations/billing/organization_billing_card_details.py">Optional[OrganizationBillingCardDetails]</a></code>
+
+### SetupIntent
+
+Types:
+
+```python
+from codex.types.organizations.billing import OrganizationBillingSetupIntent
+```
+
+Methods:
+
+- <code title="post /api/organizations/{organization_id}/billing/setup-intent">client.organizations.billing.setup_intent.<a href="./src/codex/resources/organizations/billing/setup_intent.py">create</a>(organization_id) -> <a href="./src/codex/types/organizations/billing/organization_billing_setup_intent.py">OrganizationBillingSetupIntent</a></code>
+
+### PlanDetails
+
+Types:
+
+```python
+from codex.types.organizations.billing import OrganizationBillingPlanDetails
+```
+
+Methods:
+
+- <code title="get /api/organizations/{organization_id}/billing/plan-details">client.organizations.billing.plan_details.<a href="./src/codex/resources/organizations/billing/plan_details.py">retrieve</a>(organization_id) -> <a href="./src/codex/types/organizations/billing/organization_billing_plan_details.py">OrganizationBillingPlanDetails</a></code>
 
 # Users
 
+Types:
+
+```python
+from codex.types import User
+```
+
+Methods:
+
+- <code title="patch /api/users/activate_account">client.users.<a href="./src/codex/resources/users/users.py">activate_account</a>(\*\*<a href="src/codex/types/user_activate_account_params.py">params</a>) -> <a href="./src/codex/types/user.py">User</a></code>
+
 ## Myself
 
 Types:
@@ -52,12 +98,13 @@ from codex.types.users import UserSchema, UserSchemaPublic
 
 Methods:
 
-- <code title="get /api/users/myself">client.users.myself.<a href="./src/codex/resources/users/myself/myself.py">retrieve</a>() -> <a href="./src/codex/types/users/user_schema_public.py">UserSchemaPublic</a></code>
+- <code title="get /api/users/myself">client.users.myself.<a href="./src/codex/resources/users/myself/myself.py">retrieve</a>() -> <a href="./src/codex/types/user.py">User</a></code>
 
 ### APIKey
 
 Methods:
 
+- <code title="get /api/users/myself/api-key">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">retrieve</a>() -> <a href="./src/codex/types/user.py">User</a></code>
 - <code title="post /api/users/myself/api-key/refresh">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">refresh</a>() -> <a href="./src/codex/types/users/user_schema.py">UserSchema</a></code>
 
 ### Organizations
diff --git a/src/codex/resources/organizations/billing/__init__.py b/src/codex/resources/organizations/billing/__init__.py
new file mode 100644
index 00000000..c64f6378
--- /dev/null
+++ b/src/codex/resources/organizations/billing/__init__.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .billing import (
+    BillingResource,
+    AsyncBillingResource,
+    BillingResourceWithRawResponse,
+    AsyncBillingResourceWithRawResponse,
+    BillingResourceWithStreamingResponse,
+    AsyncBillingResourceWithStreamingResponse,
+)
+from .card_details import (
+    CardDetailsResource,
+    AsyncCardDetailsResource,
+    CardDetailsResourceWithRawResponse,
+    AsyncCardDetailsResourceWithRawResponse,
+    CardDetailsResourceWithStreamingResponse,
+    AsyncCardDetailsResourceWithStreamingResponse,
+)
+from .plan_details import (
+    PlanDetailsResource,
+    AsyncPlanDetailsResource,
+    PlanDetailsResourceWithRawResponse,
+    AsyncPlanDetailsResourceWithRawResponse,
+    PlanDetailsResourceWithStreamingResponse,
+    AsyncPlanDetailsResourceWithStreamingResponse,
+)
+from .setup_intent import (
+    SetupIntentResource,
+    AsyncSetupIntentResource,
+    SetupIntentResourceWithRawResponse,
+    AsyncSetupIntentResourceWithRawResponse,
+    SetupIntentResourceWithStreamingResponse,
+    AsyncSetupIntentResourceWithStreamingResponse,
+)
+
+__all__ = [
+    "CardDetailsResource",
+    "AsyncCardDetailsResource",
+    "CardDetailsResourceWithRawResponse",
+    "AsyncCardDetailsResourceWithRawResponse",
+    "CardDetailsResourceWithStreamingResponse",
+    "AsyncCardDetailsResourceWithStreamingResponse",
+    "SetupIntentResource",
+    "AsyncSetupIntentResource",
+    "SetupIntentResourceWithRawResponse",
+    "AsyncSetupIntentResourceWithRawResponse",
+    "SetupIntentResourceWithStreamingResponse",
+    "AsyncSetupIntentResourceWithStreamingResponse",
+    "PlanDetailsResource",
+    "AsyncPlanDetailsResource",
+    "PlanDetailsResourceWithRawResponse",
+    "AsyncPlanDetailsResourceWithRawResponse",
+    "PlanDetailsResourceWithStreamingResponse",
+    "AsyncPlanDetailsResourceWithStreamingResponse",
+    "BillingResource",
+    "AsyncBillingResource",
+    "BillingResourceWithRawResponse",
+    "AsyncBillingResourceWithRawResponse",
+    "BillingResourceWithStreamingResponse",
+    "AsyncBillingResourceWithStreamingResponse",
+]
diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing/billing.py
similarity index 68%
rename from src/codex/resources/organizations/billing.py
rename to src/codex/resources/organizations/billing/billing.py
index 57424963..79c2a5c9 100644
--- a/src/codex/resources/organizations/billing.py
+++ b/src/codex/resources/organizations/billing/billing.py
@@ -4,23 +4,59 @@
 
 import httpx
 
-from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
     to_raw_response_wrapper,
     to_streamed_response_wrapper,
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ..._base_client import make_request_options
-from ...types.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema
-from ...types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema
+from .card_details import (
+    CardDetailsResource,
+    AsyncCardDetailsResource,
+    CardDetailsResourceWithRawResponse,
+    AsyncCardDetailsResourceWithRawResponse,
+    CardDetailsResourceWithStreamingResponse,
+    AsyncCardDetailsResourceWithStreamingResponse,
+)
+from .plan_details import (
+    PlanDetailsResource,
+    AsyncPlanDetailsResource,
+    PlanDetailsResourceWithRawResponse,
+    AsyncPlanDetailsResourceWithRawResponse,
+    PlanDetailsResourceWithStreamingResponse,
+    AsyncPlanDetailsResourceWithStreamingResponse,
+)
+from .setup_intent import (
+    SetupIntentResource,
+    AsyncSetupIntentResource,
+    SetupIntentResourceWithRawResponse,
+    AsyncSetupIntentResourceWithRawResponse,
+    SetupIntentResourceWithStreamingResponse,
+    AsyncSetupIntentResourceWithStreamingResponse,
+)
+from ...._base_client import make_request_options
+from ....types.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema
+from ....types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema
 
 __all__ = ["BillingResource", "AsyncBillingResource"]
 
 
 class BillingResource(SyncAPIResource):
+    @cached_property
+    def card_details(self) -> CardDetailsResource:
+        return CardDetailsResource(self._client)
+
+    @cached_property
+    def setup_intent(self) -> SetupIntentResource:
+        return SetupIntentResource(self._client)
+
+    @cached_property
+    def plan_details(self) -> PlanDetailsResource:
+        return PlanDetailsResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> BillingResourceWithRawResponse:
         """
@@ -108,6 +144,18 @@ def usage(
 
 
 class AsyncBillingResource(AsyncAPIResource):
+    @cached_property
+    def card_details(self) -> AsyncCardDetailsResource:
+        return AsyncCardDetailsResource(self._client)
+
+    @cached_property
+    def setup_intent(self) -> AsyncSetupIntentResource:
+        return AsyncSetupIntentResource(self._client)
+
+    @cached_property
+    def plan_details(self) -> AsyncPlanDetailsResource:
+        return AsyncPlanDetailsResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> AsyncBillingResourceWithRawResponse:
         """
@@ -205,6 +253,18 @@ def __init__(self, billing: BillingResource) -> None:
             billing.usage,
         )
 
+    @cached_property
+    def card_details(self) -> CardDetailsResourceWithRawResponse:
+        return CardDetailsResourceWithRawResponse(self._billing.card_details)
+
+    @cached_property
+    def setup_intent(self) -> SetupIntentResourceWithRawResponse:
+        return SetupIntentResourceWithRawResponse(self._billing.setup_intent)
+
+    @cached_property
+    def plan_details(self) -> PlanDetailsResourceWithRawResponse:
+        return PlanDetailsResourceWithRawResponse(self._billing.plan_details)
+
 
 class AsyncBillingResourceWithRawResponse:
     def __init__(self, billing: AsyncBillingResource) -> None:
@@ -217,6 +277,18 @@ def __init__(self, billing: AsyncBillingResource) -> None:
             billing.usage,
         )
 
+    @cached_property
+    def card_details(self) -> AsyncCardDetailsResourceWithRawResponse:
+        return AsyncCardDetailsResourceWithRawResponse(self._billing.card_details)
+
+    @cached_property
+    def setup_intent(self) -> AsyncSetupIntentResourceWithRawResponse:
+        return AsyncSetupIntentResourceWithRawResponse(self._billing.setup_intent)
+
+    @cached_property
+    def plan_details(self) -> AsyncPlanDetailsResourceWithRawResponse:
+        return AsyncPlanDetailsResourceWithRawResponse(self._billing.plan_details)
+
 
 class BillingResourceWithStreamingResponse:
     def __init__(self, billing: BillingResource) -> None:
@@ -229,6 +301,18 @@ def __init__(self, billing: BillingResource) -> None:
             billing.usage,
         )
 
+    @cached_property
+    def card_details(self) -> CardDetailsResourceWithStreamingResponse:
+        return CardDetailsResourceWithStreamingResponse(self._billing.card_details)
+
+    @cached_property
+    def setup_intent(self) -> SetupIntentResourceWithStreamingResponse:
+        return SetupIntentResourceWithStreamingResponse(self._billing.setup_intent)
+
+    @cached_property
+    def plan_details(self) -> PlanDetailsResourceWithStreamingResponse:
+        return PlanDetailsResourceWithStreamingResponse(self._billing.plan_details)
+
 
 class AsyncBillingResourceWithStreamingResponse:
     def __init__(self, billing: AsyncBillingResource) -> None:
@@ -240,3 +324,15 @@ def __init__(self, billing: AsyncBillingResource) -> None:
         self.usage = async_to_streamed_response_wrapper(
             billing.usage,
         )
+
+    @cached_property
+    def card_details(self) -> AsyncCardDetailsResourceWithStreamingResponse:
+        return AsyncCardDetailsResourceWithStreamingResponse(self._billing.card_details)
+
+    @cached_property
+    def setup_intent(self) -> AsyncSetupIntentResourceWithStreamingResponse:
+        return AsyncSetupIntentResourceWithStreamingResponse(self._billing.setup_intent)
+
+    @cached_property
+    def plan_details(self) -> AsyncPlanDetailsResourceWithStreamingResponse:
+        return AsyncPlanDetailsResourceWithStreamingResponse(self._billing.plan_details)
diff --git a/src/codex/resources/organizations/billing/card_details.py b/src/codex/resources/organizations/billing/card_details.py
new file mode 100644
index 00000000..94cb8a70
--- /dev/null
+++ b/src/codex/resources/organizations/billing/card_details.py
@@ -0,0 +1,165 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.organizations.billing.organization_billing_card_details import OrganizationBillingCardDetails
+
+__all__ = ["CardDetailsResource", "AsyncCardDetailsResource"]
+
+
+class CardDetailsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> CardDetailsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return CardDetailsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> CardDetailsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return CardDetailsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[OrganizationBillingCardDetails]:
+        """
+        Get card details for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/billing/card-details",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingCardDetails,
+        )
+
+
+class AsyncCardDetailsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncCardDetailsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncCardDetailsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncCardDetailsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncCardDetailsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Optional[OrganizationBillingCardDetails]:
+        """
+        Get card details for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/billing/card-details",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingCardDetails,
+        )
+
+
+class CardDetailsResourceWithRawResponse:
+    def __init__(self, card_details: CardDetailsResource) -> None:
+        self._card_details = card_details
+
+        self.retrieve = to_raw_response_wrapper(
+            card_details.retrieve,
+        )
+
+
+class AsyncCardDetailsResourceWithRawResponse:
+    def __init__(self, card_details: AsyncCardDetailsResource) -> None:
+        self._card_details = card_details
+
+        self.retrieve = async_to_raw_response_wrapper(
+            card_details.retrieve,
+        )
+
+
+class CardDetailsResourceWithStreamingResponse:
+    def __init__(self, card_details: CardDetailsResource) -> None:
+        self._card_details = card_details
+
+        self.retrieve = to_streamed_response_wrapper(
+            card_details.retrieve,
+        )
+
+
+class AsyncCardDetailsResourceWithStreamingResponse:
+    def __init__(self, card_details: AsyncCardDetailsResource) -> None:
+        self._card_details = card_details
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            card_details.retrieve,
+        )
diff --git a/src/codex/resources/organizations/billing/plan_details.py b/src/codex/resources/organizations/billing/plan_details.py
new file mode 100644
index 00000000..6ff726e2
--- /dev/null
+++ b/src/codex/resources/organizations/billing/plan_details.py
@@ -0,0 +1,167 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.organizations.billing.organization_billing_plan_details import OrganizationBillingPlanDetails
+
+__all__ = ["PlanDetailsResource", "AsyncPlanDetailsResource"]
+
+
+class PlanDetailsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> PlanDetailsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return PlanDetailsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> PlanDetailsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return PlanDetailsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingPlanDetails:
+        """
+        Get plan details for an organization.
+
+        This includes the plan name,
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/billing/plan-details",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingPlanDetails,
+        )
+
+
+class AsyncPlanDetailsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncPlanDetailsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncPlanDetailsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncPlanDetailsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncPlanDetailsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingPlanDetails:
+        """
+        Get plan details for an organization.
+
+        This includes the plan name,
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/billing/plan-details",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingPlanDetails,
+        )
+
+
+class PlanDetailsResourceWithRawResponse:
+    def __init__(self, plan_details: PlanDetailsResource) -> None:
+        self._plan_details = plan_details
+
+        self.retrieve = to_raw_response_wrapper(
+            plan_details.retrieve,
+        )
+
+
+class AsyncPlanDetailsResourceWithRawResponse:
+    def __init__(self, plan_details: AsyncPlanDetailsResource) -> None:
+        self._plan_details = plan_details
+
+        self.retrieve = async_to_raw_response_wrapper(
+            plan_details.retrieve,
+        )
+
+
+class PlanDetailsResourceWithStreamingResponse:
+    def __init__(self, plan_details: PlanDetailsResource) -> None:
+        self._plan_details = plan_details
+
+        self.retrieve = to_streamed_response_wrapper(
+            plan_details.retrieve,
+        )
+
+
+class AsyncPlanDetailsResourceWithStreamingResponse:
+    def __init__(self, plan_details: AsyncPlanDetailsResource) -> None:
+        self._plan_details = plan_details
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            plan_details.retrieve,
+        )
diff --git a/src/codex/resources/organizations/billing/setup_intent.py b/src/codex/resources/organizations/billing/setup_intent.py
new file mode 100644
index 00000000..ba915c6a
--- /dev/null
+++ b/src/codex/resources/organizations/billing/setup_intent.py
@@ -0,0 +1,163 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.organizations.billing.organization_billing_setup_intent import OrganizationBillingSetupIntent
+
+__all__ = ["SetupIntentResource", "AsyncSetupIntentResource"]
+
+
+class SetupIntentResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> SetupIntentResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return SetupIntentResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> SetupIntentResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return SetupIntentResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingSetupIntent:
+        """
+        Create a setup intent for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._post(
+            f"/api/organizations/{organization_id}/billing/setup-intent",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingSetupIntent,
+        )
+
+
+class AsyncSetupIntentResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncSetupIntentResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncSetupIntentResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncSetupIntentResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncSetupIntentResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationBillingSetupIntent:
+        """
+        Create a setup intent for an organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._post(
+            f"/api/organizations/{organization_id}/billing/setup-intent",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationBillingSetupIntent,
+        )
+
+
+class SetupIntentResourceWithRawResponse:
+    def __init__(self, setup_intent: SetupIntentResource) -> None:
+        self._setup_intent = setup_intent
+
+        self.create = to_raw_response_wrapper(
+            setup_intent.create,
+        )
+
+
+class AsyncSetupIntentResourceWithRawResponse:
+    def __init__(self, setup_intent: AsyncSetupIntentResource) -> None:
+        self._setup_intent = setup_intent
+
+        self.create = async_to_raw_response_wrapper(
+            setup_intent.create,
+        )
+
+
+class SetupIntentResourceWithStreamingResponse:
+    def __init__(self, setup_intent: SetupIntentResource) -> None:
+        self._setup_intent = setup_intent
+
+        self.create = to_streamed_response_wrapper(
+            setup_intent.create,
+        )
+
+
+class AsyncSetupIntentResourceWithStreamingResponse:
+    def __init__(self, setup_intent: AsyncSetupIntentResource) -> None:
+        self._setup_intent = setup_intent
+
+        self.create = async_to_streamed_response_wrapper(
+            setup_intent.create,
+        )
diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py
index 73c2542a..025ecba4 100644
--- a/src/codex/resources/organizations/organizations.py
+++ b/src/codex/resources/organizations/organizations.py
@@ -4,14 +4,6 @@
 
 import httpx
 
-from .billing import (
-    BillingResource,
-    AsyncBillingResource,
-    BillingResourceWithRawResponse,
-    AsyncBillingResourceWithRawResponse,
-    BillingResourceWithStreamingResponse,
-    AsyncBillingResourceWithStreamingResponse,
-)
 from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -22,6 +14,14 @@
     async_to_streamed_response_wrapper,
 )
 from ..._base_client import make_request_options
+from .billing.billing import (
+    BillingResource,
+    AsyncBillingResource,
+    BillingResourceWithRawResponse,
+    AsyncBillingResourceWithRawResponse,
+    BillingResourceWithStreamingResponse,
+    AsyncBillingResourceWithStreamingResponse,
+)
 from ...types.organization_schema_public import OrganizationSchemaPublic
 
 __all__ = ["OrganizationsResource", "AsyncOrganizationsResource"]
diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py
index d39b124a..87f647d3 100644
--- a/src/codex/resources/users/myself/api_key.py
+++ b/src/codex/resources/users/myself/api_key.py
@@ -13,6 +13,7 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
+from ....types.user import User
 from ...._base_client import make_request_options
 from ....types.users.user_schema import UserSchema
 
@@ -39,6 +40,25 @@ def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse:
         """
         return APIKeyResourceWithStreamingResponse(self)
 
+    def retrieve(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> User:
+        """Get user when authenticated with API key."""
+        return self._get(
+            "/api/users/myself/api-key",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=User,
+        )
+
     def refresh(
         self,
         *,
@@ -79,6 +99,25 @@ def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse:
         """
         return AsyncAPIKeyResourceWithStreamingResponse(self)
 
+    async def retrieve(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> User:
+        """Get user when authenticated with API key."""
+        return await self._get(
+            "/api/users/myself/api-key",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=User,
+        )
+
     async def refresh(
         self,
         *,
@@ -103,6 +142,9 @@ class APIKeyResourceWithRawResponse:
     def __init__(self, api_key: APIKeyResource) -> None:
         self._api_key = api_key
 
+        self.retrieve = to_raw_response_wrapper(
+            api_key.retrieve,
+        )
         self.refresh = to_raw_response_wrapper(
             api_key.refresh,
         )
@@ -112,6 +154,9 @@ class AsyncAPIKeyResourceWithRawResponse:
     def __init__(self, api_key: AsyncAPIKeyResource) -> None:
         self._api_key = api_key
 
+        self.retrieve = async_to_raw_response_wrapper(
+            api_key.retrieve,
+        )
         self.refresh = async_to_raw_response_wrapper(
             api_key.refresh,
         )
@@ -121,6 +166,9 @@ class APIKeyResourceWithStreamingResponse:
     def __init__(self, api_key: APIKeyResource) -> None:
         self._api_key = api_key
 
+        self.retrieve = to_streamed_response_wrapper(
+            api_key.retrieve,
+        )
         self.refresh = to_streamed_response_wrapper(
             api_key.refresh,
         )
@@ -130,6 +178,9 @@ class AsyncAPIKeyResourceWithStreamingResponse:
     def __init__(self, api_key: AsyncAPIKeyResource) -> None:
         self._api_key = api_key
 
+        self.retrieve = async_to_streamed_response_wrapper(
+            api_key.retrieve,
+        )
         self.refresh = async_to_streamed_response_wrapper(
             api_key.refresh,
         )
diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py
index 3ee27229..2cc4868b 100644
--- a/src/codex/resources/users/myself/myself.py
+++ b/src/codex/resources/users/myself/myself.py
@@ -21,6 +21,7 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
+from ....types.user import User
 from .organizations import (
     OrganizationsResource,
     AsyncOrganizationsResource,
@@ -30,7 +31,6 @@
     AsyncOrganizationsResourceWithStreamingResponse,
 )
 from ...._base_client import make_request_options
-from ....types.users.user_schema_public import UserSchemaPublic
 
 __all__ = ["MyselfResource", "AsyncMyselfResource"]
 
@@ -72,14 +72,14 @@ def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> UserSchemaPublic:
+    ) -> User:
         """Get user info for frontend."""
         return self._get(
             "/api/users/myself",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=UserSchemaPublic,
+            cast_to=User,
         )
 
 
@@ -120,14 +120,14 @@ async def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> UserSchemaPublic:
+    ) -> User:
         """Get user info for frontend."""
         return await self._get(
             "/api/users/myself",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=UserSchemaPublic,
+            cast_to=User,
         )
 
 
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index 8a73b474..fb7ee0f1 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -2,8 +2,26 @@
 
 from __future__ import annotations
 
+from typing import Union, Optional
+from datetime import datetime
+
+import httpx
+
+from ...types import user_activate_account_params
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...types.user import User
 from .myself.myself import (
     MyselfResource,
     AsyncMyselfResource,
@@ -12,6 +30,7 @@
     MyselfResourceWithStreamingResponse,
     AsyncMyselfResourceWithStreamingResponse,
 )
+from ..._base_client import make_request_options
 
 __all__ = ["UsersResource", "AsyncUsersResource"]
 
@@ -40,6 +59,55 @@ def with_streaming_response(self) -> UsersResourceWithStreamingResponse:
         """
         return UsersResourceWithStreamingResponse(self)
 
+    def activate_account(
+        self,
+        *,
+        first_name: str,
+        last_name: str,
+        account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN,
+        discovery_source: Optional[str] | NotGiven = NOT_GIVEN,
+        is_account_activated: bool | NotGiven = NOT_GIVEN,
+        phone_number: Optional[str] | NotGiven = NOT_GIVEN,
+        user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> User:
+        """
+        Activate an authenticated user's account
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._patch(
+            "/api/users/activate_account",
+            body=maybe_transform(
+                {
+                    "first_name": first_name,
+                    "last_name": last_name,
+                    "account_activated_at": account_activated_at,
+                    "discovery_source": discovery_source,
+                    "is_account_activated": is_account_activated,
+                    "phone_number": phone_number,
+                    "user_provided_company_name": user_provided_company_name,
+                },
+                user_activate_account_params.UserActivateAccountParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=User,
+        )
+
 
 class AsyncUsersResource(AsyncAPIResource):
     @cached_property
@@ -65,11 +133,64 @@ def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse:
         """
         return AsyncUsersResourceWithStreamingResponse(self)
 
+    async def activate_account(
+        self,
+        *,
+        first_name: str,
+        last_name: str,
+        account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN,
+        discovery_source: Optional[str] | NotGiven = NOT_GIVEN,
+        is_account_activated: bool | NotGiven = NOT_GIVEN,
+        phone_number: Optional[str] | NotGiven = NOT_GIVEN,
+        user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> User:
+        """
+        Activate an authenticated user's account
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._patch(
+            "/api/users/activate_account",
+            body=await async_maybe_transform(
+                {
+                    "first_name": first_name,
+                    "last_name": last_name,
+                    "account_activated_at": account_activated_at,
+                    "discovery_source": discovery_source,
+                    "is_account_activated": is_account_activated,
+                    "phone_number": phone_number,
+                    "user_provided_company_name": user_provided_company_name,
+                },
+                user_activate_account_params.UserActivateAccountParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=User,
+        )
+
 
 class UsersResourceWithRawResponse:
     def __init__(self, users: UsersResource) -> None:
         self._users = users
 
+        self.activate_account = to_raw_response_wrapper(
+            users.activate_account,
+        )
+
     @cached_property
     def myself(self) -> MyselfResourceWithRawResponse:
         return MyselfResourceWithRawResponse(self._users.myself)
@@ -79,6 +200,10 @@ class AsyncUsersResourceWithRawResponse:
     def __init__(self, users: AsyncUsersResource) -> None:
         self._users = users
 
+        self.activate_account = async_to_raw_response_wrapper(
+            users.activate_account,
+        )
+
     @cached_property
     def myself(self) -> AsyncMyselfResourceWithRawResponse:
         return AsyncMyselfResourceWithRawResponse(self._users.myself)
@@ -88,6 +213,10 @@ class UsersResourceWithStreamingResponse:
     def __init__(self, users: UsersResource) -> None:
         self._users = users
 
+        self.activate_account = to_streamed_response_wrapper(
+            users.activate_account,
+        )
+
     @cached_property
     def myself(self) -> MyselfResourceWithStreamingResponse:
         return MyselfResourceWithStreamingResponse(self._users.myself)
@@ -97,6 +226,10 @@ class AsyncUsersResourceWithStreamingResponse:
     def __init__(self, users: AsyncUsersResource) -> None:
         self._users = users
 
+        self.activate_account = async_to_streamed_response_wrapper(
+            users.activate_account,
+        )
+
     @cached_property
     def myself(self) -> AsyncMyselfResourceWithStreamingResponse:
         return AsyncMyselfResourceWithStreamingResponse(self._users.myself)
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 9a02abc3..f7ec95b6 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+from .user import User as User
 from .project_list_params import ProjectListParams as ProjectListParams
 from .health_check_response import HealthCheckResponse as HealthCheckResponse
 from .project_create_params import ProjectCreateParams as ProjectCreateParams
@@ -9,3 +10,4 @@
 from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
 from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
+from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
diff --git a/src/codex/types/organizations/billing/__init__.py b/src/codex/types/organizations/billing/__init__.py
new file mode 100644
index 00000000..b6e94543
--- /dev/null
+++ b/src/codex/types/organizations/billing/__init__.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .organization_billing_card_details import OrganizationBillingCardDetails as OrganizationBillingCardDetails
+from .organization_billing_plan_details import OrganizationBillingPlanDetails as OrganizationBillingPlanDetails
+from .organization_billing_setup_intent import OrganizationBillingSetupIntent as OrganizationBillingSetupIntent
diff --git a/src/codex/types/organizations/billing/organization_billing_card_details.py b/src/codex/types/organizations/billing/organization_billing_card_details.py
new file mode 100644
index 00000000..77576d5d
--- /dev/null
+++ b/src/codex/types/organizations/billing/organization_billing_card_details.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ...._models import BaseModel
+
+__all__ = ["OrganizationBillingCardDetails", "BillingDetails", "CardDetails"]
+
+
+class BillingDetails(BaseModel):
+    address_city: Optional[str] = None
+
+    address_country: str
+
+    address_line1: Optional[str] = None
+
+    address_line2: Optional[str] = None
+
+    address_postal_code: str
+
+    address_state: Optional[str] = None
+
+    name: Optional[str] = None
+
+
+class CardDetails(BaseModel):
+    brand: str
+
+    exp_month: int
+
+    exp_year: int
+
+    last4: str
+
+
+class OrganizationBillingCardDetails(BaseModel):
+    billing_details: BillingDetails
+
+    card_details: CardDetails
diff --git a/src/codex/types/organizations/billing/organization_billing_plan_details.py b/src/codex/types/organizations/billing/organization_billing_plan_details.py
new file mode 100644
index 00000000..03cc176f
--- /dev/null
+++ b/src/codex/types/organizations/billing/organization_billing_plan_details.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ...._models import BaseModel
+
+__all__ = ["OrganizationBillingPlanDetails"]
+
+
+class OrganizationBillingPlanDetails(BaseModel):
+    current_billing_period_credits_answers: float
+
+    current_billing_period_credits_cents: float
+
+    current_billing_period_total_cents: float
+
+    current_billing_period_usage_answers: float
+
+    current_billing_period_usage_cents: float
+
+    invoice_end_date: datetime
+
+    invoice_issue_date: datetime
+
+    invoice_start_date: datetime
+
+    name: str
+
+    is_enterprise: Optional[bool] = None
diff --git a/src/codex/types/organizations/billing/organization_billing_setup_intent.py b/src/codex/types/organizations/billing/organization_billing_setup_intent.py
new file mode 100644
index 00000000..dfd07d6b
--- /dev/null
+++ b/src/codex/types/organizations/billing/organization_billing_setup_intent.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from ...._models import BaseModel
+
+__all__ = ["OrganizationBillingSetupIntent"]
+
+
+class OrganizationBillingSetupIntent(BaseModel):
+    client_secret: str
+
+    intent_id: str
diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/user.py
similarity index 80%
rename from src/codex/types/users/user_schema_public.py
rename to src/codex/types/user.py
index 181113b0..3d7ec233 100644
--- a/src/codex/types/users/user_schema_public.py
+++ b/src/codex/types/user.py
@@ -2,12 +2,12 @@
 
 from typing import Optional
 
-from ..._models import BaseModel
+from .._models import BaseModel
 
-__all__ = ["UserSchemaPublic"]
+__all__ = ["User"]
 
 
-class UserSchemaPublic(BaseModel):
+class User(BaseModel):
     id: str
 
     api_key: str
diff --git a/src/codex/types/user_activate_account_params.py b/src/codex/types/user_activate_account_params.py
new file mode 100644
index 00000000..195c2ca2
--- /dev/null
+++ b/src/codex/types/user_activate_account_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["UserActivateAccountParams"]
+
+
+class UserActivateAccountParams(TypedDict, total=False):
+    first_name: Required[str]
+
+    last_name: Required[str]
+
+    account_activated_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+
+    discovery_source: Optional[str]
+
+    is_account_activated: bool
+
+    phone_number: Optional[str]
+
+    user_provided_company_name: Optional[str]
diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py
index 85decc6b..4256bd7f 100644
--- a/src/codex/types/users/__init__.py
+++ b/src/codex/types/users/__init__.py
@@ -3,4 +3,3 @@
 from __future__ import annotations
 
 from .user_schema import UserSchema as UserSchema
-from .user_schema_public import UserSchemaPublic as UserSchemaPublic
diff --git a/tests/api_resources/organizations/billing/__init__.py b/tests/api_resources/organizations/billing/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/organizations/billing/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py
new file mode 100644
index 00000000..2fb71fc5
--- /dev/null
+++ b/tests/api_resources/organizations/billing/test_card_details.py
@@ -0,0 +1,106 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, Optional, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.organizations.billing import OrganizationBillingCardDetails
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCardDetails:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        card_detail = client.organizations.billing.card_details.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.organizations.billing.card_details.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        card_detail = response.parse()
+        assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.organizations.billing.card_details.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            card_detail = response.parse()
+            assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.billing.card_details.with_raw_response.retrieve(
+                "",
+            )
+
+
+class TestAsyncCardDetails:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        card_detail = await async_client.organizations.billing.card_details.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.billing.card_details.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        card_detail = await response.parse()
+        assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.billing.card_details.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            card_detail = await response.parse()
+            assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.billing.card_details.with_raw_response.retrieve(
+                "",
+            )
diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py
new file mode 100644
index 00000000..2fc1b810
--- /dev/null
+++ b/tests/api_resources/organizations/billing/test_plan_details.py
@@ -0,0 +1,106 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.organizations.billing import OrganizationBillingPlanDetails
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPlanDetails:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        plan_detail = client.organizations.billing.plan_details.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.organizations.billing.plan_details.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        plan_detail = response.parse()
+        assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.organizations.billing.plan_details.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            plan_detail = response.parse()
+            assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.billing.plan_details.with_raw_response.retrieve(
+                "",
+            )
+
+
+class TestAsyncPlanDetails:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        plan_detail = await async_client.organizations.billing.plan_details.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.billing.plan_details.with_raw_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        plan_detail = await response.parse()
+        assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.billing.plan_details.with_streaming_response.retrieve(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            plan_detail = await response.parse()
+            assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.billing.plan_details.with_raw_response.retrieve(
+                "",
+            )
diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py
new file mode 100644
index 00000000..3eb05fbc
--- /dev/null
+++ b/tests/api_resources/organizations/billing/test_setup_intent.py
@@ -0,0 +1,106 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.organizations.billing import OrganizationBillingSetupIntent
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSetupIntent:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        setup_intent = client.organizations.billing.setup_intent.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.organizations.billing.setup_intent.with_raw_response.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        setup_intent = response.parse()
+        assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.organizations.billing.setup_intent.with_streaming_response.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            setup_intent = response.parse()
+            assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_create(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.billing.setup_intent.with_raw_response.create(
+                "",
+            )
+
+
+class TestAsyncSetupIntent:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        setup_intent = await async_client.organizations.billing.setup_intent.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.billing.setup_intent.with_raw_response.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        setup_intent = await response.parse()
+        assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.billing.setup_intent.with_streaming_response.create(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            setup_intent = await response.parse()
+            assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.billing.setup_intent.with_raw_response.create(
+                "",
+            )
diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py
new file mode 100644
index 00000000..7e78b99e
--- /dev/null
+++ b/tests/api_resources/test_users.py
@@ -0,0 +1,125 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import User
+from tests.utils import assert_matches_type
+from codex._utils import parse_datetime
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestUsers:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_activate_account(self, client: Codex) -> None:
+        user = client.users.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        )
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_activate_account_with_all_params(self, client: Codex) -> None:
+        user = client.users.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+            account_activated_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            discovery_source="discovery_source",
+            is_account_activated=True,
+            phone_number="phone_number",
+            user_provided_company_name="user_provided_company_name",
+        )
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_activate_account(self, client: Codex) -> None:
+        response = client.users.with_raw_response.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = response.parse()
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_activate_account(self, client: Codex) -> None:
+        with client.users.with_streaming_response.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = response.parse()
+            assert_matches_type(User, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncUsers:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_activate_account(self, async_client: AsyncCodex) -> None:
+        user = await async_client.users.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        )
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_activate_account_with_all_params(self, async_client: AsyncCodex) -> None:
+        user = await async_client.users.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+            account_activated_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            discovery_source="discovery_source",
+            is_account_activated=True,
+            phone_number="phone_number",
+            user_provided_company_name="user_provided_company_name",
+        )
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.with_raw_response.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        user = await response.parse()
+        assert_matches_type(User, user, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_activate_account(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.with_streaming_response.activate_account(
+            first_name="first_name",
+            last_name="last_name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            user = await response.parse()
+            assert_matches_type(User, user, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index 68996997..a58477bc 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -8,6 +8,7 @@
 import pytest
 
 from codex import Codex, AsyncCodex
+from codex.types import User
 from tests.utils import assert_matches_type
 from codex.types.users import UserSchema
 
@@ -17,6 +18,34 @@
 class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        api_key = client.users.myself.api_key.retrieve()
+        assert_matches_type(User, api_key, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.users.myself.api_key.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        api_key = response.parse()
+        assert_matches_type(User, api_key, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.users.myself.api_key.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            api_key = response.parse()
+            assert_matches_type(User, api_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
     @pytest.mark.skip()
     @parametrize
     def test_method_refresh(self, client: Codex) -> None:
@@ -49,6 +78,34 @@ def test_streaming_response_refresh(self, client: Codex) -> None:
 class TestAsyncAPIKey:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        api_key = await async_client.users.myself.api_key.retrieve()
+        assert_matches_type(User, api_key, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.myself.api_key.with_raw_response.retrieve()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        api_key = await response.parse()
+        assert_matches_type(User, api_key, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.myself.api_key.with_streaming_response.retrieve() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            api_key = await response.parse()
+            assert_matches_type(User, api_key, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_refresh(self, async_client: AsyncCodex) -> None:
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index 63123275..174f2cec 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -8,8 +8,8 @@
 import pytest
 
 from codex import Codex, AsyncCodex
+from codex.types import User
 from tests.utils import assert_matches_type
-from codex.types.users import UserSchemaPublic
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -21,7 +21,7 @@ class TestMyself:
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         myself = client.users.myself.retrieve()
-        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+        assert_matches_type(User, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -31,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         myself = response.parse()
-        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+        assert_matches_type(User, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -41,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             myself = response.parse()
-            assert_matches_type(UserSchemaPublic, myself, path=["response"])
+            assert_matches_type(User, myself, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -53,7 +53,7 @@ class TestAsyncMyself:
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await async_client.users.myself.retrieve()
-        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+        assert_matches_type(User, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -63,7 +63,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         myself = await response.parse()
-        assert_matches_type(UserSchemaPublic, myself, path=["response"])
+        assert_matches_type(User, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -73,6 +73,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             myself = await response.parse()
-            assert_matches_type(UserSchemaPublic, myself, path=["response"])
+            assert_matches_type(User, myself, path=["response"])
 
         assert cast(Any, response.is_closed) is True

From 6cfe9d91d02ef6aa496f561780b7d1469af86d6c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 28 Jan 2025 00:48:52 +0000
Subject: [PATCH 043/227] chore(internal): version bump (#45)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b5db7ce1..c373724d 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.7"
+  ".": "0.1.0-alpha.8"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 6aa0de59..636b3299 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.7"
+version = "0.1.0-alpha.8"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 4fb336bd..9cbcc56a 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.7"  # x-release-please-version
+__version__ = "0.1.0-alpha.8"  # x-release-please-version

From a21c250f8e0abfbae0e8bc914bd4040c91ea49a4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 19:39:12 +0000
Subject: [PATCH 044/227] feat(api): remove bearer auth (#46)

---
 README.md            |  6 +++---
 src/codex/_client.py | 50 ++------------------------------------------
 2 files changed, 5 insertions(+), 51 deletions(-)

diff --git a/README.md b/README.md
index 42ef0af5..b8e49de2 100644
--- a/README.md
+++ b/README.md
@@ -39,10 +39,10 @@ project_return_schema = client.projects.create(
 print(project_return_schema.id)
 ```
 
-While you can provide a `bearer_token` keyword argument,
+While you can provide an `api_key` keyword argument,
 we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
-to add `CODEX_BEARER_TOKEN="My Bearer Token"` to your `.env` file
-so that your Bearer Token is not stored in source control.
+to add `CODEX_API_KEY="My API Key"` to your `.env` file
+so that your API Key is not stored in source control.
 
 ## Async usage
 
diff --git a/src/codex/_client.py b/src/codex/_client.py
index fc0c27bf..843f6a21 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -65,7 +65,6 @@ class Codex(SyncAPIClient):
     with_streaming_response: CodexWithStreamedResponse
 
     # client options
-    bearer_token: str | None
     api_key: str | None
     access_key: str | None
 
@@ -74,7 +73,6 @@ class Codex(SyncAPIClient):
     def __init__(
         self,
         *,
-        bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
@@ -100,14 +98,9 @@ def __init__(
         """Construct a new synchronous Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `bearer_token` from `CODEX_BEARER_TOKEN`
         - `api_key` from `CODEX_API_KEY`
         - `access_key` from `CODEX_ACCESS_KEY`
         """
-        if bearer_token is None:
-            bearer_token = os.environ.get("CODEX_BEARER_TOKEN")
-        self.bearer_token = bearer_token
-
         if api_key is None:
             api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
@@ -168,21 +161,12 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        if self._http_bearer:
-            return self._http_bearer
         if self._authenticated_api_key:
             return self._authenticated_api_key
         if self._public_access_key:
             return self._public_access_key
         return {}
 
-    @property
-    def _http_bearer(self) -> dict[str, str]:
-        bearer_token = self.bearer_token
-        if bearer_token is None:
-            return {}
-        return {"Authorization": f"Bearer {bearer_token}"}
-
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
         api_key = self.api_key
@@ -208,11 +192,6 @@ def default_headers(self) -> dict[str, str | Omit]:
 
     @override
     def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
-        if self.bearer_token and headers.get("Authorization"):
-            return
-        if isinstance(custom_headers.get("Authorization"), Omit):
-            return
-
         if self.api_key and headers.get("X-API-Key"):
             return
         if isinstance(custom_headers.get("X-API-Key"), Omit):
@@ -224,13 +203,12 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
             return
 
         raise TypeError(
-            '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
         )
 
     def copy(
         self,
         *,
-        bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | None = None,
@@ -267,7 +245,6 @@ def copy(
 
         http_client = http_client or self._client
         return self.__class__(
-            bearer_token=bearer_token or self.bearer_token,
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,
@@ -327,7 +304,6 @@ class AsyncCodex(AsyncAPIClient):
     with_streaming_response: AsyncCodexWithStreamedResponse
 
     # client options
-    bearer_token: str | None
     api_key: str | None
     access_key: str | None
 
@@ -336,7 +312,6 @@ class AsyncCodex(AsyncAPIClient):
     def __init__(
         self,
         *,
-        bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
@@ -362,14 +337,9 @@ def __init__(
         """Construct a new async Codex client instance.
 
         This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `bearer_token` from `CODEX_BEARER_TOKEN`
         - `api_key` from `CODEX_API_KEY`
         - `access_key` from `CODEX_ACCESS_KEY`
         """
-        if bearer_token is None:
-            bearer_token = os.environ.get("CODEX_BEARER_TOKEN")
-        self.bearer_token = bearer_token
-
         if api_key is None:
             api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
@@ -430,21 +400,12 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        if self._http_bearer:
-            return self._http_bearer
         if self._authenticated_api_key:
             return self._authenticated_api_key
         if self._public_access_key:
             return self._public_access_key
         return {}
 
-    @property
-    def _http_bearer(self) -> dict[str, str]:
-        bearer_token = self.bearer_token
-        if bearer_token is None:
-            return {}
-        return {"Authorization": f"Bearer {bearer_token}"}
-
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
         api_key = self.api_key
@@ -470,11 +431,6 @@ def default_headers(self) -> dict[str, str | Omit]:
 
     @override
     def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
-        if self.bearer_token and headers.get("Authorization"):
-            return
-        if isinstance(custom_headers.get("Authorization"), Omit):
-            return
-
         if self.api_key and headers.get("X-API-Key"):
             return
         if isinstance(custom_headers.get("X-API-Key"), Omit):
@@ -486,13 +442,12 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
             return
 
         raise TypeError(
-            '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
         )
 
     def copy(
         self,
         *,
-        bearer_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | None = None,
@@ -529,7 +484,6 @@ def copy(
 
         http_client = http_client or self._client
         return self.__class__(
-            bearer_token=bearer_token or self.bearer_token,
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,

From 71aaab337ebf0902b39192a7fcdc4d89921ff32e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 19:50:00 +0000
Subject: [PATCH 045/227] feat(api): remove env var auth (#48)

---
 README.md            |  5 -----
 src/codex/_client.py | 22 ++--------------------
 2 files changed, 2 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index b8e49de2..2695ffe2 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,6 @@ project_return_schema = client.projects.create(
 print(project_return_schema.id)
 ```
 
-While you can provide an `api_key` keyword argument,
-we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
-to add `CODEX_API_KEY="My API Key"` to your `.env` file
-so that your API Key is not stored in source control.
-
 ## Async usage
 
 Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 843f6a21..09aaafd1 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -95,18 +95,9 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous Codex client instance.
-
-        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `api_key` from `CODEX_API_KEY`
-        - `access_key` from `CODEX_ACCESS_KEY`
-        """
-        if api_key is None:
-            api_key = os.environ.get("CODEX_API_KEY")
+        """Construct a new synchronous Codex client instance."""
         self.api_key = api_key
 
-        if access_key is None:
-            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment
@@ -334,18 +325,9 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Codex client instance.
-
-        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `api_key` from `CODEX_API_KEY`
-        - `access_key` from `CODEX_ACCESS_KEY`
-        """
-        if api_key is None:
-            api_key = os.environ.get("CODEX_API_KEY")
+        """Construct a new async Codex client instance."""
         self.api_key = api_key
 
-        if access_key is None:
-            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment

From f6ca515d734d5a08d34242ad5bdfb050232cf687 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 19:52:49 +0000
Subject: [PATCH 046/227] feat(api): add back env var auth (#49)

---
 README.md            |  5 +++++
 src/codex/_client.py | 22 ++++++++++++++++++++--
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 2695ffe2..b8e49de2 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,11 @@ project_return_schema = client.projects.create(
 print(project_return_schema.id)
 ```
 
+While you can provide an `api_key` keyword argument,
+we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
+to add `CODEX_API_KEY="My API Key"` to your `.env` file
+so that your API Key is not stored in source control.
+
 ## Async usage
 
 Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 09aaafd1..843f6a21 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -95,9 +95,18 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous Codex client instance."""
+        """Construct a new synchronous Codex client instance.
+
+        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+        - `api_key` from `CODEX_API_KEY`
+        - `access_key` from `CODEX_ACCESS_KEY`
+        """
+        if api_key is None:
+            api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
 
+        if access_key is None:
+            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment
@@ -325,9 +334,18 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Codex client instance."""
+        """Construct a new async Codex client instance.
+
+        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+        - `api_key` from `CODEX_API_KEY`
+        - `access_key` from `CODEX_ACCESS_KEY`
+        """
+        if api_key is None:
+            api_key = os.environ.get("CODEX_API_KEY")
         self.api_key = api_key
 
+        if access_key is None:
+            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment

From 45e225c08f1e5ec8985aa19a06d17841f717fdf8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 20:02:13 +0000
Subject: [PATCH 047/227] feat(api): remove env var auth (#50)

---
 README.md            |  5 -----
 src/codex/_client.py | 22 ++--------------------
 2 files changed, 2 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index b8e49de2..2695ffe2 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,6 @@ project_return_schema = client.projects.create(
 print(project_return_schema.id)
 ```
 
-While you can provide an `api_key` keyword argument,
-we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
-to add `CODEX_API_KEY="My API Key"` to your `.env` file
-so that your API Key is not stored in source control.
-
 ## Async usage
 
 Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 843f6a21..09aaafd1 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -95,18 +95,9 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new synchronous Codex client instance.
-
-        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `api_key` from `CODEX_API_KEY`
-        - `access_key` from `CODEX_ACCESS_KEY`
-        """
-        if api_key is None:
-            api_key = os.environ.get("CODEX_API_KEY")
+        """Construct a new synchronous Codex client instance."""
         self.api_key = api_key
 
-        if access_key is None:
-            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment
@@ -334,18 +325,9 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Codex client instance.
-
-        This automatically infers the following arguments from their corresponding environment variables if they are not provided:
-        - `api_key` from `CODEX_API_KEY`
-        - `access_key` from `CODEX_ACCESS_KEY`
-        """
-        if api_key is None:
-            api_key = os.environ.get("CODEX_API_KEY")
+        """Construct a new async Codex client instance."""
         self.api_key = api_key
 
-        if access_key is None:
-            access_key = os.environ.get("CODEX_ACCESS_KEY")
         self.access_key = access_key
 
         self._environment = environment

From d4cde55c3bc3f208228e8902fc85b6f32d1b3a1c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 20:04:19 +0000
Subject: [PATCH 048/227] chore(internal): version bump (#51)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index c373724d..46b9b6b2 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.8"
+  ".": "0.1.0-alpha.9"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 636b3299..b38e6458 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.8"
+version = "0.1.0-alpha.9"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 9cbcc56a..398bec65 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.8"  # x-release-please-version
+__version__ = "0.1.0-alpha.9"  # x-release-please-version

From 41d818a68b36a53535397bc9971a79c047af2c71 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 19:54:01 +0000
Subject: [PATCH 049/227] feat(api): update readme title (#53)

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 2695ffe2..1c5be89e 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Codex Python API library
+# Codex SDK API library
 
 [![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/)
 
-The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+
+The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,
 and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
 

From 51195452af93181b4af7c5a955055c575d566b1b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 19:56:18 +0000
Subject: [PATCH 050/227] chore(internal): version bump (#54)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 46b9b6b2..3b005e52 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.9"
+  ".": "0.1.0-alpha.10"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index b38e6458..7f4e270e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.9"
+version = "0.1.0-alpha.10"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 398bec65..656b38ab 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.9"  # x-release-please-version
+__version__ = "0.1.0-alpha.10"  # x-release-please-version

From f9e60045c1e99bbddba37b5b4e188fe0652aa873 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 20:05:01 +0000
Subject: [PATCH 051/227] chore(internal): version bump (#58)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3b005e52..ee49ac2d 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.10"
+  ".": "0.1.0-alpha.11"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 7f4e270e..8648de69 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.10"
+version = "0.1.0-alpha.11"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 656b38ab..fd5f7081 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.10"  # x-release-please-version
+__version__ = "0.1.0-alpha.11"  # x-release-please-version

From 71a7631645a24fde4c7d9aec4304995004b1d52e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 20:30:25 +0000
Subject: [PATCH 052/227] feat(api): api update (#60)

---
 src/codex/resources/projects/entries.py         | 16 ++++++++++++----
 src/codex/types/projects/entry.py               |  2 ++
 src/codex/types/projects/entry_update_params.py |  2 ++
 tests/api_resources/projects/test_entries.py    |  2 ++
 4 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index d307d3d0..66762d51 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -138,6 +138,7 @@ def update(
         *,
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -167,6 +168,7 @@ def update(
             body=maybe_transform(
                 {
                     "answer": answer,
+                    "frequency_count": frequency_count,
                     "question": question,
                 },
                 entry_update_params.EntryUpdateParams,
@@ -318,8 +320,10 @@ def query(
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> Optional[Entry]:
-        """
-        Query knowledge for a project.
+        """Query knowledge for a project.
+
+        Also increments the frequency_count for the
+        matching entry if found.
 
         Returns the matching entry if found and answered, otherwise returns None. This
         allows the client to distinguish between: (1) no matching question found
@@ -451,6 +455,7 @@ async def update(
         *,
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -480,6 +485,7 @@ async def update(
             body=await async_maybe_transform(
                 {
                     "answer": answer,
+                    "frequency_count": frequency_count,
                     "question": question,
                 },
                 entry_update_params.EntryUpdateParams,
@@ -631,8 +637,10 @@ async def query(
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> Optional[Entry]:
-        """
-        Query knowledge for a project.
+        """Query knowledge for a project.
+
+        Also increments the frequency_count for the
+        matching entry if found.
 
         Returns the matching entry if found and answered, otherwise returns None. This
         allows the client to distinguish between: (1) no matching question found
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index d3e1fc5f..4621cc4b 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -18,3 +18,5 @@ class Entry(BaseModel):
     answer: Optional[str] = None
 
     answered_at: Optional[datetime] = None
+
+    frequency_count: Optional[int] = None
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
index 0a676f31..ba105495 100644
--- a/src/codex/types/projects/entry_update_params.py
+++ b/src/codex/types/projects/entry_update_params.py
@@ -13,4 +13,6 @@ class EntryUpdateParams(TypedDict, total=False):
 
     answer: Optional[str]
 
+    frequency_count: Optional[int]
+
     question: Optional[str]
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 5b51ec12..026add46 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -144,6 +144,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
+            frequency_count=0,
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -519,6 +520,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
+            frequency_count=0,
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])

From 5a9804219610fa14906d96b52fbe13a288ee6a0e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 03:09:51 +0000
Subject: [PATCH 053/227] chore(internal): change default timeout to an int
 (#61)

---
 src/codex/_constants.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codex/_constants.py b/src/codex/_constants.py
index a2ac3b6f..6ddf2c71 100644
--- a/src/codex/_constants.py
+++ b/src/codex/_constants.py
@@ -6,7 +6,7 @@
 OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
 
 # default timeout is 1 minute
-DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0)
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
 DEFAULT_MAX_RETRIES = 2
 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
 

From 87571919f0720b870f175772560f75238ccdd699 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 03:11:26 +0000
Subject: [PATCH 054/227] chore(internal): bummp ruff dependency (#62)

---
 pyproject.toml               | 2 +-
 requirements-dev.lock        | 2 +-
 scripts/utils/ruffen-docs.py | 4 ++--
 src/codex/_models.py         | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 8648de69..7594a895 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -177,7 +177,7 @@ select = [
   "T201",
   "T203",
   # misuse of typing.TYPE_CHECKING
-  "TCH004",
+  "TC004",
   # import rules
   "TID251",
 ]
diff --git a/requirements-dev.lock b/requirements-dev.lock
index ef078719..1961e8d6 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -78,7 +78,7 @@ pytz==2023.3.post1
     # via dirty-equals
 respx==0.22.0
 rich==13.7.1
-ruff==0.6.9
+ruff==0.9.4
 setuptools==68.2.2
     # via nodeenv
 six==1.16.0
diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py
index 37b3d94f..0cf2bd2f 100644
--- a/scripts/utils/ruffen-docs.py
+++ b/scripts/utils/ruffen-docs.py
@@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str:
         with _collect_error(match):
             code = format_code_block(code)
         code = textwrap.indent(code, match["indent"])
-        return f'{match["before"]}{code}{match["after"]}'
+        return f"{match['before']}{code}{match['after']}"
 
     def _pycon_match(match: Match[str]) -> str:
         code = ""
@@ -97,7 +97,7 @@ def finish_fragment() -> None:
     def _md_pycon_match(match: Match[str]) -> str:
         code = _pycon_match(match)
         code = textwrap.indent(code, match["indent"])
-        return f'{match["before"]}{code}{match["after"]}'
+        return f"{match['before']}{code}{match['after']}"
 
     src = MD_RE.sub(_md_match, src)
     src = MD_PYCON_RE.sub(_md_pycon_match, src)
diff --git a/src/codex/_models.py b/src/codex/_models.py
index 9a918aab..12c34b7d 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -172,7 +172,7 @@ def to_json(
     @override
     def __str__(self) -> str:
         # mypy complains about an invalid self arg
-        return f'{self.__repr_name__()}({self.__repr_str__(", ")})'  # type: ignore[misc]
+        return f"{self.__repr_name__()}({self.__repr_str__(', ')})"  # type: ignore[misc]
 
     # Override the 'construct' method in a way that supports recursive parsing without validation.
     # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.

From a4a10aea0a54bdce753b5068f2ce3c611b62e8b9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 6 Feb 2025 03:13:40 +0000
Subject: [PATCH 055/227] feat(client): send `X-Stainless-Read-Timeout` header
 (#63)

---
 src/codex/_base_client.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 9090e758..e908e40a 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -418,10 +418,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
         if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
             headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
 
-        # Don't set the retry count header if it was already set or removed by the caller. We check
+        # Don't set these headers if they were already set or removed by the caller. We check
         # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
-        if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
+        lower_custom_headers = [header.lower() for header in custom_headers]
+        if "x-stainless-retry-count" not in lower_custom_headers:
             headers["x-stainless-retry-count"] = str(retries_taken)
+        if "x-stainless-read-timeout" not in lower_custom_headers:
+            timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
+            if isinstance(timeout, Timeout):
+                timeout = timeout.read
+            if timeout is not None:
+                headers["x-stainless-read-timeout"] = str(timeout)
 
         return headers
 

From 207ee5cf9e112f7f9f28db7f1385a5d55f5323db Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 7 Feb 2025 03:12:20 +0000
Subject: [PATCH 056/227] chore(internal): fix type traversing dictionary
 params (#64)

---
 src/codex/_utils/_transform.py | 12 +++++++++++-
 tests/test_transform.py        | 11 ++++++++++-
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index a6b62cad..18afd9d8 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -25,7 +25,7 @@
     is_annotated_type,
     strip_annotated_type,
 )
-from .._compat import model_dump, is_typeddict
+from .._compat import get_origin, model_dump, is_typeddict
 
 _T = TypeVar("_T")
 
@@ -164,9 +164,14 @@ def _transform_recursive(
         inner_type = annotation
 
     stripped_type = strip_annotated_type(inner_type)
+    origin = get_origin(stripped_type) or stripped_type
     if is_typeddict(stripped_type) and is_mapping(data):
         return _transform_typeddict(data, stripped_type)
 
+    if origin == dict and is_mapping(data):
+        items_type = get_args(stripped_type)[1]
+        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
     if (
         # List[T]
         (is_list_type(stripped_type) and is_list(data))
@@ -307,9 +312,14 @@ async def _async_transform_recursive(
         inner_type = annotation
 
     stripped_type = strip_annotated_type(inner_type)
+    origin = get_origin(stripped_type) or stripped_type
     if is_typeddict(stripped_type) and is_mapping(data):
         return await _async_transform_typeddict(data, stripped_type)
 
+    if origin == dict and is_mapping(data):
+        items_type = get_args(stripped_type)[1]
+        return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
     if (
         # List[T]
         (is_list_type(stripped_type) and is_list(data))
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 2e918885..324f31a7 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -2,7 +2,7 @@
 
 import io
 import pathlib
-from typing import Any, List, Union, TypeVar, Iterable, Optional, cast
+from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast
 from datetime import date, datetime
 from typing_extensions import Required, Annotated, TypedDict
 
@@ -388,6 +388,15 @@ def my_iter() -> Iterable[Baz8]:
     }
 
 
+@parametrize
+@pytest.mark.asyncio
+async def test_dictionary_items(use_async: bool) -> None:
+    class DictItems(TypedDict):
+        foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+    assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}}
+
+
 class TypedDictIterableUnionStr(TypedDict):
     foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]
 

From 23f52a8e244a616e99d5f00044610abfeaf35e92 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 7 Feb 2025 03:15:15 +0000
Subject: [PATCH 057/227] chore(internal): minor type handling changes (#65)

---
 src/codex/_models.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index 12c34b7d..c4401ff8 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object:
 
     If the given value does not match the expected type then it is returned as-is.
     """
+
+    # store a reference to the original type we were given before we extract any inner
+    # types so that we can properly resolve forward references in `TypeAliasType` annotations
+    original_type = None
+
     # we allow `object` as the input type because otherwise, passing things like
     # `Literal['value']` will be reported as a type error by type checkers
     type_ = cast("type[object]", type_)
     if is_type_alias_type(type_):
+        original_type = type_  # type: ignore[unreachable]
         type_ = type_.__value__  # type: ignore[unreachable]
 
     # unwrap `Annotated[T, ...]` -> `T`
@@ -446,7 +452,7 @@ def construct_type(*, value: object, type_: object) -> object:
 
     if is_union(origin):
         try:
-            return validate_type(type_=cast("type[object]", type_), value=value)
+            return validate_type(type_=cast("type[object]", original_type or type_), value=value)
         except Exception:
             pass
 

From 6e8571f48e4e6ff8628ceae9b9cd392a2d83ccf4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 13 Feb 2025 01:30:11 +0000
Subject: [PATCH 058/227] feat(api): api update (#67)

---
 src/codex/resources/projects/entries.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 66762d51..2fcc8e0b 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -70,6 +70,8 @@ def create(
         """
         Create a knowledge entry for a project.
 
+        Raises: HTTPException: If an existing entry is found with the same question.
+
         Args:
           extra_headers: Send extra headers
 
@@ -387,6 +389,8 @@ async def create(
         """
         Create a knowledge entry for a project.
 
+        Raises: HTTPException: If an existing entry is found with the same question.
+
         Args:
           extra_headers: Send extra headers
 

From 86b755bd78f8671d9a24100eb4c5cc5d455a7a43 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 13 Feb 2025 03:36:01 +0000
Subject: [PATCH 059/227] chore(internal): update client tests (#68)

---
 tests/test_client.py | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index b421541f..0b0b783b 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -23,10 +23,12 @@
 
 from codex import Codex, AsyncCodex, APIResponseValidationError
 from codex._types import Omit
+from codex._utils import maybe_transform
 from codex._models import BaseModel, FinalRequestOptions
 from codex._constants import RAW_RESPONSE_HEADER
 from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
 from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options
+from codex.types.project_create_params import ProjectCreateParams
 
 from .utils import update_env
 
@@ -680,7 +682,13 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No
         with pytest.raises(APITimeoutError):
             self.client.post(
                 "/api/projects/",
-                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                body=cast(
+                    object,
+                    maybe_transform(
+                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
+                        ProjectCreateParams,
+                    ),
+                ),
                 cast_to=httpx.Response,
                 options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
             )
@@ -695,7 +703,13 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non
         with pytest.raises(APIStatusError):
             self.client.post(
                 "/api/projects/",
-                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                body=cast(
+                    object,
+                    maybe_transform(
+                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
+                        ProjectCreateParams,
+                    ),
+                ),
                 cast_to=httpx.Response,
                 options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
             )
@@ -1425,7 +1439,13 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter)
         with pytest.raises(APITimeoutError):
             await self.client.post(
                 "/api/projects/",
-                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                body=cast(
+                    object,
+                    maybe_transform(
+                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
+                        ProjectCreateParams,
+                    ),
+                ),
                 cast_to=httpx.Response,
                 options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
             )
@@ -1440,7 +1460,13 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter)
         with pytest.raises(APIStatusError):
             await self.client.post(
                 "/api/projects/",
-                body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")),
+                body=cast(
+                    object,
+                    maybe_transform(
+                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
+                        ProjectCreateParams,
+                    ),
+                ),
                 cast_to=httpx.Response,
                 options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
             )

From 1b7608b3a892e325720237b222306498ec284b64 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 14 Feb 2025 03:24:26 +0000
Subject: [PATCH 060/227] fix: asyncify on non-asyncio runtimes (#69)

---
 src/codex/_utils/_sync.py | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/codex/_utils/_sync.py b/src/codex/_utils/_sync.py
index 8b3aaf2b..ad7ec71b 100644
--- a/src/codex/_utils/_sync.py
+++ b/src/codex/_utils/_sync.py
@@ -7,16 +7,20 @@
 from typing import Any, TypeVar, Callable, Awaitable
 from typing_extensions import ParamSpec
 
+import anyio
+import sniffio
+import anyio.to_thread
+
 T_Retval = TypeVar("T_Retval")
 T_ParamSpec = ParamSpec("T_ParamSpec")
 
 
 if sys.version_info >= (3, 9):
-    to_thread = asyncio.to_thread
+    _asyncio_to_thread = asyncio.to_thread
 else:
     # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
     # for Python 3.8 support
-    async def to_thread(
+    async def _asyncio_to_thread(
         func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
     ) -> Any:
         """Asynchronously run function *func* in a separate thread.
@@ -34,6 +38,17 @@ async def to_thread(
         return await loop.run_in_executor(None, func_call)
 
 
+async def to_thread(
+    func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
+) -> T_Retval:
+    if sniffio.current_async_library() == "asyncio":
+        return await _asyncio_to_thread(func, *args, **kwargs)
+
+    return await anyio.to_thread.run_sync(
+        functools.partial(func, *args, **kwargs),
+    )
+
+
 # inspired by `asyncer`, https://github.com/tiangolo/asyncer
 def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
     """

From 497d779396287f754e5e65125c12ef9c4a702416 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 21 Feb 2025 04:33:53 +0000
Subject: [PATCH 061/227] feat(client): allow passing `NotGiven` for body (#70)

fix(client): mark some request bodies as optional
---
 src/codex/_base_client.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index e908e40a..634d1be2 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -518,7 +518,7 @@ def _build_request(
             # so that passing a `TypedDict` doesn't cause an error.
             # https://github.com/microsoft/pyright/issues/3526#event-6715453066
             params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
-            json=json_data,
+            json=json_data if is_given(json_data) else None,
             files=files,
             **kwargs,
         )

From 08d238bc62dbda6d4e643e2c2075402fced12fff Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 22 Feb 2025 03:37:58 +0000
Subject: [PATCH 062/227] chore(internal): fix devcontainers setup (#71)

---
 .devcontainer/Dockerfile        | 2 +-
 .devcontainer/devcontainer.json | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index ac9a2e75..55d20255 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -6,4 +6,4 @@ USER vscode
 RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
 ENV PATH=/home/vscode/.rye/shims:$PATH
 
-RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc
+RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index bbeb30b1..c17fdc16 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -24,6 +24,9 @@
         }
       }
     }
+  },
+  "features": {
+    "ghcr.io/devcontainers/features/node:1": {}
   }
 
   // Features to add to the dev container. More info: https://containers.dev/features.

From 03126a48fa9ddb92544a323aa13855fc88229ac9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 26 Feb 2025 03:14:51 +0000
Subject: [PATCH 063/227] chore(internal): properly set __pydantic_private__
 (#72)

---
 src/codex/_base_client.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 634d1be2..cc521c2b 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -63,7 +63,7 @@
     ModelBuilderProtocol,
 )
 from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
-from ._compat import model_copy, model_dump
+from ._compat import PYDANTIC_V2, model_copy, model_dump
 from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
 from ._response import (
     APIResponse,
@@ -207,6 +207,9 @@ def _set_private_attributes(
         model: Type[_T],
         options: FinalRequestOptions,
     ) -> None:
+        if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
+            self.__pydantic_private__ = {}
+
         self._model = model
         self._client = client
         self._options = options
@@ -292,6 +295,9 @@ def _set_private_attributes(
         client: AsyncAPIClient,
         options: FinalRequestOptions,
     ) -> None:
+        if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
+            self.__pydantic_private__ = {}
+
         self._model = model
         self._client = client
         self._options = options

From 7ddb32ee6ac3e16eca46852a24a8b568a48fe33b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Feb 2025 03:05:04 +0000
Subject: [PATCH 064/227] docs: update URLs from stainlessapi.com to
 stainless.com (#73)

More details at https://www.stainless.com/changelog/stainless-com
---
 README.md   | 2 +-
 SECURITY.md | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 1c5be89e..7a4f2e12 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ The Codex SDK library provides convenient access to the Codex REST API from any
 application. The library includes type definitions for all request params and response fields,
 and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
 
-It is generated with [Stainless](https://www.stainlessapi.com/).
+It is generated with [Stainless](https://www.stainless.com/).
 
 ## Documentation
 
diff --git a/SECURITY.md b/SECURITY.md
index 54f64467..9fc6ee28 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,9 +2,9 @@
 
 ## Reporting Security Issues
 
-This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
+This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
 
-To report a security issue, please contact the Stainless team at security@stainlessapi.com.
+To report a security issue, please contact the Stainless team at security@stainless.com.
 
 ## Responsible Disclosure
 

From 4caecec85d93c6005bd26d3a19c55f70c5d8b23f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Feb 2025 03:05:48 +0000
Subject: [PATCH 065/227] chore(docs): update client docstring (#75)

---
 src/codex/_client.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index 09aaafd1..deff2779 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -325,7 +325,7 @@ def __init__(
         # part of our public interface in the future.
         _strict_response_validation: bool = False,
     ) -> None:
-        """Construct a new async Codex client instance."""
+        """Construct a new async AsyncCodex client instance."""
         self.api_key = api_key
 
         self.access_key = access_key

From 1a07feeb4f8e734370d2818851a4f077d581f871 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 4 Mar 2025 03:43:37 +0000
Subject: [PATCH 066/227] chore(internal): remove unused http client options
 forwarding (#76)

---
 src/codex/_base_client.py | 97 +--------------------------------------
 1 file changed, 1 insertion(+), 96 deletions(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index cc521c2b..273341b1 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -9,7 +9,6 @@
 import inspect
 import logging
 import platform
-import warnings
 import email.utils
 from types import TracebackType
 from random import random
@@ -36,7 +35,7 @@
 import httpx
 import distro
 import pydantic
-from httpx import URL, Limits
+from httpx import URL
 from pydantic import PrivateAttr
 
 from . import _exceptions
@@ -51,13 +50,10 @@
     Timeout,
     NotGiven,
     ResponseT,
-    Transport,
     AnyMapping,
     PostParser,
-    ProxiesTypes,
     RequestFiles,
     HttpxSendArgs,
-    AsyncTransport,
     RequestOptions,
     HttpxRequestFiles,
     ModelBuilderProtocol,
@@ -337,9 +333,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
     _base_url: URL
     max_retries: int
     timeout: Union[float, Timeout, None]
-    _limits: httpx.Limits
-    _proxies: ProxiesTypes | None
-    _transport: Transport | AsyncTransport | None
     _strict_response_validation: bool
     _idempotency_header: str | None
     _default_stream_cls: type[_DefaultStreamT] | None = None
@@ -352,9 +345,6 @@ def __init__(
         _strict_response_validation: bool,
         max_retries: int = DEFAULT_MAX_RETRIES,
         timeout: float | Timeout | None = DEFAULT_TIMEOUT,
-        limits: httpx.Limits,
-        transport: Transport | AsyncTransport | None,
-        proxies: ProxiesTypes | None,
         custom_headers: Mapping[str, str] | None = None,
         custom_query: Mapping[str, object] | None = None,
     ) -> None:
@@ -362,9 +352,6 @@ def __init__(
         self._base_url = self._enforce_trailing_slash(URL(base_url))
         self.max_retries = max_retries
         self.timeout = timeout
-        self._limits = limits
-        self._proxies = proxies
-        self._transport = transport
         self._custom_headers = custom_headers or {}
         self._custom_query = custom_query or {}
         self._strict_response_validation = _strict_response_validation
@@ -800,46 +787,11 @@ def __init__(
         base_url: str | URL,
         max_retries: int = DEFAULT_MAX_RETRIES,
         timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
-        transport: Transport | None = None,
-        proxies: ProxiesTypes | None = None,
-        limits: Limits | None = None,
         http_client: httpx.Client | None = None,
         custom_headers: Mapping[str, str] | None = None,
         custom_query: Mapping[str, object] | None = None,
         _strict_response_validation: bool,
     ) -> None:
-        kwargs: dict[str, Any] = {}
-        if limits is not None:
-            warnings.warn(
-                "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
-        else:
-            limits = DEFAULT_CONNECTION_LIMITS
-
-        if transport is not None:
-            kwargs["transport"] = transport
-            warnings.warn(
-                "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
-
-        if proxies is not None:
-            kwargs["proxies"] = proxies
-            warnings.warn(
-                "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
-
         if not is_given(timeout):
             # if the user passed in a custom http client with a non-default
             # timeout set then we use that timeout.
@@ -860,12 +812,9 @@ def __init__(
 
         super().__init__(
             version=version,
-            limits=limits,
             # cast to a valid type because mypy doesn't understand our type narrowing
             timeout=cast(Timeout, timeout),
-            proxies=proxies,
             base_url=base_url,
-            transport=transport,
             max_retries=max_retries,
             custom_query=custom_query,
             custom_headers=custom_headers,
@@ -875,9 +824,6 @@ def __init__(
             base_url=base_url,
             # cast to a valid type because mypy doesn't understand our type narrowing
             timeout=cast(Timeout, timeout),
-            limits=limits,
-            follow_redirects=True,
-            **kwargs,  # type: ignore
         )
 
     def is_closed(self) -> bool:
@@ -1372,45 +1318,10 @@ def __init__(
         _strict_response_validation: bool,
         max_retries: int = DEFAULT_MAX_RETRIES,
         timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
-        transport: AsyncTransport | None = None,
-        proxies: ProxiesTypes | None = None,
-        limits: Limits | None = None,
         http_client: httpx.AsyncClient | None = None,
         custom_headers: Mapping[str, str] | None = None,
         custom_query: Mapping[str, object] | None = None,
     ) -> None:
-        kwargs: dict[str, Any] = {}
-        if limits is not None:
-            warnings.warn(
-                "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
-        else:
-            limits = DEFAULT_CONNECTION_LIMITS
-
-        if transport is not None:
-            kwargs["transport"] = transport
-            warnings.warn(
-                "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
-
-        if proxies is not None:
-            kwargs["proxies"] = proxies
-            warnings.warn(
-                "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
-                category=DeprecationWarning,
-                stacklevel=3,
-            )
-            if http_client is not None:
-                raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
-
         if not is_given(timeout):
             # if the user passed in a custom http client with a non-default
             # timeout set then we use that timeout.
@@ -1432,11 +1343,8 @@ def __init__(
         super().__init__(
             version=version,
             base_url=base_url,
-            limits=limits,
             # cast to a valid type because mypy doesn't understand our type narrowing
             timeout=cast(Timeout, timeout),
-            proxies=proxies,
-            transport=transport,
             max_retries=max_retries,
             custom_query=custom_query,
             custom_headers=custom_headers,
@@ -1446,9 +1354,6 @@ def __init__(
             base_url=base_url,
             # cast to a valid type because mypy doesn't understand our type narrowing
             timeout=cast(Timeout, timeout),
-            limits=limits,
-            follow_redirects=True,
-            **kwargs,  # type: ignore
         )
 
     def is_closed(self) -> bool:

From 08b9e6959b4621e6272e5c332dbe97e9bce7cd99 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 10:45:29 +0000
Subject: [PATCH 067/227] docs: revise readme docs about nested params (#77)

---
 README.md | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/README.md b/README.md
index 7a4f2e12..95452e72 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,23 @@ for entry in first_page.entries:
 # Remove `await` for non-async usage.
 ```
 
+## Nested params
+
+Nested parameters are dictionaries, typed using `TypedDict`, for example:
+
+```python
+from codex import Codex
+
+client = Codex()
+
+project_return_schema = client.projects.create(
+    config={"max_distance": 0},
+    name="name",
+    organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(project_return_schema.config)
+```
+
 ## Handling errors
 
 When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised.

From e9b6186795a630a163d300125a26ea65df9d3866 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 10:59:01 +0000
Subject: [PATCH 068/227] test: add DEFER_PYDANTIC_BUILD=false flag to tests
 (#78)

---
 scripts/test | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/scripts/test b/scripts/test
index 4fa5698b..2b878456 100755
--- a/scripts/test
+++ b/scripts/test
@@ -52,6 +52,8 @@ else
   echo
 fi
 
+export DEFER_PYDANTIC_BUILD=false
+
 echo "==> Running tests"
 rye run pytest "$@"
 

From 502ace3fc76a71330e32c25b54b3a20cc016dc61 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 19:45:39 +0000
Subject: [PATCH 069/227] feat(api): add tlm routes (#79)

---
 .stats.yml                                    |   2 +-
 api.md                                        |  25 +
 src/codex/_client.py                          |  10 +-
 src/codex/resources/__init__.py               |  14 +
 src/codex/resources/tlm.py                    | 656 ++++++++++++++++++
 src/codex/resources/users/__init__.py         |  14 +
 src/codex/resources/users/users.py            |  32 +
 src/codex/resources/users/verification.py     | 135 ++++
 src/codex/types/__init__.py                   |   4 +
 src/codex/types/tlm_prompt_params.py          | 127 ++++
 src/codex/types/tlm_prompt_response.py        |  15 +
 src/codex/types/tlm_score_params.py           | 129 ++++
 src/codex/types/tlm_score_response.py         |  13 +
 src/codex/types/users/__init__.py             |   1 +
 .../users/verification_resend_response.py     |   8 +
 tests/api_resources/test_tlm.py               | 254 +++++++
 .../api_resources/users/test_verification.py  |  78 +++
 17 files changed, 1515 insertions(+), 2 deletions(-)
 create mode 100644 src/codex/resources/tlm.py
 create mode 100644 src/codex/resources/users/verification.py
 create mode 100644 src/codex/types/tlm_prompt_params.py
 create mode 100644 src/codex/types/tlm_prompt_response.py
 create mode 100644 src/codex/types/tlm_score_params.py
 create mode 100644 src/codex/types/tlm_score_response.py
 create mode 100644 src/codex/types/users/verification_resend_response.py
 create mode 100644 tests/api_resources/test_tlm.py
 create mode 100644 tests/api_resources/users/test_verification.py

diff --git a/.stats.yml b/.stats.yml
index 966331a1..7982133f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 34
+configured_endpoints: 37
diff --git a/api.md b/api.md
index b2fc2f00..5e258334 100644
--- a/api.md
+++ b/api.md
@@ -119,6 +119,18 @@ Methods:
 
 - <code title="get /api/users/myself/organizations">client.users.myself.organizations.<a href="./src/codex/resources/users/myself/organizations.py">list</a>() -> <a href="./src/codex/types/users/myself/user_organizations_schema.py">UserOrganizationsSchema</a></code>
 
+## Verification
+
+Types:
+
+```python
+from codex.types.users import VerificationResendResponse
+```
+
+Methods:
+
+- <code title="post /api/users/verification/resend">client.users.verification.<a href="./src/codex/resources/users/verification.py">resend</a>() -> <a href="./src/codex/types/users/verification_resend_response.py">VerificationResendResponse</a></code>
+
 # Projects
 
 Types:
@@ -175,3 +187,16 @@ Methods:
 - <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
 - <code title="post /api/projects/{project_id}/entries/add_question">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/entry_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
+
+# Tlm
+
+Types:
+
+```python
+from codex.types import TlmPromptResponse, TlmScoreResponse
+```
+
+Methods:
+
+- <code title="post /api/tlm/prompt">client.tlm.<a href="./src/codex/resources/tlm.py">prompt</a>(\*\*<a href="src/codex/types/tlm_prompt_params.py">params</a>) -> <a href="./src/codex/types/tlm_prompt_response.py">TlmPromptResponse</a></code>
+- <code title="post /api/tlm/score">client.tlm.<a href="./src/codex/resources/tlm.py">score</a>(\*\*<a href="src/codex/types/tlm_score_params.py">params</a>) -> <a href="./src/codex/types/tlm_score_response.py">TlmScoreResponse</a></code>
diff --git a/src/codex/_client.py b/src/codex/_client.py
index deff2779..9bdd5f02 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -25,7 +25,7 @@
     get_async_library,
 )
 from ._version import __version__
-from .resources import health
+from .resources import tlm, health
 from ._streaming import Stream as Stream, AsyncStream as AsyncStream
 from ._exceptions import APIStatusError
 from ._base_client import (
@@ -61,6 +61,7 @@ class Codex(SyncAPIClient):
     organizations: organizations.OrganizationsResource
     users: users.UsersResource
     projects: projects.ProjectsResource
+    tlm: tlm.TlmResource
     with_raw_response: CodexWithRawResponse
     with_streaming_response: CodexWithStreamedResponse
 
@@ -141,6 +142,7 @@ def __init__(
         self.organizations = organizations.OrganizationsResource(self)
         self.users = users.UsersResource(self)
         self.projects = projects.ProjectsResource(self)
+        self.tlm = tlm.TlmResource(self)
         self.with_raw_response = CodexWithRawResponse(self)
         self.with_streaming_response = CodexWithStreamedResponse(self)
 
@@ -291,6 +293,7 @@ class AsyncCodex(AsyncAPIClient):
     organizations: organizations.AsyncOrganizationsResource
     users: users.AsyncUsersResource
     projects: projects.AsyncProjectsResource
+    tlm: tlm.AsyncTlmResource
     with_raw_response: AsyncCodexWithRawResponse
     with_streaming_response: AsyncCodexWithStreamedResponse
 
@@ -371,6 +374,7 @@ def __init__(
         self.organizations = organizations.AsyncOrganizationsResource(self)
         self.users = users.AsyncUsersResource(self)
         self.projects = projects.AsyncProjectsResource(self)
+        self.tlm = tlm.AsyncTlmResource(self)
         self.with_raw_response = AsyncCodexWithRawResponse(self)
         self.with_streaming_response = AsyncCodexWithStreamedResponse(self)
 
@@ -522,6 +526,7 @@ def __init__(self, client: Codex) -> None:
         self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.UsersResourceWithRawResponse(client.users)
         self.projects = projects.ProjectsResourceWithRawResponse(client.projects)
+        self.tlm = tlm.TlmResourceWithRawResponse(client.tlm)
 
 
 class AsyncCodexWithRawResponse:
@@ -530,6 +535,7 @@ def __init__(self, client: AsyncCodex) -> None:
         self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithRawResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects)
+        self.tlm = tlm.AsyncTlmResourceWithRawResponse(client.tlm)
 
 
 class CodexWithStreamedResponse:
@@ -538,6 +544,7 @@ def __init__(self, client: Codex) -> None:
         self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.UsersResourceWithStreamingResponse(client.users)
         self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects)
+        self.tlm = tlm.TlmResourceWithStreamingResponse(client.tlm)
 
 
 class AsyncCodexWithStreamedResponse:
@@ -546,6 +553,7 @@ def __init__(self, client: AsyncCodex) -> None:
         self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations)
         self.users = users.AsyncUsersResourceWithStreamingResponse(client.users)
         self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects)
+        self.tlm = tlm.AsyncTlmResourceWithStreamingResponse(client.tlm)
 
 
 Client = Codex
diff --git a/src/codex/resources/__init__.py b/src/codex/resources/__init__.py
index b96b725a..f91f0e43 100644
--- a/src/codex/resources/__init__.py
+++ b/src/codex/resources/__init__.py
@@ -1,5 +1,13 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
+from .tlm import (
+    TlmResource,
+    AsyncTlmResource,
+    TlmResourceWithRawResponse,
+    AsyncTlmResourceWithRawResponse,
+    TlmResourceWithStreamingResponse,
+    AsyncTlmResourceWithStreamingResponse,
+)
 from .users import (
     UsersResource,
     AsyncUsersResource,
@@ -58,4 +66,10 @@
     "AsyncProjectsResourceWithRawResponse",
     "ProjectsResourceWithStreamingResponse",
     "AsyncProjectsResourceWithStreamingResponse",
+    "TlmResource",
+    "AsyncTlmResource",
+    "TlmResourceWithRawResponse",
+    "AsyncTlmResourceWithRawResponse",
+    "TlmResourceWithStreamingResponse",
+    "AsyncTlmResourceWithStreamingResponse",
 ]
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
new file mode 100644
index 00000000..c6585d0d
--- /dev/null
+++ b/src/codex/resources/tlm.py
@@ -0,0 +1,656 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import tlm_score_params, tlm_prompt_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import (
+    maybe_transform,
+    async_maybe_transform,
+)
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.tlm_score_response import TlmScoreResponse
+from ..types.tlm_prompt_response import TlmPromptResponse
+
+__all__ = ["TlmResource", "AsyncTlmResource"]
+
+
+class TlmResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> TlmResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return TlmResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> TlmResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return TlmResourceWithStreamingResponse(self)
+
+    def prompt(
+        self,
+        *,
+        prompt: str,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TlmPromptResponse:
+        """
+        Prompts the TLM API.
+
+        Args:
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o1",
+              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+              "gpt-4o-mini".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+                  This parameter must be between 1 and 20.
+                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+                  This consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+                  generated and self-evaluate this response.
+                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches answers that are obviously incorrect/bad.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+                  similarity between sampled responses considered by the model in the consistency assessment.
+                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+                  Set this to "string" to improve latency/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+                  when considering alternative possible responses and double-checking responses.
+                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/api/tlm/prompt",
+            body=maybe_transform(
+                {
+                    "prompt": prompt,
+                    "constrain_outputs": constrain_outputs,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                tlm_prompt_params.TlmPromptParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TlmPromptResponse,
+        )
+
+    def score(
+        self,
+        *,
+        prompt: str,
+        response: str,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TlmScoreResponse:
+        """
+        Scores the TLM API.
+
+        TODO:
+
+        - Track query count in DB
+        - Enforce hard cap on queries for users w/o credit card on file
+
+        Args:
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o1",
+              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+              "gpt-4o-mini".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+                  This parameter must be between 1 and 20.
+                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+                  This consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+                  generated and self-evaluate this response.
+                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches answers that are obviously incorrect/bad.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+                  similarity between sampled responses considered by the model in the consistency assessment.
+                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+                  Set this to "string" to improve latency/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+                  when considering alternative possible responses and double-checking responses.
+                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return self._post(
+            "/api/tlm/score",
+            body=maybe_transform(
+                {
+                    "prompt": prompt,
+                    "response": response,
+                    "constrain_outputs": constrain_outputs,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                tlm_score_params.TlmScoreParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TlmScoreResponse,
+        )
+
+
+class AsyncTlmResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncTlmResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncTlmResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncTlmResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncTlmResourceWithStreamingResponse(self)
+
+    async def prompt(
+        self,
+        *,
+        prompt: str,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TlmPromptResponse:
+        """
+        Prompts the TLM API.
+
+        Args:
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o1",
+              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+              "gpt-4o-mini".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+                  This parameter must be between 1 and 20.
+                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+                  This consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+                  generated and self-evaluate this response.
+                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches answers that are obviously incorrect/bad.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+                  similarity between sampled responses considered by the model in the consistency assessment.
+                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+                  Set this to "string" to improve latency/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+                  when considering alternative possible responses and double-checking responses.
+                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/api/tlm/prompt",
+            body=await async_maybe_transform(
+                {
+                    "prompt": prompt,
+                    "constrain_outputs": constrain_outputs,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                tlm_prompt_params.TlmPromptParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TlmPromptResponse,
+        )
+
+    async def score(
+        self,
+        *,
+        prompt: str,
+        response: str,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> TlmScoreResponse:
+        """
+        Scores the TLM API.
+
+        TODO:
+
+        - Track query count in DB
+        - Enforce hard cap on queries for users w/o credit card on file
+
+        Args:
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o1",
+              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+              "gpt-4o-mini".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+                  This parameter must be between 1 and 20.
+                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+                  This consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+                  generated and self-evaluate this response.
+                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches answers that are obviously incorrect/bad.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+                  similarity between sampled responses considered by the model in the consistency assessment.
+                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+                  Set this to "string" to improve latency/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+                  when considering alternative possible responses and double-checking responses.
+                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        return await self._post(
+            "/api/tlm/score",
+            body=await async_maybe_transform(
+                {
+                    "prompt": prompt,
+                    "response": response,
+                    "constrain_outputs": constrain_outputs,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                tlm_score_params.TlmScoreParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=TlmScoreResponse,
+        )
+
+
+class TlmResourceWithRawResponse:
+    def __init__(self, tlm: TlmResource) -> None:
+        self._tlm = tlm
+
+        self.prompt = to_raw_response_wrapper(
+            tlm.prompt,
+        )
+        self.score = to_raw_response_wrapper(
+            tlm.score,
+        )
+
+
+class AsyncTlmResourceWithRawResponse:
+    def __init__(self, tlm: AsyncTlmResource) -> None:
+        self._tlm = tlm
+
+        self.prompt = async_to_raw_response_wrapper(
+            tlm.prompt,
+        )
+        self.score = async_to_raw_response_wrapper(
+            tlm.score,
+        )
+
+
+class TlmResourceWithStreamingResponse:
+    def __init__(self, tlm: TlmResource) -> None:
+        self._tlm = tlm
+
+        self.prompt = to_streamed_response_wrapper(
+            tlm.prompt,
+        )
+        self.score = to_streamed_response_wrapper(
+            tlm.score,
+        )
+
+
+class AsyncTlmResourceWithStreamingResponse:
+    def __init__(self, tlm: AsyncTlmResource) -> None:
+        self._tlm = tlm
+
+        self.prompt = async_to_streamed_response_wrapper(
+            tlm.prompt,
+        )
+        self.score = async_to_streamed_response_wrapper(
+            tlm.score,
+        )
diff --git a/src/codex/resources/users/__init__.py b/src/codex/resources/users/__init__.py
index 18ed37eb..9618f588 100644
--- a/src/codex/resources/users/__init__.py
+++ b/src/codex/resources/users/__init__.py
@@ -16,6 +16,14 @@
     MyselfResourceWithStreamingResponse,
     AsyncMyselfResourceWithStreamingResponse,
 )
+from .verification import (
+    VerificationResource,
+    AsyncVerificationResource,
+    VerificationResourceWithRawResponse,
+    AsyncVerificationResourceWithRawResponse,
+    VerificationResourceWithStreamingResponse,
+    AsyncVerificationResourceWithStreamingResponse,
+)
 
 __all__ = [
     "MyselfResource",
@@ -24,6 +32,12 @@
     "AsyncMyselfResourceWithRawResponse",
     "MyselfResourceWithStreamingResponse",
     "AsyncMyselfResourceWithStreamingResponse",
+    "VerificationResource",
+    "AsyncVerificationResource",
+    "VerificationResourceWithRawResponse",
+    "AsyncVerificationResourceWithRawResponse",
+    "VerificationResourceWithStreamingResponse",
+    "AsyncVerificationResourceWithStreamingResponse",
     "UsersResource",
     "AsyncUsersResource",
     "UsersResourceWithRawResponse",
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index fb7ee0f1..a7d9d2ab 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -22,6 +22,14 @@
     async_to_streamed_response_wrapper,
 )
 from ...types.user import User
+from .verification import (
+    VerificationResource,
+    AsyncVerificationResource,
+    VerificationResourceWithRawResponse,
+    AsyncVerificationResourceWithRawResponse,
+    VerificationResourceWithStreamingResponse,
+    AsyncVerificationResourceWithStreamingResponse,
+)
 from .myself.myself import (
     MyselfResource,
     AsyncMyselfResource,
@@ -40,6 +48,10 @@ class UsersResource(SyncAPIResource):
     def myself(self) -> MyselfResource:
         return MyselfResource(self._client)
 
+    @cached_property
+    def verification(self) -> VerificationResource:
+        return VerificationResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> UsersResourceWithRawResponse:
         """
@@ -114,6 +126,10 @@ class AsyncUsersResource(AsyncAPIResource):
     def myself(self) -> AsyncMyselfResource:
         return AsyncMyselfResource(self._client)
 
+    @cached_property
+    def verification(self) -> AsyncVerificationResource:
+        return AsyncVerificationResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> AsyncUsersResourceWithRawResponse:
         """
@@ -195,6 +211,10 @@ def __init__(self, users: UsersResource) -> None:
     def myself(self) -> MyselfResourceWithRawResponse:
         return MyselfResourceWithRawResponse(self._users.myself)
 
+    @cached_property
+    def verification(self) -> VerificationResourceWithRawResponse:
+        return VerificationResourceWithRawResponse(self._users.verification)
+
 
 class AsyncUsersResourceWithRawResponse:
     def __init__(self, users: AsyncUsersResource) -> None:
@@ -208,6 +228,10 @@ def __init__(self, users: AsyncUsersResource) -> None:
     def myself(self) -> AsyncMyselfResourceWithRawResponse:
         return AsyncMyselfResourceWithRawResponse(self._users.myself)
 
+    @cached_property
+    def verification(self) -> AsyncVerificationResourceWithRawResponse:
+        return AsyncVerificationResourceWithRawResponse(self._users.verification)
+
 
 class UsersResourceWithStreamingResponse:
     def __init__(self, users: UsersResource) -> None:
@@ -221,6 +245,10 @@ def __init__(self, users: UsersResource) -> None:
     def myself(self) -> MyselfResourceWithStreamingResponse:
         return MyselfResourceWithStreamingResponse(self._users.myself)
 
+    @cached_property
+    def verification(self) -> VerificationResourceWithStreamingResponse:
+        return VerificationResourceWithStreamingResponse(self._users.verification)
+
 
 class AsyncUsersResourceWithStreamingResponse:
     def __init__(self, users: AsyncUsersResource) -> None:
@@ -233,3 +261,7 @@ def __init__(self, users: AsyncUsersResource) -> None:
     @cached_property
     def myself(self) -> AsyncMyselfResourceWithStreamingResponse:
         return AsyncMyselfResourceWithStreamingResponse(self._users.myself)
+
+    @cached_property
+    def verification(self) -> AsyncVerificationResourceWithStreamingResponse:
+        return AsyncVerificationResourceWithStreamingResponse(self._users.verification)
diff --git a/src/codex/resources/users/verification.py b/src/codex/resources/users/verification.py
new file mode 100644
index 00000000..e75326e1
--- /dev/null
+++ b/src/codex/resources/users/verification.py
@@ -0,0 +1,135 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.users.verification_resend_response import VerificationResendResponse
+
+__all__ = ["VerificationResource", "AsyncVerificationResource"]
+
+
+class VerificationResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> VerificationResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return VerificationResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> VerificationResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return VerificationResourceWithStreamingResponse(self)
+
+    def resend(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> VerificationResendResponse:
+        """Resend verification email to the specified user through Auth0."""
+        return self._post(
+            "/api/users/verification/resend",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=VerificationResendResponse,
+        )
+
+
+class AsyncVerificationResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncVerificationResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncVerificationResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncVerificationResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncVerificationResourceWithStreamingResponse(self)
+
+    async def resend(
+        self,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> VerificationResendResponse:
+        """Resend verification email to the specified user through Auth0."""
+        return await self._post(
+            "/api/users/verification/resend",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=VerificationResendResponse,
+        )
+
+
+class VerificationResourceWithRawResponse:
+    def __init__(self, verification: VerificationResource) -> None:
+        self._verification = verification
+
+        self.resend = to_raw_response_wrapper(
+            verification.resend,
+        )
+
+
+class AsyncVerificationResourceWithRawResponse:
+    def __init__(self, verification: AsyncVerificationResource) -> None:
+        self._verification = verification
+
+        self.resend = async_to_raw_response_wrapper(
+            verification.resend,
+        )
+
+
+class VerificationResourceWithStreamingResponse:
+    def __init__(self, verification: VerificationResource) -> None:
+        self._verification = verification
+
+        self.resend = to_streamed_response_wrapper(
+            verification.resend,
+        )
+
+
+class AsyncVerificationResourceWithStreamingResponse:
+    def __init__(self, verification: AsyncVerificationResource) -> None:
+        self._verification = verification
+
+        self.resend = async_to_streamed_response_wrapper(
+            verification.resend,
+        )
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index f7ec95b6..8f241bc9 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -3,7 +3,11 @@
 from __future__ import annotations
 
 from .user import User as User
+from .tlm_score_params import TlmScoreParams as TlmScoreParams
+from .tlm_prompt_params import TlmPromptParams as TlmPromptParams
+from .tlm_score_response import TlmScoreResponse as TlmScoreResponse
 from .project_list_params import ProjectListParams as ProjectListParams
+from .tlm_prompt_response import TlmPromptResponse as TlmPromptResponse
 from .health_check_response import HealthCheckResponse as HealthCheckResponse
 from .project_create_params import ProjectCreateParams as ProjectCreateParams
 from .project_list_response import ProjectListResponse as ProjectListResponse
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
new file mode 100644
index 00000000..860f1a77
--- /dev/null
+++ b/src/codex/types/tlm_prompt_params.py
@@ -0,0 +1,127 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Iterable, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["TlmPromptParams", "Options"]
+
+
+class TlmPromptParams(TypedDict, total=False):
+    prompt: Required[str]
+
+    constrain_outputs: Optional[List[str]]
+
+    options: Optional[Options]
+    """
+    Typed dict of advanced configuration options for the Trustworthy Language Model.
+    Many of these configurations are determined by the quality preset selected
+    (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+    Specifying TLMOptions values directly overrides any default values set from the
+    quality preset.
+
+    For all options described below, higher settings will lead to longer runtimes
+    and may consume more tokens internally. You may not be able to run long prompts
+    (or prompts with long responses) in your account, unless your token/rate limits
+    are increased. If you hit token limit issues, try lower/less expensive
+    TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+    increase your limits.
+
+    The default values corresponding to each quality preset are:
+
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a cheaper self-reflection will be used to compute the
+      trustworthiness score.
+
+    By default, the TLM uses the "medium" quality preset. The default base LLM
+    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+    You can set custom values for these arguments regardless of the quality preset
+    specified.
+
+    Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+    "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+    faster models yield faster/cheaper results). - Models still in beta: "o1",
+    "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+    "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+    "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+    "gpt-4o-mini".
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+        If you experience token/rate limit errors while using TLM, try lowering this number.
+        For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+        TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+        This parameter must be between 1 and 20.
+        When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+        num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+        This consistency helps quantify the epistemic uncertainty associated with
+        strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+        TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+        generated and self-evaluate this response.
+        Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+        Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches answers that are obviously incorrect/bad.
+
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+        similarity between sampled responses considered by the model in the consistency assessment.
+        Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+        "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+        Set this to "string" to improve latency/costs.
+
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+        when considering alternative possible responses and double-checking responses.
+        Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+    """
+
+    quality_preset: Literal["best", "high", "medium", "low", "base"]
+
+    task: Optional[str]
+
+
+class Options(TypedDict, total=False):
+    custom_eval_criteria: Iterable[object]
+
+    log: List[str]
+
+    max_tokens: int
+
+    model: str
+
+    num_candidate_responses: int
+
+    num_consistency_samples: int
+
+    reasoning_effort: str
+
+    similarity_measure: str
+
+    use_self_reflection: bool
diff --git a/src/codex/types/tlm_prompt_response.py b/src/codex/types/tlm_prompt_response.py
new file mode 100644
index 00000000..d939c00e
--- /dev/null
+++ b/src/codex/types/tlm_prompt_response.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["TlmPromptResponse"]
+
+
+class TlmPromptResponse(BaseModel):
+    response: str
+
+    trustworthiness_score: float
+
+    log: Optional[object] = None
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
new file mode 100644
index 00000000..213da422
--- /dev/null
+++ b/src/codex/types/tlm_score_params.py
@@ -0,0 +1,129 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Iterable, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["TlmScoreParams", "Options"]
+
+
+class TlmScoreParams(TypedDict, total=False):
+    prompt: Required[str]
+
+    response: Required[str]
+
+    constrain_outputs: Optional[List[str]]
+
+    options: Optional[Options]
+    """
+    Typed dict of advanced configuration options for the Trustworthy Language Model.
+    Many of these configurations are determined by the quality preset selected
+    (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+    Specifying TLMOptions values directly overrides any default values set from the
+    quality preset.
+
+    For all options described below, higher settings will lead to longer runtimes
+    and may consume more tokens internally. You may not be able to run long prompts
+    (or prompts with long responses) in your account, unless your token/rate limits
+    are increased. If you hit token limit issues, try lower/less expensive
+    TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+    increase your limits.
+
+    The default values corresponding to each quality preset are:
+
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a cheaper self-reflection will be used to compute the
+      trustworthiness score.
+
+    By default, the TLM uses the "medium" quality preset. The default base LLM
+    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+    You can set custom values for these arguments regardless of the quality preset
+    specified.
+
+    Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
+    "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+    faster models yield faster/cheaper results). - Models still in beta: "o1",
+    "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
+    "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
+    "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
+    "gpt-4o-mini".
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+        If you experience token/rate limit errors while using TLM, try lowering this number.
+        For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
+        TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
+        This parameter must be between 1 and 20.
+        When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+
+        num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
+        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
+        This consistency helps quantify the epistemic uncertainty associated with
+        strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+        TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
+        generated and self-evaluate this response.
+        Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
+        Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches answers that are obviously incorrect/bad.
+
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
+        similarity between sampled responses considered by the model in the consistency assessment.
+        Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
+        "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
+        Set this to "string" to improve latency/costs.
+
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
+        when considering alternative possible responses and double-checking responses.
+        Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+    """
+
+    quality_preset: Literal["best", "high", "medium", "low", "base"]
+
+    task: Optional[str]
+
+
+class Options(TypedDict, total=False):
+    custom_eval_criteria: Iterable[object]
+
+    log: List[str]
+
+    max_tokens: int
+
+    model: str
+
+    num_candidate_responses: int
+
+    num_consistency_samples: int
+
+    reasoning_effort: str
+
+    similarity_measure: str
+
+    use_self_reflection: bool
diff --git a/src/codex/types/tlm_score_response.py b/src/codex/types/tlm_score_response.py
new file mode 100644
index 00000000..e92b2e09
--- /dev/null
+++ b/src/codex/types/tlm_score_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["TlmScoreResponse"]
+
+
+class TlmScoreResponse(BaseModel):
+    trustworthiness_score: float
+
+    log: Optional[object] = None
diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py
index 4256bd7f..438bc6f3 100644
--- a/src/codex/types/users/__init__.py
+++ b/src/codex/types/users/__init__.py
@@ -3,3 +3,4 @@
 from __future__ import annotations
 
 from .user_schema import UserSchema as UserSchema
+from .verification_resend_response import VerificationResendResponse as VerificationResendResponse
diff --git a/src/codex/types/users/verification_resend_response.py b/src/codex/types/users/verification_resend_response.py
new file mode 100644
index 00000000..6617ff54
--- /dev/null
+++ b/src/codex/types/users/verification_resend_response.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["VerificationResendResponse"]
+
+VerificationResendResponse: TypeAlias = Dict[str, str]
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
new file mode 100644
index 00000000..32d5a67f
--- /dev/null
+++ b/tests/api_resources/test_tlm.py
@@ -0,0 +1,254 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import TlmScoreResponse, TlmPromptResponse
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestTlm:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_prompt(self, client: Codex) -> None:
+        tlm = client.tlm.prompt(
+            prompt="prompt",
+        )
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_prompt_with_all_params(self, client: Codex) -> None:
+        tlm = client.tlm.prompt(
+            prompt="prompt",
+            constrain_outputs=["string"],
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+        )
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_prompt(self, client: Codex) -> None:
+        response = client.tlm.with_raw_response.prompt(
+            prompt="prompt",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        tlm = response.parse()
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_prompt(self, client: Codex) -> None:
+        with client.tlm.with_streaming_response.prompt(
+            prompt="prompt",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            tlm = response.parse()
+            assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_score(self, client: Codex) -> None:
+        tlm = client.tlm.score(
+            prompt="prompt",
+            response="response",
+        )
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_score_with_all_params(self, client: Codex) -> None:
+        tlm = client.tlm.score(
+            prompt="prompt",
+            response="response",
+            constrain_outputs=["string"],
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+        )
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_score(self, client: Codex) -> None:
+        response = client.tlm.with_raw_response.score(
+            prompt="prompt",
+            response="response",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        tlm = response.parse()
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_score(self, client: Codex) -> None:
+        with client.tlm.with_streaming_response.score(
+            prompt="prompt",
+            response="response",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            tlm = response.parse()
+            assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncTlm:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_prompt(self, async_client: AsyncCodex) -> None:
+        tlm = await async_client.tlm.prompt(
+            prompt="prompt",
+        )
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> None:
+        tlm = await async_client.tlm.prompt(
+            prompt="prompt",
+            constrain_outputs=["string"],
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+        )
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None:
+        response = await async_client.tlm.with_raw_response.prompt(
+            prompt="prompt",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        tlm = await response.parse()
+        assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None:
+        async with async_client.tlm.with_streaming_response.prompt(
+            prompt="prompt",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            tlm = await response.parse()
+            assert_matches_type(TlmPromptResponse, tlm, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_score(self, async_client: AsyncCodex) -> None:
+        tlm = await async_client.tlm.score(
+            prompt="prompt",
+            response="response",
+        )
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> None:
+        tlm = await async_client.tlm.score(
+            prompt="prompt",
+            response="response",
+            constrain_outputs=["string"],
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+        )
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_score(self, async_client: AsyncCodex) -> None:
+        response = await async_client.tlm.with_raw_response.score(
+            prompt="prompt",
+            response="response",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        tlm = await response.parse()
+        assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_score(self, async_client: AsyncCodex) -> None:
+        async with async_client.tlm.with_streaming_response.score(
+            prompt="prompt",
+            response="response",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            tlm = await response.parse()
+            assert_matches_type(TlmScoreResponse, tlm, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py
new file mode 100644
index 00000000..8332327e
--- /dev/null
+++ b/tests/api_resources/users/test_verification.py
@@ -0,0 +1,78 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.types.users import VerificationResendResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVerification:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_resend(self, client: Codex) -> None:
+        verification = client.users.verification.resend()
+        assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_resend(self, client: Codex) -> None:
+        response = client.users.verification.with_raw_response.resend()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        verification = response.parse()
+        assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_resend(self, client: Codex) -> None:
+        with client.users.verification.with_streaming_response.resend() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            verification = response.parse()
+            assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVerification:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_resend(self, async_client: AsyncCodex) -> None:
+        verification = await async_client.users.verification.resend()
+        assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_resend(self, async_client: AsyncCodex) -> None:
+        response = await async_client.users.verification.with_raw_response.resend()
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        verification = await response.parse()
+        assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_resend(self, async_client: AsyncCodex) -> None:
+        async with async_client.users.verification.with_streaming_response.resend() as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            verification = await response.parse()
+            assert_matches_type(VerificationResendResponse, verification, path=["response"])
+
+        assert cast(Any, response.is_closed) is True

From 317bef6441a5f59ab99151bba9aec66d95862e1a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 19:58:25 +0000
Subject: [PATCH 070/227] chore(internal): version bump (#81)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index ee49ac2d..fd0ccba9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.11"
+  ".": "0.1.0-alpha.12"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 7594a895..82e32b0a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.11"
+version = "0.1.0-alpha.12"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index fd5f7081..fe7cc739 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.11"  # x-release-please-version
+__version__ = "0.1.0-alpha.12"  # x-release-please-version

From bf43bf1e2d241de642f4736493f1ea7f592f3512 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 13 Mar 2025 21:29:05 +0000
Subject: [PATCH 071/227] feat(api): api update (#83)

---
 src/codex/resources/projects/entries.py         | 14 +++++++++++++-
 src/codex/types/projects/entry.py               |  7 +++++++
 src/codex/types/projects/entry_create_params.py |  2 ++
 src/codex/types/projects/entry_list_params.py   |  3 +++
 src/codex/types/projects/entry_update_params.py |  2 ++
 tests/api_resources/projects/test_entries.py    |  6 ++++++
 6 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 2fcc8e0b..e2c16547 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import Optional
+from typing import List, Optional
 from typing_extensions import Literal
 
 import httpx
@@ -60,6 +60,7 @@ def create(
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -89,6 +90,7 @@ def create(
                 {
                     "question": question,
                     "answer": answer,
+                    "draft_answer": draft_answer,
                 },
                 entry_create_params.EntryCreateParams,
             ),
@@ -140,6 +142,7 @@ def update(
         *,
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -170,6 +173,7 @@ def update(
             body=maybe_transform(
                 {
                     "answer": answer,
+                    "draft_answer": draft_answer,
                     "frequency_count": frequency_count,
                     "question": question,
                 },
@@ -190,6 +194,7 @@ def list(
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         unanswered_only: bool | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -227,6 +232,7 @@ def list(
                         "offset": offset,
                         "order": order,
                         "sort": sort,
+                        "states": states,
                         "unanswered_only": unanswered_only,
                     },
                     entry_list_params.EntryListParams,
@@ -379,6 +385,7 @@ async def create(
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -408,6 +415,7 @@ async def create(
                 {
                     "question": question,
                     "answer": answer,
+                    "draft_answer": draft_answer,
                 },
                 entry_create_params.EntryCreateParams,
             ),
@@ -459,6 +467,7 @@ async def update(
         *,
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -489,6 +498,7 @@ async def update(
             body=await async_maybe_transform(
                 {
                     "answer": answer,
+                    "draft_answer": draft_answer,
                     "frequency_count": frequency_count,
                     "question": question,
                 },
@@ -509,6 +519,7 @@ def list(
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
+        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         unanswered_only: bool | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -546,6 +557,7 @@ def list(
                         "offset": offset,
                         "order": order,
                         "sort": sort,
+                        "states": states,
                         "unanswered_only": unanswered_only,
                     },
                     entry_list_params.EntryListParams,
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 4621cc4b..7a2c4502 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -2,6 +2,7 @@
 
 from typing import Optional
 from datetime import datetime
+from typing_extensions import Literal
 
 from ..._models import BaseModel
 
@@ -13,10 +14,16 @@ class Entry(BaseModel):
 
     question: str
 
+    state: Literal["unanswered", "draft", "published", "published_with_draft"]
+
     id: Optional[str] = None
 
     answer: Optional[str] = None
 
     answered_at: Optional[datetime] = None
 
+    draft_answer: Optional[str] = None
+
+    draft_answer_last_edited: Optional[datetime] = None
+
     frequency_count: Optional[int] = None
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
index 1ac23dd4..daf30897 100644
--- a/src/codex/types/projects/entry_create_params.py
+++ b/src/codex/types/projects/entry_create_params.py
@@ -12,3 +12,5 @@ class EntryCreateParams(TypedDict, total=False):
     question: Required[str]
 
     answer: Optional[str]
+
+    draft_answer: Optional[str]
diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/entry_list_params.py
index b50181f7..605ea482 100644
--- a/src/codex/types/projects/entry_list_params.py
+++ b/src/codex/types/projects/entry_list_params.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+from typing import List
 from typing_extensions import Literal, TypedDict
 
 __all__ = ["EntryListParams"]
@@ -18,4 +19,6 @@ class EntryListParams(TypedDict, total=False):
 
     sort: Literal["created_at", "answered_at"]
 
+    states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
+
     unanswered_only: bool
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
index ba105495..05a6332d 100644
--- a/src/codex/types/projects/entry_update_params.py
+++ b/src/codex/types/projects/entry_update_params.py
@@ -13,6 +13,8 @@ class EntryUpdateParams(TypedDict, total=False):
 
     answer: Optional[str]
 
+    draft_answer: Optional[str]
+
     frequency_count: Optional[int]
 
     question: Optional[str]
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 026add46..fbbf6f25 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -36,6 +36,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
+            draft_answer="draft_answer",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -144,6 +145,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
+            draft_answer="draft_answer",
             frequency_count=0,
             question="question",
         )
@@ -210,6 +212,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             offset=0,
             order="asc",
             sort="created_at",
+            states=["unanswered"],
             unanswered_only=True,
         )
         assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
@@ -412,6 +415,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
+            draft_answer="draft_answer",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -520,6 +524,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
             entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
+            draft_answer="draft_answer",
             frequency_count=0,
             question="question",
         )
@@ -586,6 +591,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             offset=0,
             order="asc",
             sort="created_at",
+            states=["unanswered"],
             unanswered_only=True,
         )
         assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])

From 08855389f32cb1786efdf789f6f44b786ee8ffe5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 14 Mar 2025 08:05:45 +0000
Subject: [PATCH 072/227] chore(internal): remove extra empty newlines (#84)

---
 pyproject.toml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 82e32b0a..e2e84f14 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,6 @@ Homepage = "https://github.com/cleanlab/codex-python"
 Repository = "https://github.com/cleanlab/codex-python"
 
 
-
 [tool.rye]
 managed = true
 # version pins are in requirements-dev.lock
@@ -152,7 +151,6 @@ reportImplicitOverride = true
 reportImportCycles = false
 reportPrivateUsage = false
 
-
 [tool.ruff]
 line-length = 120
 output-format = "grouped"

From 2a2b9db65825719141934848b2da87a24b7250b0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 06:29:25 +0000
Subject: [PATCH 073/227] chore(internal): codegen related update (#85)

---
 requirements-dev.lock | 1 +
 requirements.lock     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/requirements-dev.lock b/requirements-dev.lock
index 1961e8d6..5b7189d6 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -7,6 +7,7 @@
 #   all-features: true
 #   with-sources: false
 #   generate-hashes: false
+#   universal: false
 
 -e file:.
 annotated-types==0.6.0
diff --git a/requirements.lock b/requirements.lock
index 5d7fff82..1f6c5f7f 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -7,6 +7,7 @@
 #   all-features: true
 #   with-sources: false
 #   generate-hashes: false
+#   universal: false
 
 -e file:.
 annotated-types==0.6.0

From 4abff16a96af4e0b964d4e1a0b2a7831ef859314 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 06:32:58 +0000
Subject: [PATCH 074/227] chore(internal): bump rye to 0.44.0 (#86)

---
 .devcontainer/Dockerfile           | 2 +-
 .github/workflows/ci.yml           | 2 +-
 .github/workflows/publish-pypi.yml | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 55d20255..ff261bad 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
 
 USER vscode
 
-RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash
 ENV PATH=/home/vscode/.rye/shims:$PATH
 
 RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e503784c..5ac5f63f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
           curl -sSf https://rye.astral.sh/get | bash
           echo "$HOME/.rye/shims" >> $GITHUB_PATH
         env:
-          RYE_VERSION: '0.35.0'
+          RYE_VERSION: '0.44.0'
           RYE_INSTALL_OPTION: '--yes'
 
       - name: Install dependencies
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 5ed611c7..7c078aa3 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -21,7 +21,7 @@ jobs:
           curl -sSf https://rye.astral.sh/get | bash
           echo "$HOME/.rye/shims" >> $GITHUB_PATH
         env:
-          RYE_VERSION: '0.35.0'
+          RYE_VERSION: '0.44.0'
           RYE_INSTALL_OPTION: '--yes'
 
       - name: Publish to PyPI

From 1a1166db743a1447d98e21aaa8dfb2c2ef84804a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 06:37:06 +0000
Subject: [PATCH 075/227] fix(types): handle more discriminated union shapes
 (#87)

---
 src/codex/_models.py |  7 +++++--
 tests/test_models.py | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index c4401ff8..b51a1bf5 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -65,7 +65,7 @@
 from ._constants import RAW_RESPONSE_HEADER
 
 if TYPE_CHECKING:
-    from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema
+    from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
 
 __all__ = ["BaseModel", "GenericModel"]
 
@@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
 
 def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
     schema = model.__pydantic_core_schema__
+    if schema["type"] == "definitions":
+        schema = schema["schema"]
+
     if schema["type"] != "model":
         return None
 
+    schema = cast("ModelSchema", schema)
     fields_schema = schema["schema"]
     if fields_schema["type"] != "model-fields":
         return None
 
     fields_schema = cast("ModelFieldsSchema", fields_schema)
-
     field = fields_schema["fields"].get(field_name)
     if not field:
         return None
diff --git a/tests/test_models.py b/tests/test_models.py
index 26df341f..73e6d67c 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -854,3 +854,35 @@ class Model(BaseModel):
     m = construct_type(value={"cls": "foo"}, type_=Model)
     assert isinstance(m, Model)
     assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+    class A(BaseModel):
+        type: Literal["a"]
+
+        data: bool
+
+    class B(BaseModel):
+        type: Literal["b"]
+
+        data: List[Union[A, object]]
+
+    class ModelA(BaseModel):
+        type: Literal["modelA"]
+
+        data: int
+
+    class ModelB(BaseModel):
+        type: Literal["modelB"]
+
+        required: str
+
+        data: Union[A, B]
+
+    # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+    m = construct_type(
+        value={"type": "modelB", "data": {"type": "a", "data": True}},
+        type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+    )
+
+    assert isinstance(m, ModelB)

From cb2352da6b93465618458cf0d12a7408494ff3d5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 17 Mar 2025 17:58:10 +0000
Subject: [PATCH 076/227] fix(ci): ensure pip is always available (#88)

---
 bin/publish-pypi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/bin/publish-pypi b/bin/publish-pypi
index 05bfccbb..ebebf916 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -5,5 +5,6 @@ mkdir -p dist
 rye build --clean
 # Patching importlib-metadata version until upstream library version is updated
 # https://github.com/pypa/twine/issues/977#issuecomment-2189800841
+"$HOME/.rye/self/bin/python3" -m ensurepip
 "$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
 rye publish --yes --token=$PYPI_TOKEN

From e1383ac4afec1db3f70aba80b07401bc64ab4bdd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 17 Mar 2025 18:05:48 +0000
Subject: [PATCH 077/227] fix(ci): remove publishing patch (#89)

---
 bin/publish-pypi | 4 ----
 pyproject.toml   | 2 +-
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/bin/publish-pypi b/bin/publish-pypi
index ebebf916..826054e9 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -3,8 +3,4 @@
 set -eux
 mkdir -p dist
 rye build --clean
-# Patching importlib-metadata version until upstream library version is updated
-# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
-"$HOME/.rye/self/bin/python3" -m ensurepip
-"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
 rye publish --yes --token=$PYPI_TOKEN
diff --git a/pyproject.toml b/pyproject.toml
index e2e84f14..ff7153aa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -86,7 +86,7 @@ typecheck = { chain = [
 "typecheck:mypy" = "mypy ."
 
 [build-system]
-requires = ["hatchling", "hatch-fancy-pypi-readme"]
+requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
 build-backend = "hatchling.build"
 
 [tool.hatch.build]

From 418bcd8240fc62d97937ecefdd9747400f4ee2da Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 19 Mar 2025 17:29:08 +0000
Subject: [PATCH 078/227] feat(api): api update (#90)

---
 src/codex/resources/projects/access_keys.py   | 31 +++++++
 src/codex/resources/projects/entries.py       | 91 +++++++++++++++++++
 .../projects/access_key_create_params.py      |  8 ++
 .../projects/entry_add_question_params.py     | 12 ++-
 .../types/projects/entry_create_params.py     | 12 ++-
 .../types/projects/entry_query_params.py      | 12 ++-
 .../projects/test_access_keys.py              |  8 ++
 tests/api_resources/projects/test_entries.py  | 60 ++++++++++++
 8 files changed, 231 insertions(+), 3 deletions(-)

diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index d375dbca..61987399 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -10,6 +10,7 @@
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
 from ..._utils import (
     maybe_transform,
+    strip_not_given,
     async_maybe_transform,
 )
 from ..._compat import cached_property
@@ -56,6 +57,10 @@ def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -77,6 +82,17 @@ def create(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return self._post(
             f"/api/projects/{project_id}/access_keys/",
             body=maybe_transform(
@@ -330,6 +346,10 @@ async def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -351,6 +371,17 @@ async def create(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return await self._post(
             f"/api/projects/{project_id}/access_keys/",
             body=await async_maybe_transform(
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index e2c16547..f5eebad6 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -10,6 +10,7 @@
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
 from ..._utils import (
     maybe_transform,
+    strip_not_given,
     async_maybe_transform,
 )
 from ..._compat import cached_property
@@ -61,6 +62,10 @@ def create(
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -84,6 +89,17 @@ def create(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return self._post(
             f"/api/projects/{project_id}/entries/",
             body=maybe_transform(
@@ -283,6 +299,10 @@ def add_question(
         project_id: str,
         *,
         question: str,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -307,6 +327,17 @@ def add_question(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return self._post(
             f"/api/projects/{project_id}/entries/add_question",
             body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
@@ -321,6 +352,10 @@ def query(
         project_id: str,
         *,
         question: str,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -349,6 +384,17 @@ def query(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return self._post(
             f"/api/projects/{project_id}/entries/query",
             body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
@@ -386,6 +432,10 @@ async def create(
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -409,6 +459,17 @@ async def create(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return await self._post(
             f"/api/projects/{project_id}/entries/",
             body=await async_maybe_transform(
@@ -608,6 +669,10 @@ async def add_question(
         project_id: str,
         *,
         question: str,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -632,6 +697,17 @@ async def add_question(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return await self._post(
             f"/api/projects/{project_id}/entries/add_question",
             body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
@@ -646,6 +722,10 @@ async def query(
         project_id: str,
         *,
         question: str,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -674,6 +754,17 @@ async def query(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
         return await self._post(
             f"/api/projects/{project_id}/entries/query",
             body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py
index 1cbc202f..cf5f00fb 100644
--- a/src/codex/types/projects/access_key_create_params.py
+++ b/src/codex/types/projects/access_key_create_params.py
@@ -17,3 +17,11 @@ class AccessKeyCreateParams(TypedDict, total=False):
     description: Optional[str]
 
     expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+
+    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
+
+    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
+
+    x_source: Annotated[str, PropertyInfo(alias="x-source")]
+
+    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py
index e2d009b4..b310f96b 100644
--- a/src/codex/types/projects/entry_add_question_params.py
+++ b/src/codex/types/projects/entry_add_question_params.py
@@ -2,10 +2,20 @@
 
 from __future__ import annotations
 
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
 
 __all__ = ["EntryAddQuestionParams"]
 
 
 class EntryAddQuestionParams(TypedDict, total=False):
     question: Required[str]
+
+    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
+
+    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
+
+    x_source: Annotated[str, PropertyInfo(alias="x-source")]
+
+    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
index daf30897..0022942f 100644
--- a/src/codex/types/projects/entry_create_params.py
+++ b/src/codex/types/projects/entry_create_params.py
@@ -3,7 +3,9 @@
 from __future__ import annotations
 
 from typing import Optional
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
 
 __all__ = ["EntryCreateParams"]
 
@@ -14,3 +16,11 @@ class EntryCreateParams(TypedDict, total=False):
     answer: Optional[str]
 
     draft_answer: Optional[str]
+
+    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
+
+    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
+
+    x_source: Annotated[str, PropertyInfo(alias="x-source")]
+
+    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index b6fbc437..bc0e317c 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -2,10 +2,20 @@
 
 from __future__ import annotations
 
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
 
 __all__ = ["EntryQueryParams"]
 
 
 class EntryQueryParams(TypedDict, total=False):
     question: Required[str]
+
+    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
+
+    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
+
+    x_source: Annotated[str, PropertyInfo(alias="x-source")]
+
+    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index 240c31e1..ad4ee5e4 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -39,6 +39,10 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
@@ -395,6 +399,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index fbbf6f25..8a18dc01 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -37,6 +37,10 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             question="question",
             answer="answer",
             draft_answer="draft_answer",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -312,6 +316,19 @@ def test_method_add_question(self, client: Codex) -> None:
         )
         assert_matches_type(Entry, entry, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_add_question_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.add_question(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_add_question(self, client: Codex) -> None:
@@ -358,6 +375,19 @@ def test_method_query(self, client: Codex) -> None:
         )
         assert_matches_type(Optional[Entry], entry, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_query_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.query(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_query(self, client: Codex) -> None:
@@ -416,6 +446,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             question="question",
             answer="answer",
             draft_answer="draft_answer",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
         )
         assert_matches_type(Entry, entry, path=["response"])
 
@@ -691,6 +725,19 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Entry, entry, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_add_question_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.add_question(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
@@ -737,6 +784,19 @@ async def test_method_query(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Optional[Entry], entry, path=["response"])
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.query(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(Optional[Entry], entry, path=["response"])
+
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_query(self, async_client: AsyncCodex) -> None:

From d021150a95529d5d47bea715a7c4803b56c4fbf5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 05:25:22 +0000
Subject: [PATCH 079/227] chore: fix typos (#91)

---
 src/codex/_models.py           | 2 +-
 src/codex/_utils/_transform.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index b51a1bf5..34935716 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -681,7 +681,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
     setattr(typ, "__pydantic_config__", config)  # noqa: B010
 
 
-# our use of subclasssing here causes weirdness for type checkers,
+# our use of subclassing here causes weirdness for type checkers,
 # so we just pretend that we don't subclass
 if TYPE_CHECKING:
     GenericModel = BaseModel
diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 18afd9d8..7ac2e17f 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -126,7 +126,7 @@ def _get_annotated_type(type_: type) -> type | None:
 def _maybe_transform_key(key: str, type_: type) -> str:
     """Transform the given `data` based on the annotations provided in `type_`.
 
-    Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata.
+    Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
     """
     annotated_type = _get_annotated_type(type_)
     if annotated_type is None:

From 2a180776687fbc3969cada6fc070a91e5046c9e5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 05:26:03 +0000
Subject: [PATCH 080/227] codegen metadata

---
 .stats.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.stats.yml b/.stats.yml
index 7982133f..d526653e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1,3 @@
 configured_endpoints: 37
+openapi_spec_hash: c2648fabb12e6096d5f7a3ab78300d66
+config_hash: 0529917e0e61d8cd6366481b77eff77e

From 429c38b75d1e952ce93bd48c53966989df2245db Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 21:28:44 +0000
Subject: [PATCH 081/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index d526653e..893f8fab 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 37
-openapi_spec_hash: c2648fabb12e6096d5f7a3ab78300d66
+openapi_spec_hash: 6993cffe14e021af26bf142f5b557263
 config_hash: 0529917e0e61d8cd6366481b77eff77e

From 7c346a8bfb0b804796e36ba9d3e252510dae92e2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 2 Apr 2025 21:11:49 +0000
Subject: [PATCH 082/227] feat(api): updates from question grouping (#93)

---
 .stats.yml                                    |   6 +-
 README.md                                     |  28 +-
 api.md                                        |  20 +-
 src/codex/pagination.py                       |  62 ++++
 src/codex/resources/health.py                 |  50 ---
 src/codex/resources/projects/__init__.py      |  14 +
 src/codex/resources/projects/clusters.py      | 296 +++++++++++++++++
 src/codex/resources/projects/entries.py       | 299 +-----------------
 src/codex/resources/projects/projects.py      |  32 ++
 src/codex/types/projects/__init__.py          |   6 +-
 ..._list_params.py => cluster_list_params.py} |  10 +-
 .../types/projects/cluster_list_response.py   |  36 +++
 .../cluster_list_variants_response.py         |  14 +
 src/codex/types/projects/entry.py             |   8 +-
 .../projects/entry_add_question_params.py     |  21 --
 .../types/projects/entry_query_response.py    |  23 ++
 .../types/projects/entry_update_params.py     |   2 -
 tests/api_resources/projects/test_clusters.py | 241 ++++++++++++++
 tests/api_resources/projects/test_entries.py  | 257 +--------------
 tests/api_resources/test_health.py            |  56 ----
 20 files changed, 785 insertions(+), 696 deletions(-)
 create mode 100644 src/codex/resources/projects/clusters.py
 rename src/codex/types/projects/{entry_list_params.py => cluster_list_params.py} (66%)
 create mode 100644 src/codex/types/projects/cluster_list_response.py
 create mode 100644 src/codex/types/projects/cluster_list_variants_response.py
 delete mode 100644 src/codex/types/projects/entry_add_question_params.py
 create mode 100644 src/codex/types/projects/entry_query_response.py
 create mode 100644 tests/api_resources/projects/test_clusters.py

diff --git a/.stats.yml b/.stats.yml
index 893f8fab..d2d3f6a5 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 37
-openapi_spec_hash: 6993cffe14e021af26bf142f5b557263
-config_hash: 0529917e0e61d8cd6366481b77eff77e
+configured_endpoints: 36
+openapi_spec_hash: 4e7cb2cd6132c29f60a87a958f617a41
+config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/README.md b/README.md
index 95452e72..73c8c169 100644
--- a/README.md
+++ b/README.md
@@ -87,14 +87,14 @@ from codex import Codex
 
 client = Codex()
 
-all_entries = []
+all_clusters = []
 # Automatically fetches more pages as needed.
-for entry in client.projects.entries.list(
+for cluster in client.projects.clusters.list(
     project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 ):
-    # Do something with entry here
-    all_entries.append(entry)
-print(all_entries)
+    # Do something with cluster here
+    all_clusters.append(cluster)
+print(all_clusters)
 ```
 
 Or, asynchronously:
@@ -107,13 +107,13 @@ client = AsyncCodex()
 
 
 async def main() -> None:
-    all_entries = []
+    all_clusters = []
     # Iterate through items across all pages, issuing requests as needed.
-    async for entry in client.projects.entries.list(
+    async for cluster in client.projects.clusters.list(
         project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
     ):
-        all_entries.append(entry)
-    print(all_entries)
+        all_clusters.append(cluster)
+    print(all_clusters)
 
 
 asyncio.run(main())
@@ -122,13 +122,13 @@ asyncio.run(main())
 Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
 
 ```python
-first_page = await client.projects.entries.list(
+first_page = await client.projects.clusters.list(
     project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )
 if first_page.has_next_page():
     print(f"will fetch next page using these details: {first_page.next_page_info()}")
     next_page = await first_page.get_next_page()
-    print(f"number of items we just fetched: {len(next_page.entries)}")
+    print(f"number of items we just fetched: {len(next_page.clusters)}")
 
 # Remove `await` for non-async usage.
 ```
@@ -136,11 +136,11 @@ if first_page.has_next_page():
 Or just work directly with the returned data:
 
 ```python
-first_page = await client.projects.entries.list(
+first_page = await client.projects.clusters.list(
     project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )
-for entry in first_page.entries:
-    print(entry.id)
+for cluster in first_page.clusters:
+    print(cluster.id)
 
 # Remove `await` for non-async usage.
 ```
diff --git a/api.md b/api.md
index 5e258334..b241c15c 100644
--- a/api.md
+++ b/api.md
@@ -10,7 +10,6 @@ Methods:
 
 - <code title="get /api/health/">client.health.<a href="./src/codex/resources/health.py">check</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
 - <code title="get /api/health/db">client.health.<a href="./src/codex/resources/health.py">db</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
-- <code title="get /api/health/weaviate">client.health.<a href="./src/codex/resources/health.py">weaviate</a>() -> <a href="./src/codex/types/health_check_response.py">HealthCheckResponse</a></code>
 
 # Organizations
 
@@ -175,7 +174,7 @@ Methods:
 Types:
 
 ```python
-from codex.types.projects import Entry
+from codex.types.projects import Entry, EntryQueryResponse
 ```
 
 Methods:
@@ -183,10 +182,21 @@ Methods:
 - <code title="post /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/entry_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="get /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="put /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="get /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/entry_list_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">SyncOffsetPageEntries[Entry]</a></code>
 - <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
-- <code title="post /api/projects/{project_id}/entries/add_question">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">add_question</a>(project_id, \*\*<a href="src/codex/types/projects/entry_add_question_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Optional[Entry]</a></code>
+- <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_query_response.py">EntryQueryResponse</a></code>
+
+## Clusters
+
+Types:
+
+```python
+from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse
+```
+
+Methods:
+
+- <code title="get /api/projects/{project_id}/entries/clusters">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/cluster_list_params.py">params</a>) -> <a href="./src/codex/types/projects/cluster_list_response.py">SyncOffsetPageClusters[ClusterListResponse]</a></code>
+- <code title="get /api/projects/{project_id}/entries/clusters/{representative_entry_id}">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list_variants</a>(representative_entry_id, \*, project_id) -> <a href="./src/codex/types/projects/cluster_list_variants_response.py">ClusterListVariantsResponse</a></code>
 
 # Tlm
 
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 48ed5018..1f590df5 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -12,6 +12,8 @@
 __all__ = [
     "SyncMyOffsetPageTopLevelArray",
     "AsyncMyOffsetPageTopLevelArray",
+    "SyncOffsetPageClusters",
+    "AsyncOffsetPageClusters",
     "SyncOffsetPageEntries",
     "AsyncOffsetPageEntries",
 ]
@@ -83,6 +85,66 @@ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseM
         )
 
 
+class SyncOffsetPageClusters(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    clusters: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        clusters = self.clusters
+        if not clusters:
+            return []
+        return clusters
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class AsyncOffsetPageClusters(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    clusters: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        clusters = self.clusters
+        if not clusters:
+            return []
+        return clusters
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
 class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
     entries: List[_T]
     total_count: Optional[int] = None
diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py
index 6a777c63..d74d23a5 100644
--- a/src/codex/resources/health.py
+++ b/src/codex/resources/health.py
@@ -77,25 +77,6 @@ def db(
             cast_to=HealthCheckResponse,
         )
 
-    def weaviate(
-        self,
-        *,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> HealthCheckResponse:
-        """Check the weaviate connection."""
-        return self._get(
-            "/api/health/weaviate",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=HealthCheckResponse,
-        )
-
 
 class AsyncHealthResource(AsyncAPIResource):
     @cached_property
@@ -155,25 +136,6 @@ async def db(
             cast_to=HealthCheckResponse,
         )
 
-    async def weaviate(
-        self,
-        *,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> HealthCheckResponse:
-        """Check the weaviate connection."""
-        return await self._get(
-            "/api/health/weaviate",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=HealthCheckResponse,
-        )
-
 
 class HealthResourceWithRawResponse:
     def __init__(self, health: HealthResource) -> None:
@@ -185,9 +147,6 @@ def __init__(self, health: HealthResource) -> None:
         self.db = to_raw_response_wrapper(
             health.db,
         )
-        self.weaviate = to_raw_response_wrapper(
-            health.weaviate,
-        )
 
 
 class AsyncHealthResourceWithRawResponse:
@@ -200,9 +159,6 @@ def __init__(self, health: AsyncHealthResource) -> None:
         self.db = async_to_raw_response_wrapper(
             health.db,
         )
-        self.weaviate = async_to_raw_response_wrapper(
-            health.weaviate,
-        )
 
 
 class HealthResourceWithStreamingResponse:
@@ -215,9 +171,6 @@ def __init__(self, health: HealthResource) -> None:
         self.db = to_streamed_response_wrapper(
             health.db,
         )
-        self.weaviate = to_streamed_response_wrapper(
-            health.weaviate,
-        )
 
 
 class AsyncHealthResourceWithStreamingResponse:
@@ -230,6 +183,3 @@ def __init__(self, health: AsyncHealthResource) -> None:
         self.db = async_to_streamed_response_wrapper(
             health.db,
         )
-        self.weaviate = async_to_streamed_response_wrapper(
-            health.weaviate,
-        )
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
index 01599858..2c0595d2 100644
--- a/src/codex/resources/projects/__init__.py
+++ b/src/codex/resources/projects/__init__.py
@@ -8,6 +8,14 @@
     EntriesResourceWithStreamingResponse,
     AsyncEntriesResourceWithStreamingResponse,
 )
+from .clusters import (
+    ClustersResource,
+    AsyncClustersResource,
+    ClustersResourceWithRawResponse,
+    AsyncClustersResourceWithRawResponse,
+    ClustersResourceWithStreamingResponse,
+    AsyncClustersResourceWithStreamingResponse,
+)
 from .projects import (
     ProjectsResource,
     AsyncProjectsResource,
@@ -38,6 +46,12 @@
     "AsyncEntriesResourceWithRawResponse",
     "EntriesResourceWithStreamingResponse",
     "AsyncEntriesResourceWithStreamingResponse",
+    "ClustersResource",
+    "AsyncClustersResource",
+    "ClustersResourceWithRawResponse",
+    "AsyncClustersResourceWithRawResponse",
+    "ClustersResourceWithStreamingResponse",
+    "AsyncClustersResourceWithStreamingResponse",
     "ProjectsResource",
     "AsyncProjectsResource",
     "ProjectsResourceWithRawResponse",
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
new file mode 100644
index 00000000..54b6c4c3
--- /dev/null
+++ b/src/codex/resources/projects/clusters.py
@@ -0,0 +1,296 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.projects import cluster_list_params
+from ...types.projects.cluster_list_response import ClusterListResponse
+from ...types.projects.cluster_list_variants_response import ClusterListVariantsResponse
+
+__all__ = ["ClustersResource", "AsyncClustersResource"]
+
+
+class ClustersResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> ClustersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return ClustersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> ClustersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return ClustersResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        project_id: str,
+        *,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> SyncOffsetPageClusters[ClusterListResponse]:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get_api_list(
+            f"/api/projects/{project_id}/entries/clusters",
+            page=SyncOffsetPageClusters[ClusterListResponse],
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "states": states,
+                    },
+                    cluster_list_params.ClusterListParams,
+                ),
+            ),
+            model=ClusterListResponse,
+        )
+
+    def list_variants(
+        self,
+        representative_entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ClusterListVariantsResponse:
+        """
+        Get Cluster Variants Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not representative_entry_id:
+            raise ValueError(
+                f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}"
+            )
+        return self._get(
+            f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ClusterListVariantsResponse,
+        )
+
+
+class AsyncClustersResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncClustersResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncClustersResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncClustersResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncClustersResourceWithStreamingResponse(self)
+
+    def list(
+        self,
+        project_id: str,
+        *,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> AsyncPaginator[ClusterListResponse, AsyncOffsetPageClusters[ClusterListResponse]]:
+        """
+        List knowledge entries for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get_api_list(
+            f"/api/projects/{project_id}/entries/clusters",
+            page=AsyncOffsetPageClusters[ClusterListResponse],
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "states": states,
+                    },
+                    cluster_list_params.ClusterListParams,
+                ),
+            ),
+            model=ClusterListResponse,
+        )
+
+    async def list_variants(
+        self,
+        representative_entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ClusterListVariantsResponse:
+        """
+        Get Cluster Variants Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not representative_entry_id:
+            raise ValueError(
+                f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}"
+            )
+        return await self._get(
+            f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ClusterListVariantsResponse,
+        )
+
+
+class ClustersResourceWithRawResponse:
+    def __init__(self, clusters: ClustersResource) -> None:
+        self._clusters = clusters
+
+        self.list = to_raw_response_wrapper(
+            clusters.list,
+        )
+        self.list_variants = to_raw_response_wrapper(
+            clusters.list_variants,
+        )
+
+
+class AsyncClustersResourceWithRawResponse:
+    def __init__(self, clusters: AsyncClustersResource) -> None:
+        self._clusters = clusters
+
+        self.list = async_to_raw_response_wrapper(
+            clusters.list,
+        )
+        self.list_variants = async_to_raw_response_wrapper(
+            clusters.list_variants,
+        )
+
+
+class ClustersResourceWithStreamingResponse:
+    def __init__(self, clusters: ClustersResource) -> None:
+        self._clusters = clusters
+
+        self.list = to_streamed_response_wrapper(
+            clusters.list,
+        )
+        self.list_variants = to_streamed_response_wrapper(
+            clusters.list_variants,
+        )
+
+
+class AsyncClustersResourceWithStreamingResponse:
+    def __init__(self, clusters: AsyncClustersResource) -> None:
+        self._clusters = clusters
+
+        self.list = async_to_streamed_response_wrapper(
+            clusters.list,
+        )
+        self.list_variants = async_to_streamed_response_wrapper(
+            clusters.list_variants,
+        )
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index f5eebad6..8be96b06 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -2,8 +2,7 @@
 
 from __future__ import annotations
 
-from typing import List, Optional
-from typing_extensions import Literal
+from typing import Optional
 
 import httpx
 
@@ -21,16 +20,10 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ...pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries
-from ..._base_client import AsyncPaginator, make_request_options
-from ...types.projects import (
-    entry_list_params,
-    entry_query_params,
-    entry_create_params,
-    entry_update_params,
-    entry_add_question_params,
-)
+from ..._base_client import make_request_options
+from ...types.projects import entry_query_params, entry_create_params, entry_update_params
 from ...types.projects.entry import Entry
+from ...types.projects.entry_query_response import EntryQueryResponse
 
 __all__ = ["EntriesResource", "AsyncEntriesResource"]
 
@@ -74,9 +67,7 @@ def create(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> Entry:
         """
-        Create a knowledge entry for a project.
-
-        Raises: HTTPException: If an existing entry is found with the same question.
+        Create a new knowledge entry for a project.
 
         Args:
           extra_headers: Send extra headers
@@ -159,7 +150,6 @@ def update(
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -190,7 +180,6 @@ def update(
                 {
                     "answer": answer,
                     "draft_answer": draft_answer,
-                    "frequency_count": frequency_count,
                     "question": question,
                 },
                 entry_update_params.EntryUpdateParams,
@@ -201,62 +190,6 @@ def update(
             cast_to=Entry,
         )
 
-    def list(
-        self,
-        project_id: str,
-        *,
-        answered_only: bool | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
-        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
-        unanswered_only: bool | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> SyncOffsetPageEntries[Entry]:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get_api_list(
-            f"/api/projects/{project_id}/entries/",
-            page=SyncOffsetPageEntries[Entry],
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform(
-                    {
-                        "answered_only": answered_only,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "states": states,
-                        "unanswered_only": unanswered_only,
-                    },
-                    entry_list_params.EntryListParams,
-                ),
-            ),
-            model=Entry,
-        )
-
     def delete(
         self,
         entry_id: str,
@@ -294,59 +227,6 @@ def delete(
             cast_to=NoneType,
         )
 
-    def add_question(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Add a question to a project.
-
-        Returns: 201 Created if a new question was added 200 OK if the question already
-        existed
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return self._post(
-            f"/api/projects/{project_id}/entries/add_question",
-            body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
     def query(
         self,
         project_id: str,
@@ -362,16 +242,9 @@ def query(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Optional[Entry]:
-        """Query knowledge for a project.
-
-        Also increments the frequency_count for the
-        matching entry if found.
-
-        Returns the matching entry if found and answered, otherwise returns None. This
-        allows the client to distinguish between: (1) no matching question found
-        (returns None), and (2) matching question found but not yet answered (returns
-        Entry with answer=None).
+    ) -> EntryQueryResponse:
+        """
+        Query Entries Route
 
         Args:
           extra_headers: Send extra headers
@@ -401,7 +274,7 @@ def query(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=Entry,
+            cast_to=EntryQueryResponse,
         )
 
 
@@ -444,9 +317,7 @@ async def create(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> Entry:
         """
-        Create a knowledge entry for a project.
-
-        Raises: HTTPException: If an existing entry is found with the same question.
+        Create a new knowledge entry for a project.
 
         Args:
           extra_headers: Send extra headers
@@ -529,7 +400,6 @@ async def update(
         project_id: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        frequency_count: Optional[int] | NotGiven = NOT_GIVEN,
         question: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -560,7 +430,6 @@ async def update(
                 {
                     "answer": answer,
                     "draft_answer": draft_answer,
-                    "frequency_count": frequency_count,
                     "question": question,
                 },
                 entry_update_params.EntryUpdateParams,
@@ -571,62 +440,6 @@ async def update(
             cast_to=Entry,
         )
 
-    def list(
-        self,
-        project_id: str,
-        *,
-        answered_only: bool | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN,
-        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
-        unanswered_only: bool | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> AsyncPaginator[Entry, AsyncOffsetPageEntries[Entry]]:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get_api_list(
-            f"/api/projects/{project_id}/entries/",
-            page=AsyncOffsetPageEntries[Entry],
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform(
-                    {
-                        "answered_only": answered_only,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "states": states,
-                        "unanswered_only": unanswered_only,
-                    },
-                    entry_list_params.EntryListParams,
-                ),
-            ),
-            model=Entry,
-        )
-
     async def delete(
         self,
         entry_id: str,
@@ -664,59 +477,6 @@ async def delete(
             cast_to=NoneType,
         )
 
-    async def add_question(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Add a question to a project.
-
-        Returns: 201 Created if a new question was added 200 OK if the question already
-        existed
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return await self._post(
-            f"/api/projects/{project_id}/entries/add_question",
-            body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
     async def query(
         self,
         project_id: str,
@@ -732,16 +492,9 @@ async def query(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Optional[Entry]:
-        """Query knowledge for a project.
-
-        Also increments the frequency_count for the
-        matching entry if found.
-
-        Returns the matching entry if found and answered, otherwise returns None. This
-        allows the client to distinguish between: (1) no matching question found
-        (returns None), and (2) matching question found but not yet answered (returns
-        Entry with answer=None).
+    ) -> EntryQueryResponse:
+        """
+        Query Entries Route
 
         Args:
           extra_headers: Send extra headers
@@ -771,7 +524,7 @@ async def query(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=Entry,
+            cast_to=EntryQueryResponse,
         )
 
 
@@ -788,15 +541,9 @@ def __init__(self, entries: EntriesResource) -> None:
         self.update = to_raw_response_wrapper(
             entries.update,
         )
-        self.list = to_raw_response_wrapper(
-            entries.list,
-        )
         self.delete = to_raw_response_wrapper(
             entries.delete,
         )
-        self.add_question = to_raw_response_wrapper(
-            entries.add_question,
-        )
         self.query = to_raw_response_wrapper(
             entries.query,
         )
@@ -815,15 +562,9 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.update = async_to_raw_response_wrapper(
             entries.update,
         )
-        self.list = async_to_raw_response_wrapper(
-            entries.list,
-        )
         self.delete = async_to_raw_response_wrapper(
             entries.delete,
         )
-        self.add_question = async_to_raw_response_wrapper(
-            entries.add_question,
-        )
         self.query = async_to_raw_response_wrapper(
             entries.query,
         )
@@ -842,15 +583,9 @@ def __init__(self, entries: EntriesResource) -> None:
         self.update = to_streamed_response_wrapper(
             entries.update,
         )
-        self.list = to_streamed_response_wrapper(
-            entries.list,
-        )
         self.delete = to_streamed_response_wrapper(
             entries.delete,
         )
-        self.add_question = to_streamed_response_wrapper(
-            entries.add_question,
-        )
         self.query = to_streamed_response_wrapper(
             entries.query,
         )
@@ -869,15 +604,9 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.update = async_to_streamed_response_wrapper(
             entries.update,
         )
-        self.list = async_to_streamed_response_wrapper(
-            entries.list,
-        )
         self.delete = async_to_streamed_response_wrapper(
             entries.delete,
         )
-        self.add_question = async_to_streamed_response_wrapper(
-            entries.add_question,
-        )
         self.query = async_to_streamed_response_wrapper(
             entries.query,
         )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 38d7b036..8a7ff1b3 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -21,6 +21,14 @@
     maybe_transform,
     async_maybe_transform,
 )
+from .clusters import (
+    ClustersResource,
+    AsyncClustersResource,
+    ClustersResourceWithRawResponse,
+    AsyncClustersResourceWithRawResponse,
+    ClustersResourceWithStreamingResponse,
+    AsyncClustersResourceWithStreamingResponse,
+)
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (
@@ -53,6 +61,10 @@ def access_keys(self) -> AccessKeysResource:
     def entries(self) -> EntriesResource:
         return EntriesResource(self._client)
 
+    @cached_property
+    def clusters(self) -> ClustersResource:
+        return ClustersResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         """
@@ -321,6 +333,10 @@ def access_keys(self) -> AsyncAccessKeysResource:
     def entries(self) -> AsyncEntriesResource:
         return AsyncEntriesResource(self._client)
 
+    @cached_property
+    def clusters(self) -> AsyncClustersResource:
+        return AsyncClustersResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         """
@@ -611,6 +627,10 @@ def access_keys(self) -> AccessKeysResourceWithRawResponse:
     def entries(self) -> EntriesResourceWithRawResponse:
         return EntriesResourceWithRawResponse(self._projects.entries)
 
+    @cached_property
+    def clusters(self) -> ClustersResourceWithRawResponse:
+        return ClustersResourceWithRawResponse(self._projects.clusters)
+
 
 class AsyncProjectsResourceWithRawResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -643,6 +663,10 @@ def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
     def entries(self) -> AsyncEntriesResourceWithRawResponse:
         return AsyncEntriesResourceWithRawResponse(self._projects.entries)
 
+    @cached_property
+    def clusters(self) -> AsyncClustersResourceWithRawResponse:
+        return AsyncClustersResourceWithRawResponse(self._projects.clusters)
+
 
 class ProjectsResourceWithStreamingResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -675,6 +699,10 @@ def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
     def entries(self) -> EntriesResourceWithStreamingResponse:
         return EntriesResourceWithStreamingResponse(self._projects.entries)
 
+    @cached_property
+    def clusters(self) -> ClustersResourceWithStreamingResponse:
+        return ClustersResourceWithStreamingResponse(self._projects.clusters)
+
 
 class AsyncProjectsResourceWithStreamingResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -706,3 +734,7 @@ def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
     @cached_property
     def entries(self) -> AsyncEntriesResourceWithStreamingResponse:
         return AsyncEntriesResourceWithStreamingResponse(self._projects.entries)
+
+    @cached_property
+    def clusters(self) -> AsyncClustersResourceWithStreamingResponse:
+        return AsyncClustersResourceWithStreamingResponse(self._projects.clusters)
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 44c304f5..2733406c 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -4,14 +4,16 @@
 
 from .entry import Entry as Entry
 from .access_key_schema import AccessKeySchema as AccessKeySchema
-from .entry_list_params import EntryListParams as EntryListParams
 from .entry_query_params import EntryQueryParams as EntryQueryParams
+from .cluster_list_params import ClusterListParams as ClusterListParams
 from .entry_create_params import EntryCreateParams as EntryCreateParams
 from .entry_update_params import EntryUpdateParams as EntryUpdateParams
+from .entry_query_response import EntryQueryResponse as EntryQueryResponse
+from .cluster_list_response import ClusterListResponse as ClusterListResponse
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
-from .entry_add_question_params import EntryAddQuestionParams as EntryAddQuestionParams
+from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse
 from .access_key_retrieve_project_id_response import (
     AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
 )
diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/cluster_list_params.py
similarity index 66%
rename from src/codex/types/projects/entry_list_params.py
rename to src/codex/types/projects/cluster_list_params.py
index 605ea482..438b4812 100644
--- a/src/codex/types/projects/entry_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -5,20 +5,16 @@
 from typing import List
 from typing_extensions import Literal, TypedDict
 
-__all__ = ["EntryListParams"]
+__all__ = ["ClusterListParams"]
 
 
-class EntryListParams(TypedDict, total=False):
-    answered_only: bool
-
+class ClusterListParams(TypedDict, total=False):
     limit: int
 
     offset: int
 
     order: Literal["asc", "desc"]
 
-    sort: Literal["created_at", "answered_at"]
+    sort: Literal["created_at", "answered_at", "cluster_frequency_count"]
 
     states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
-
-    unanswered_only: bool
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
new file mode 100644
index 00000000..7b4ef3ad
--- /dev/null
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ClusterListResponse"]
+
+
+class ClusterListResponse(BaseModel):
+    id: str
+
+    cluster_frequency_count: int
+
+    created_at: datetime
+
+    project_id: str
+
+    question: str
+
+    state: Literal["unanswered", "draft", "published", "published_with_draft"]
+
+    answer: Optional[str] = None
+
+    answered_at: Optional[datetime] = None
+
+    draft_answer: Optional[str] = None
+
+    draft_answer_last_edited: Optional[datetime] = None
+
+    frequency_count: Optional[int] = None
+    """number of times the entry matched for a /query request"""
+
+    representative_entry_id: Optional[str] = None
diff --git a/src/codex/types/projects/cluster_list_variants_response.py b/src/codex/types/projects/cluster_list_variants_response.py
new file mode 100644
index 00000000..aa359058
--- /dev/null
+++ b/src/codex/types/projects/cluster_list_variants_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .entry import Entry
+from ..._models import BaseModel
+
+__all__ = ["ClusterListVariantsResponse"]
+
+
+class ClusterListVariantsResponse(BaseModel):
+    entries: List[Entry]
+
+    total_count: int
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 7a2c4502..442eddd8 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -10,14 +10,16 @@
 
 
 class Entry(BaseModel):
+    id: str
+
     created_at: datetime
 
+    project_id: str
+
     question: str
 
     state: Literal["unanswered", "draft", "published", "published_with_draft"]
 
-    id: Optional[str] = None
-
     answer: Optional[str] = None
 
     answered_at: Optional[datetime] = None
@@ -25,5 +27,3 @@ class Entry(BaseModel):
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
-
-    frequency_count: Optional[int] = None
diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py
deleted file mode 100644
index b310f96b..00000000
--- a/src/codex/types/projects/entry_add_question_params.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, Annotated, TypedDict
-
-from ..._utils import PropertyInfo
-
-__all__ = ["EntryAddQuestionParams"]
-
-
-class EntryAddQuestionParams(TypedDict, total=False):
-    question: Required[str]
-
-    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
-
-    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
-
-    x_source: Annotated[str, PropertyInfo(alias="x-source")]
-
-    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
new file mode 100644
index 00000000..ad106d7a
--- /dev/null
+++ b/src/codex/types/projects/entry_query_response.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["EntryQueryResponse", "Entry"]
+
+
+class Entry(BaseModel):
+    id: str
+
+    question: str
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
+
+
+class EntryQueryResponse(BaseModel):
+    entry: Entry
+
+    answer: Optional[str] = None
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
index 05a6332d..aac256f9 100644
--- a/src/codex/types/projects/entry_update_params.py
+++ b/src/codex/types/projects/entry_update_params.py
@@ -15,6 +15,4 @@ class EntryUpdateParams(TypedDict, total=False):
 
     draft_answer: Optional[str]
 
-    frequency_count: Optional[int]
-
     question: Optional[str]
diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py
new file mode 100644
index 00000000..5a464970
--- /dev/null
+++ b/tests/api_resources/projects/test_clusters.py
@@ -0,0 +1,241 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex.pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters
+from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestClusters:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        cluster = client.projects.clusters.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        cluster = client.projects.clusters.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            states=["unanswered"],
+        )
+        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.clusters.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        cluster = response.parse()
+        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.clusters.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            cluster = response.parse()
+            assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.clusters.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_variants(self, client: Codex) -> None:
+        cluster = client.projects.clusters.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_variants(self, client: Codex) -> None:
+        response = client.projects.clusters.with_raw_response.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        cluster = response.parse()
+        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_variants(self, client: Codex) -> None:
+        with client.projects.clusters.with_streaming_response.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            cluster = response.parse()
+            assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list_variants(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.clusters.with_raw_response.list_variants(
+                representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(
+            ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''"
+        ):
+            client.projects.clusters.with_raw_response.list_variants(
+                representative_entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+
+class TestAsyncClusters:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        cluster = await async_client.projects.clusters.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        cluster = await async_client.projects.clusters.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            states=["unanswered"],
+        )
+        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.clusters.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        cluster = await response.parse()
+        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.clusters.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            cluster = await response.parse()
+            assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.clusters.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_variants(self, async_client: AsyncCodex) -> None:
+        cluster = await async_client.projects.clusters.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_variants(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.clusters.with_raw_response.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        cluster = await response.parse()
+        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_variants(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.clusters.with_streaming_response.list_variants(
+            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            cluster = await response.parse()
+            assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list_variants(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.clusters.with_raw_response.list_variants(
+                representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(
+            ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''"
+        ):
+            await async_client.projects.clusters.with_raw_response.list_variants(
+                representative_entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 8a18dc01..e8965e4a 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -3,16 +3,13 @@
 from __future__ import annotations
 
 import os
-from typing import Any, Optional, cast
+from typing import Any, cast
 
 import pytest
 
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
-from codex.pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries
-from codex.types.projects import (
-    Entry,
-)
+from codex.types.projects import Entry, EntryQueryResponse
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -150,7 +147,6 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
             draft_answer="draft_answer",
-            frequency_count=0,
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -198,63 +194,6 @@ def test_path_params_update(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list(self, client: Codex) -> None:
-        entry = client.projects.entries.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            answered_only=True,
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            states=["unanswered"],
-            unanswered_only=True,
-        )
-        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_list(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.list(
-                project_id="",
-            )
-
     @pytest.mark.skip()
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
@@ -307,65 +246,6 @@ def test_path_params_delete(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_add_question(self, client: Codex) -> None:
-        entry = client.projects.entries.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_add_question_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_add_question(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_add_question(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_add_question(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.add_question(
-                project_id="",
-                question="question",
-            )
-
     @pytest.mark.skip()
     @parametrize
     def test_method_query(self, client: Codex) -> None:
@@ -373,7 +253,7 @@ def test_method_query(self, client: Codex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -386,7 +266,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
             x_source="x-source",
             x_stainless_package_version="x-stainless-package-version",
         )
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -399,7 +279,7 @@ def test_raw_response_query(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         entry = response.parse()
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -412,7 +292,7 @@ def test_streaming_response_query(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             entry = response.parse()
-            assert_matches_type(Optional[Entry], entry, path=["response"])
+            assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -559,7 +439,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             answer="answer",
             draft_answer="draft_answer",
-            frequency_count=0,
             question="question",
         )
         assert_matches_type(Entry, entry, path=["response"])
@@ -607,63 +486,6 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            answered_only=True,
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            states=["unanswered"],
-            unanswered_only=True,
-        )
-        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.list(
-                project_id="",
-            )
-
     @pytest.mark.skip()
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
@@ -716,65 +538,6 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_add_question(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_add_question_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.add_question(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_add_question(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.add_question(
-                project_id="",
-                question="question",
-            )
-
     @pytest.mark.skip()
     @parametrize
     async def test_method_query(self, async_client: AsyncCodex) -> None:
@@ -782,7 +545,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
         )
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -795,7 +558,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
             x_source="x-source",
             x_stainless_package_version="x-stainless-package-version",
         )
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -808,7 +571,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         entry = await response.parse()
-        assert_matches_type(Optional[Entry], entry, path=["response"])
+        assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -821,7 +584,7 @@ async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             entry = await response.parse()
-            assert_matches_type(Optional[Entry], entry, path=["response"])
+            assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index 09700120..ed942cd2 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -73,34 +73,6 @@ def test_streaming_response_db(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_weaviate(self, client: Codex) -> None:
-        health = client.health.weaviate()
-        assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_weaviate(self, client: Codex) -> None:
-        response = client.health.with_raw_response.weaviate()
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        health = response.parse()
-        assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_weaviate(self, client: Codex) -> None:
-        with client.health.with_streaming_response.weaviate() as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            health = response.parse()
-            assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
 
 class TestAsyncHealth:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -160,31 +132,3 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
             assert_matches_type(HealthCheckResponse, health, path=["response"])
 
         assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_weaviate(self, async_client: AsyncCodex) -> None:
-        health = await async_client.health.weaviate()
-        assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None:
-        response = await async_client.health.with_raw_response.weaviate()
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        health = await response.parse()
-        assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None:
-        async with async_client.health.with_streaming_response.weaviate() as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            health = await response.parse()
-            assert_matches_type(HealthCheckResponse, health, path=["response"])
-
-        assert cast(Any, response.is_closed) is True

From 7e259409666e8642c3e97cd8bede90b9749b9dae Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 2 Apr 2025 21:16:36 +0000
Subject: [PATCH 083/227] chore(internal): version bump (#95)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index fd0ccba9..000572ec 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.12"
+  ".": "0.1.0-alpha.13"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index ff7153aa..a4cf5307 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.12"
+version = "0.1.0-alpha.13"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index fe7cc739..8790f4ea 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.12"  # x-release-please-version
+__version__ = "0.1.0-alpha.13"  # x-release-please-version

From 208dfc4ebc22ec9d687928d12298144e2d2d30ff Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 2 Apr 2025 21:29:04 +0000
Subject: [PATCH 084/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index d2d3f6a5..86d74db8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 4e7cb2cd6132c29f60a87a958f617a41
+openapi_spec_hash: d84beeafebbb22261946b77758a506a6
 config_hash: adbedb6317fca6f566f54564cc341846

From 6035794a69860eec4301d8640e6e4d98b9cda6c3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 3 Apr 2025 18:20:03 +0000
Subject: [PATCH 085/227] feat(api): api update (#98)

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/entries.py       | 24 ++++++++++++++++---
 .../types/projects/cluster_list_response.py   |  4 +++-
 src/codex/types/projects/entry.py             |  4 +++-
 .../types/projects/entry_create_params.py     |  4 +++-
 .../types/projects/entry_query_params.py      |  3 +++
 .../types/projects/entry_query_response.py    |  4 +++-
 tests/api_resources/projects/test_entries.py  |  4 ++++
 8 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 86d74db8..c22765ce 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: d84beeafebbb22261946b77758a506a6
+openapi_spec_hash: 6a5cfa54c19b354978b1654c152b0431
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 8be96b06..7df9f1f6 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import Optional
+from typing import Iterable, Optional
 
 import httpx
 
@@ -54,6 +54,7 @@ def create(
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -97,6 +98,7 @@ def create(
                 {
                     "question": question,
                     "answer": answer,
+                    "client_query_metadata": client_query_metadata,
                     "draft_answer": draft_answer,
                 },
                 entry_create_params.EntryCreateParams,
@@ -232,6 +234,7 @@ def query(
         project_id: str,
         *,
         question: str,
+        client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -270,7 +273,13 @@ def query(
         }
         return self._post(
             f"/api/projects/{project_id}/entries/query",
-            body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
+            body=maybe_transform(
+                {
+                    "question": question,
+                    "client_metadata": client_metadata,
+                },
+                entry_query_params.EntryQueryParams,
+            ),
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
@@ -304,6 +313,7 @@ async def create(
         *,
         question: str,
         answer: Optional[str] | NotGiven = NOT_GIVEN,
+        client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -347,6 +357,7 @@ async def create(
                 {
                     "question": question,
                     "answer": answer,
+                    "client_query_metadata": client_query_metadata,
                     "draft_answer": draft_answer,
                 },
                 entry_create_params.EntryCreateParams,
@@ -482,6 +493,7 @@ async def query(
         project_id: str,
         *,
         question: str,
+        client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -520,7 +532,13 @@ async def query(
         }
         return await self._post(
             f"/api/projects/{project_id}/entries/query",
-            body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams),
+            body=await async_maybe_transform(
+                {
+                    "question": question,
+                    "client_metadata": client_metadata,
+                },
+                entry_query_params.EntryQueryParams,
+            ),
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index 7b4ef3ad..d13a5f98 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -1,6 +1,6 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Optional
+from typing import List, Optional
 from datetime import datetime
 from typing_extensions import Literal
 
@@ -26,6 +26,8 @@ class ClusterListResponse(BaseModel):
 
     answered_at: Optional[datetime] = None
 
+    client_query_metadata: Optional[List[object]] = None
+
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 442eddd8..77e2ca33 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -1,6 +1,6 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Optional
+from typing import List, Optional
 from datetime import datetime
 from typing_extensions import Literal
 
@@ -24,6 +24,8 @@ class Entry(BaseModel):
 
     answered_at: Optional[datetime] = None
 
+    client_query_metadata: Optional[List[object]] = None
+
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
index 0022942f..f06846bb 100644
--- a/src/codex/types/projects/entry_create_params.py
+++ b/src/codex/types/projects/entry_create_params.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import Optional
+from typing import Iterable, Optional
 from typing_extensions import Required, Annotated, TypedDict
 
 from ..._utils import PropertyInfo
@@ -15,6 +15,8 @@ class EntryCreateParams(TypedDict, total=False):
 
     answer: Optional[str]
 
+    client_query_metadata: Iterable[object]
+
     draft_answer: Optional[str]
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index bc0e317c..50b5f268 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+from typing import Optional
 from typing_extensions import Required, Annotated, TypedDict
 
 from ..._utils import PropertyInfo
@@ -12,6 +13,8 @@
 class EntryQueryParams(TypedDict, total=False):
     question: Required[str]
 
+    client_metadata: Optional[object]
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
index ad106d7a..766e1e24 100644
--- a/src/codex/types/projects/entry_query_response.py
+++ b/src/codex/types/projects/entry_query_response.py
@@ -1,6 +1,6 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Optional
+from typing import List, Optional
 
 from ..._models import BaseModel
 
@@ -14,6 +14,8 @@ class Entry(BaseModel):
 
     answer: Optional[str] = None
 
+    client_query_metadata: Optional[List[object]] = None
+
     draft_answer: Optional[str] = None
 
 
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index e8965e4a..5fa5ed9a 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -33,6 +33,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
+            client_query_metadata=[{}],
             draft_answer="draft_answer",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -261,6 +262,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
         entry = client.projects.entries.query(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
+            client_metadata={},
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -325,6 +327,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
             answer="answer",
+            client_query_metadata=[{}],
             draft_answer="draft_answer",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -553,6 +556,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
         entry = await async_client.projects.entries.query(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
+            client_metadata={},
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",

From 6dc925a18d9efec16d0a922af510584053c212d0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 3 Apr 2025 18:29:53 +0000
Subject: [PATCH 086/227] chore(internal): version bump (#99)

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 000572ec..b0699969 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.13"
+  ".": "0.1.0-alpha.14"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index a4cf5307..ff15445f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.13"
+version = "0.1.0-alpha.14"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 8790f4ea..15097538 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.13"  # x-release-please-version
+__version__ = "0.1.0-alpha.14"  # x-release-please-version

From b7c547ea3d057ac646ce9527a39e605327dd2bcb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 17:29:48 +0000
Subject: [PATCH 087/227] feat(api): api update (#102)

---
 .stats.yml                                      | 2 +-
 src/codex/resources/projects/clusters.py        | 6 +++---
 src/codex/types/projects/cluster_list_params.py | 4 ++--
 src/codex/types/projects/entry.py               | 3 +++
 4 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index c22765ce..be1ee82d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 6a5cfa54c19b354978b1654c152b0431
+openapi_spec_hash: 1850a850b7e27992c6e47190db9add88
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 54b6c4c3..2faed31e 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import List
+from typing import List, Optional
 from typing_extensions import Literal
 
 import httpx
@@ -53,7 +53,7 @@ def list(
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -164,7 +164,7 @@ def list(
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index 438b4812..48893245 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import List
+from typing import List, Optional
 from typing_extensions import Literal, TypedDict
 
 __all__ = ["ClusterListParams"]
@@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False):
 
     order: Literal["asc", "desc"]
 
-    sort: Literal["created_at", "answered_at", "cluster_frequency_count"]
+    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]]
 
     states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 77e2ca33..b8eeb0ec 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -29,3 +29,6 @@ class Entry(BaseModel):
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
+
+    frequency_count: Optional[int] = None
+    """number of times the entry matched for a /query request"""

From c360da6924a128cbd98438252d1c1ad3922f9f6d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 19:58:07 +0000
Subject: [PATCH 088/227] chore(internal): remove trailing character (#103)

---
 tests/test_client.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index 0b0b783b..2d356fac 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1577,7 +1577,7 @@ def test_get_platform(self) -> None:
         import threading
 
         from codex._utils import asyncify
-        from codex._base_client import get_platform 
+        from codex._base_client import get_platform
 
         async def test_main() -> None:
             result = await asyncify(get_platform)()

From 4abd0f4ccf3461a47b4dfeece23f2c8e7fcdd5e6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 23:28:59 +0000
Subject: [PATCH 089/227] feat(api): api update (#104)

---
 .stats.yml                                    |  2 +-
 README.md                                     | 10 ++++-
 src/codex/resources/projects/entries.py       | 16 +++++++-
 src/codex/types/project_create_params.py      | 12 ++++++
 src/codex/types/project_list_response.py      | 12 ++++++
 src/codex/types/project_return_schema.py      | 12 ++++++
 src/codex/types/project_update_params.py      | 12 ++++++
 .../types/projects/entry_query_params.py      |  2 +
 tests/api_resources/projects/test_entries.py  |  2 +
 tests/api_resources/test_projects.py          | 40 +++++++++++++++++--
 10 files changed, 112 insertions(+), 8 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index be1ee82d..69bb6fce 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 1850a850b7e27992c6e47190db9add88
+openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/README.md b/README.md
index 73c8c169..c2f168fe 100644
--- a/README.md
+++ b/README.md
@@ -155,7 +155,15 @@ from codex import Codex
 client = Codex()
 
 project_return_schema = client.projects.create(
-    config={"max_distance": 0},
+    config={
+        "clustering_use_llm_matching": True,
+        "llm_matching_model": "llm_matching_model",
+        "llm_matching_quality_preset": "llm_matching_quality_preset",
+        "lower_llm_match_distance_threshold": 0,
+        "max_distance": 0,
+        "query_use_llm_matching": True,
+        "upper_llm_match_distance_threshold": 0,
+    },
     name="name",
     organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 7df9f1f6..f0fa39cb 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -234,6 +234,7 @@ def query(
         project_id: str,
         *,
         question: str,
+        use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -281,7 +282,11 @@ def query(
                 entry_query_params.EntryQueryParams,
             ),
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform({"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams),
             ),
             cast_to=EntryQueryResponse,
         )
@@ -493,6 +498,7 @@ async def query(
         project_id: str,
         *,
         question: str,
+        use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -540,7 +546,13 @@ async def query(
                 entry_query_params.EntryQueryParams,
             ),
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams
+                ),
             ),
             cast_to=EntryQueryResponse,
         )
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 80882b21..ecdd194d 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -19,4 +19,16 @@ class ProjectCreateParams(TypedDict, total=False):
 
 
 class Config(TypedDict, total=False):
+    clustering_use_llm_matching: bool
+
+    llm_matching_model: str
+
+    llm_matching_quality_preset: str
+
+    lower_llm_match_distance_threshold: float
+
     max_distance: float
+
+    query_use_llm_matching: bool
+
+    upper_llm_match_distance_threshold: float
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 84aafa98..2b4fec42 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -9,8 +9,20 @@
 
 
 class ProjectConfig(BaseModel):
+    clustering_use_llm_matching: Optional[bool] = None
+
+    llm_matching_model: Optional[str] = None
+
+    llm_matching_quality_preset: Optional[str] = None
+
+    lower_llm_match_distance_threshold: Optional[float] = None
+
     max_distance: Optional[float] = None
 
+    query_use_llm_matching: Optional[bool] = None
+
+    upper_llm_match_distance_threshold: Optional[float] = None
+
 
 class Project(BaseModel):
     id: str
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 85312729..51a6c1ad 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -9,8 +9,20 @@
 
 
 class Config(BaseModel):
+    clustering_use_llm_matching: Optional[bool] = None
+
+    llm_matching_model: Optional[str] = None
+
+    llm_matching_quality_preset: Optional[str] = None
+
+    lower_llm_match_distance_threshold: Optional[float] = None
+
     max_distance: Optional[float] = None
 
+    query_use_llm_matching: Optional[bool] = None
+
+    upper_llm_match_distance_threshold: Optional[float] = None
+
 
 class ProjectReturnSchema(BaseModel):
     id: str
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 46d747ee..0a5aa540 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -17,4 +17,16 @@ class ProjectUpdateParams(TypedDict, total=False):
 
 
 class Config(TypedDict, total=False):
+    clustering_use_llm_matching: bool
+
+    llm_matching_model: str
+
+    llm_matching_quality_preset: str
+
+    lower_llm_match_distance_threshold: float
+
     max_distance: float
+
+    query_use_llm_matching: bool
+
+    upper_llm_match_distance_threshold: float
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index 50b5f268..d58b7bfa 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -13,6 +13,8 @@
 class EntryQueryParams(TypedDict, total=False):
     question: Required[str]
 
+    use_llm_matching: bool
+
     client_metadata: Optional[object]
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 5fa5ed9a..ca7eecbb 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -262,6 +262,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
         entry = client.projects.entries.query(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
+            use_llm_matching=True,
             client_metadata={},
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -556,6 +557,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
         entry = await async_client.projects.entries.query(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             question="question",
+            use_llm_matching=True,
             client_metadata={},
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 10d23461..210f4e17 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -34,7 +34,15 @@ def test_method_create(self, client: Codex) -> None:
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         project = client.projects.create(
-            config={"max_distance": 0},
+            config={
+                "clustering_use_llm_matching": True,
+                "llm_matching_model": "llm_matching_model",
+                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "lower_llm_match_distance_threshold": 0,
+                "max_distance": 0,
+                "query_use_llm_matching": True,
+                "upper_llm_match_distance_threshold": 0,
+            },
             name="name",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             description="description",
@@ -128,7 +136,15 @@ def test_method_update(self, client: Codex) -> None:
     def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={"max_distance": 0},
+            config={
+                "clustering_use_llm_matching": True,
+                "llm_matching_model": "llm_matching_model",
+                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "lower_llm_match_distance_threshold": 0,
+                "max_distance": 0,
+                "query_use_llm_matching": True,
+                "upper_llm_match_distance_threshold": 0,
+            },
             name="name",
             description="description",
         )
@@ -324,7 +340,15 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
-            config={"max_distance": 0},
+            config={
+                "clustering_use_llm_matching": True,
+                "llm_matching_model": "llm_matching_model",
+                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "lower_llm_match_distance_threshold": 0,
+                "max_distance": 0,
+                "query_use_llm_matching": True,
+                "upper_llm_match_distance_threshold": 0,
+            },
             name="name",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             description="description",
@@ -418,7 +442,15 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={"max_distance": 0},
+            config={
+                "clustering_use_llm_matching": True,
+                "llm_matching_model": "llm_matching_model",
+                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "lower_llm_match_distance_threshold": 0,
+                "max_distance": 0,
+                "query_use_llm_matching": True,
+                "upper_llm_match_distance_threshold": 0,
+            },
             name="name",
             description="description",
         )

From ae147ae779f3c8034a8bc030d96f5bfeaac779a1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 8 Apr 2025 04:40:49 +0000
Subject: [PATCH 090/227] fix(client): send all configured auth headers (#106)

---
 src/codex/_client.py | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index 9bdd5f02..2641513f 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -154,11 +154,7 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        if self._authenticated_api_key:
-            return self._authenticated_api_key
-        if self._public_access_key:
-            return self._public_access_key
-        return {}
+        return {**self._authenticated_api_key, **self._public_access_key}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
@@ -386,11 +382,7 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        if self._authenticated_api_key:
-            return self._authenticated_api_key
-        if self._public_access_key:
-            return self._public_access_key
-        return {}
+        return {**self._authenticated_api_key, **self._public_access_key}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:

From a91d91e349e3a7a538c9925e20df104f2fba4bb9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 8 Apr 2025 19:28:30 +0000
Subject: [PATCH 091/227] feat(api): api update (#107)

---
 .stats.yml                                    |  2 +-
 api.md                                        |  9 +++-
 src/codex/resources/projects/clusters.py      |  6 ++-
 src/codex/resources/projects/projects.py      |  9 ++--
 src/codex/types/__init__.py                   |  1 +
 src/codex/types/project_retrieve_response.py  | 44 +++++++++++++++++++
 .../types/projects/cluster_list_params.py     |  2 +-
 tests/api_resources/test_projects.py          | 13 +++---
 8 files changed, 70 insertions(+), 16 deletions(-)
 create mode 100644 src/codex/types/project_retrieve_response.py

diff --git a/.stats.yml b/.stats.yml
index 69bb6fce..4500dc4b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b
+openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/api.md b/api.md
index b241c15c..e9dd565d 100644
--- a/api.md
+++ b/api.md
@@ -135,13 +135,18 @@ Methods:
 Types:
 
 ```python
-from codex.types import ProjectReturnSchema, ProjectListResponse, ProjectExportResponse
+from codex.types import (
+    ProjectReturnSchema,
+    ProjectRetrieveResponse,
+    ProjectListResponse,
+    ProjectExportResponse,
+)
 ```
 
 Methods:
 
 - <code title="post /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">create</a>(\*\*<a href="src/codex/types/project_create_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
-- <code title="get /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve</a>(project_id) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="get /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve</a>(project_id) -> <a href="./src/codex/types/project_retrieve_response.py">ProjectRetrieveResponse</a></code>
 - <code title="put /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">update</a>(project_id, \*\*<a href="src/codex/types/project_update_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
 - <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 2faed31e..f376c0b1 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -53,7 +53,8 @@ def list(
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+        | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -164,7 +165,8 @@ def list(
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+        | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 8a7ff1b3..ccc77268 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -48,6 +48,7 @@
 from ..._base_client import make_request_options
 from ...types.project_list_response import ProjectListResponse
 from ...types.project_return_schema import ProjectReturnSchema
+from ...types.project_retrieve_response import ProjectRetrieveResponse
 
 __all__ = ["ProjectsResource", "AsyncProjectsResource"]
 
@@ -137,7 +138,7 @@ def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ProjectReturnSchema:
+    ) -> ProjectRetrieveResponse:
         """
         Get a single project.
 
@@ -157,7 +158,7 @@ def retrieve(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=ProjectReturnSchema,
+            cast_to=ProjectRetrieveResponse,
         )
 
     def update(
@@ -409,7 +410,7 @@ async def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ProjectReturnSchema:
+    ) -> ProjectRetrieveResponse:
         """
         Get a single project.
 
@@ -429,7 +430,7 @@ async def retrieve(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=ProjectReturnSchema,
+            cast_to=ProjectRetrieveResponse,
         )
 
     async def update(
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 8f241bc9..6c184372 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -13,5 +13,6 @@
 from .project_list_response import ProjectListResponse as ProjectListResponse
 from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
 from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
new file mode 100644
index 00000000..62209d32
--- /dev/null
+++ b/src/codex/types/project_retrieve_response.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ProjectRetrieveResponse", "Config"]
+
+
+class Config(BaseModel):
+    clustering_use_llm_matching: Optional[bool] = None
+
+    llm_matching_model: Optional[str] = None
+
+    llm_matching_quality_preset: Optional[str] = None
+
+    lower_llm_match_distance_threshold: Optional[float] = None
+
+    max_distance: Optional[float] = None
+
+    query_use_llm_matching: Optional[bool] = None
+
+    upper_llm_match_distance_threshold: Optional[float] = None
+
+
+class ProjectRetrieveResponse(BaseModel):
+    id: str
+
+    config: Config
+
+    created_at: datetime
+
+    created_by_user_id: str
+
+    name: str
+
+    organization_id: str
+
+    updated_at: datetime
+
+    custom_rank_enabled: Optional[bool] = None
+
+    description: Optional[str] = None
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index 48893245..b272d806 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False):
 
     order: Literal["asc", "desc"]
 
-    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]]
+    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
 
     states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 210f4e17..1e2fccbc 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -11,6 +11,7 @@
 from codex.types import (
     ProjectListResponse,
     ProjectReturnSchema,
+    ProjectRetrieveResponse,
 )
 from tests.utils import assert_matches_type
 
@@ -85,7 +86,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         project = client.projects.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -97,7 +98,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         project = response.parse()
-        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -109,7 +110,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             project = response.parse()
-            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+            assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -391,7 +392,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve(
             "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -403,7 +404,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         project = await response.parse()
-        assert_matches_type(ProjectReturnSchema, project, path=["response"])
+        assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -415,7 +416,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             project = await response.parse()
-            assert_matches_type(ProjectReturnSchema, project, path=["response"])
+            assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 

From 7819dc64ffdb333da33ac6f80211d33476b203d4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 9 Apr 2025 03:40:16 +0000
Subject: [PATCH 092/227] chore(internal): slight transform perf improvement
 (#108)

---
 src/codex/_utils/_transform.py | 22 ++++++++++++++++++++++
 tests/test_transform.py        | 12 ++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 7ac2e17f..3ec62081 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -142,6 +142,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
     return key
 
 
+def _no_transform_needed(annotation: type) -> bool:
+    return annotation == float or annotation == int
+
+
 def _transform_recursive(
     data: object,
     *,
@@ -184,6 +188,15 @@ def _transform_recursive(
             return cast(object, data)
 
         inner_type = extract_type_arg(stripped_type, 0)
+        if _no_transform_needed(inner_type):
+            # for some types there is no need to transform anything, so we can get a small
+            # perf boost from skipping that work.
+            #
+            # but we still need to convert to a list to ensure the data is json-serializable
+            if is_list(data):
+                return data
+            return list(data)
+
         return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
 
     if is_union_type(stripped_type):
@@ -332,6 +345,15 @@ async def _async_transform_recursive(
             return cast(object, data)
 
         inner_type = extract_type_arg(stripped_type, 0)
+        if _no_transform_needed(inner_type):
+            # for some types there is no need to transform anything, so we can get a small
+            # perf boost from skipping that work.
+            #
+            # but we still need to convert to a list to ensure the data is json-serializable
+            if is_list(data):
+                return data
+            return list(data)
+
         return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
 
     if is_union_type(stripped_type):
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 324f31a7..b1ea479f 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -432,3 +432,15 @@ async def test_base64_file_input(use_async: bool) -> None:
     assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
         "foo": "SGVsbG8sIHdvcmxkIQ=="
     }  # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+    # lists of ints are left as-is
+    data = [1, 2, 3]
+    assert await transform(data, List[int], use_async) is data
+
+    # iterables of ints are converted to a list
+    data = iter([1, 2, 3])
+    assert await transform(data, Iterable[int], use_async) == [1, 2, 3]

From 4fd8f44f8e2d94af3f3a9688724ee2c22e272c7b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 02:29:52 +0000
Subject: [PATCH 093/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 .../types/projects/cluster_list_response.py   | 110 ++++++++++++++++-
 src/codex/types/projects/entry.py             | 110 ++++++++++++++++-
 .../types/projects/entry_query_response.py    | 111 +++++++++++++++++-
 4 files changed, 329 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 4500dc4b..ab9fbe2f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394
+openapi_spec_hash: 2e10455457751c9efb36adc4399c684d
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index d13a5f98..33bc18a7 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -6,7 +6,112 @@
 
 from ..._models import BaseModel
 
-__all__ = ["ClusterListResponse"]
+__all__ = [
+    "ClusterListResponse",
+    "ManagedMetadata",
+    "ManagedMetadataContextSufficiency",
+    "ManagedMetadataQueryEaseCustomized",
+    "ManagedMetadataResponseHelpfulness",
+    "ManagedMetadataTrustworthiness",
+]
+
+
+class ManagedMetadataContextSufficiency(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataQueryEaseCustomized(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataResponseHelpfulness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataTrustworthiness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadata(BaseModel):
+    latest_context: Optional[str] = None
+    """The most recent context string."""
+
+    latest_entry_point: Optional[str] = None
+    """The most recent entry point string."""
+
+    latest_llm_response: Optional[str] = None
+    """The most recent LLM response string."""
+
+    latest_location: Optional[str] = None
+    """The most recent location string."""
+
+    context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    contexts: Optional[List[str]] = None
+
+    entry_points: Optional[List[str]] = None
+
+    llm_responses: Optional[List[str]] = None
+
+    locations: Optional[List[str]] = None
+
+    query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
+    """Holds a list of scores and computes aggregate statistics."""
 
 
 class ClusterListResponse(BaseModel):
@@ -16,6 +121,9 @@ class ClusterListResponse(BaseModel):
 
     created_at: datetime
 
+    managed_metadata: ManagedMetadata
+    """Extract system-defined, managed metadata from client_query_metadata."""
+
     project_id: str
 
     question: str
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index b8eeb0ec..24d57c89 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -6,7 +6,112 @@
 
 from ..._models import BaseModel
 
-__all__ = ["Entry"]
+__all__ = [
+    "Entry",
+    "ManagedMetadata",
+    "ManagedMetadataContextSufficiency",
+    "ManagedMetadataQueryEaseCustomized",
+    "ManagedMetadataResponseHelpfulness",
+    "ManagedMetadataTrustworthiness",
+]
+
+
+class ManagedMetadataContextSufficiency(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataQueryEaseCustomized(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataResponseHelpfulness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadataTrustworthiness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class ManagedMetadata(BaseModel):
+    latest_context: Optional[str] = None
+    """The most recent context string."""
+
+    latest_entry_point: Optional[str] = None
+    """The most recent entry point string."""
+
+    latest_llm_response: Optional[str] = None
+    """The most recent LLM response string."""
+
+    latest_location: Optional[str] = None
+    """The most recent location string."""
+
+    context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    contexts: Optional[List[str]] = None
+
+    entry_points: Optional[List[str]] = None
+
+    llm_responses: Optional[List[str]] = None
+
+    locations: Optional[List[str]] = None
+
+    query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
+    """Holds a list of scores and computes aggregate statistics."""
 
 
 class Entry(BaseModel):
@@ -14,6 +119,9 @@ class Entry(BaseModel):
 
     created_at: datetime
 
+    managed_metadata: ManagedMetadata
+    """Extract system-defined, managed metadata from client_query_metadata."""
+
     project_id: str
 
     question: str
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
index 766e1e24..26db1d83 100644
--- a/src/codex/types/projects/entry_query_response.py
+++ b/src/codex/types/projects/entry_query_response.py
@@ -4,12 +4,121 @@
 
 from ..._models import BaseModel
 
-__all__ = ["EntryQueryResponse", "Entry"]
+__all__ = [
+    "EntryQueryResponse",
+    "Entry",
+    "EntryManagedMetadata",
+    "EntryManagedMetadataContextSufficiency",
+    "EntryManagedMetadataQueryEaseCustomized",
+    "EntryManagedMetadataResponseHelpfulness",
+    "EntryManagedMetadataTrustworthiness",
+]
+
+
+class EntryManagedMetadataContextSufficiency(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataQueryEaseCustomized(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataResponseHelpfulness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataTrustworthiness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadata(BaseModel):
+    latest_context: Optional[str] = None
+    """The most recent context string."""
+
+    latest_entry_point: Optional[str] = None
+    """The most recent entry point string."""
+
+    latest_llm_response: Optional[str] = None
+    """The most recent LLM response string."""
+
+    latest_location: Optional[str] = None
+    """The most recent location string."""
+
+    context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    contexts: Optional[List[str]] = None
+
+    entry_points: Optional[List[str]] = None
+
+    llm_responses: Optional[List[str]] = None
+
+    locations: Optional[List[str]] = None
+
+    query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
+    trustworthiness: Optional[EntryManagedMetadataTrustworthiness] = None
+    """Holds a list of scores and computes aggregate statistics."""
 
 
 class Entry(BaseModel):
     id: str
 
+    managed_metadata: EntryManagedMetadata
+    """Extract system-defined, managed metadata from client_query_metadata."""
+
     question: str
 
     answer: Optional[str] = None

From 48b70386c62b026a912dd3d9358ed0eeb02a94ed Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 04:04:24 +0000
Subject: [PATCH 094/227] chore(internal): expand CI branch coverage

---
 .github/workflows/ci.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5ac5f63f..b8920208 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,18 @@
 name: CI
 on:
   push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-      - next
+    branches-ignore:
+      - 'generated'
+      - 'codegen/**'
+      - 'integrated/**'
+      - 'preview-head/**'
+      - 'preview-base/**'
+      - 'preview/**'
 
 jobs:
   lint:
     name: lint
     runs-on: ubuntu-latest
-
     steps:
       - uses: actions/checkout@v4
 

From 8bdffb4656784378d6a8d9ba7c5503e08788332c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 04:08:38 +0000
Subject: [PATCH 095/227] chore(internal): reduce CI branch coverage

---
 .github/workflows/ci.yml | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b8920208..e8b72361 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,13 +1,12 @@
 name: CI
 on:
   push:
-    branches-ignore:
-      - 'generated'
-      - 'codegen/**'
-      - 'integrated/**'
-      - 'preview-head/**'
-      - 'preview-base/**'
-      - 'preview/**'
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+      - next
 
 jobs:
   lint:

From 3b32bc4fc8eca38d0e462a91050aa209b6fad3c9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:09:29 +0000
Subject: [PATCH 096/227] fix(perf): skip traversing types for NotGiven values

---
 src/codex/_utils/_transform.py | 11 +++++++++++
 tests/test_transform.py        |  9 ++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 3ec62081..3b2b8e00 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -12,6 +12,7 @@
 
 from ._utils import (
     is_list,
+    is_given,
     is_mapping,
     is_iterable,
 )
@@ -258,6 +259,11 @@ def _transform_typeddict(
     result: dict[str, object] = {}
     annotations = get_type_hints(expected_type, include_extras=True)
     for key, value in data.items():
+        if not is_given(value):
+            # we don't need to include `NotGiven` values here as they'll
+            # be stripped out before the request is sent anyway
+            continue
+
         type_ = annotations.get(key)
         if type_ is None:
             # we do not have a type annotation for this field, leave it as is
@@ -415,6 +421,11 @@ async def _async_transform_typeddict(
     result: dict[str, object] = {}
     annotations = get_type_hints(expected_type, include_extras=True)
     for key, value in data.items():
+        if not is_given(value):
+            # we don't need to include `NotGiven` values here as they'll
+            # be stripped out before the request is sent anyway
+            continue
+
         type_ = annotations.get(key)
         if type_ is None:
             # we do not have a type annotation for this field, leave it as is
diff --git a/tests/test_transform.py b/tests/test_transform.py
index b1ea479f..527845a0 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -8,7 +8,7 @@
 
 import pytest
 
-from codex._types import Base64FileInput
+from codex._types import NOT_GIVEN, Base64FileInput
 from codex._utils import (
     PropertyInfo,
     transform as _transform,
@@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None:
     # iterables of ints are converted to a list
     data = iter([1, 2, 3])
     assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_notgiven(use_async: bool) -> None:
+    assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+    assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}

From 026df181e6103e6c1cd8581973c4a9958f86b5e3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:11:14 +0000
Subject: [PATCH 097/227] fix(perf): optimize some hot paths

---
 src/codex/_utils/_transform.py | 14 +++++++++++++-
 src/codex/_utils/_typing.py    |  2 ++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 3b2b8e00..b0cc20a7 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -5,7 +5,7 @@
 import pathlib
 from typing import Any, Mapping, TypeVar, cast
 from datetime import date, datetime
-from typing_extensions import Literal, get_args, override, get_type_hints
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
 
 import anyio
 import pydantic
@@ -13,6 +13,7 @@
 from ._utils import (
     is_list,
     is_given,
+    lru_cache,
     is_mapping,
     is_iterable,
 )
@@ -109,6 +110,7 @@ class Params(TypedDict, total=False):
     return cast(_T, transformed)
 
 
+@lru_cache(maxsize=8096)
 def _get_annotated_type(type_: type) -> type | None:
     """If the given type is an `Annotated` type then it is returned, if not `None` is returned.
 
@@ -433,3 +435,13 @@ async def _async_transform_typeddict(
         else:
             result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
     return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+    obj: Any,
+    globalns: dict[str, Any] | None = None,
+    localns: Mapping[str, Any] | None = None,
+    include_extras: bool = False,
+) -> dict[str, Any]:
+    return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py
index 278749b1..1958820f 100644
--- a/src/codex/_utils/_typing.py
+++ b/src/codex/_utils/_typing.py
@@ -13,6 +13,7 @@
     get_origin,
 )
 
+from ._utils import lru_cache
 from .._types import InheritsGeneric
 from .._compat import is_union as _is_union
 
@@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
 
 
 # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
 def strip_annotated_type(typ: type) -> type:
     if is_required_type(typ) or is_annotated_type(typ):
         return strip_annotated_type(cast(type, get_args(typ)[0]))

From 9d57264e11ccb23719e513e3f598079dcb273364 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:11:58 +0000
Subject: [PATCH 098/227] chore(internal): update pyright settings

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index ff15445f..e4307cda 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -147,6 +147,7 @@ exclude = [
 ]
 
 reportImplicitOverride = true
+reportOverlappingOverload = false
 
 reportImportCycles = false
 reportPrivateUsage = false

From 6e05ce67d32ed65184238e3acaa2e792a6ea1feb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:12:59 +0000
Subject: [PATCH 099/227] chore(client): minor internal fixes

---
 src/codex/_base_client.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 273341b1..564ab8a7 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -409,7 +409,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
 
         idempotency_header = self._idempotency_header
         if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
-            headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
+            options.idempotency_key = options.idempotency_key or self._idempotency_key()
+            headers[idempotency_header] = options.idempotency_key
 
         # Don't set these headers if they were already set or removed by the caller. We check
         # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
@@ -943,6 +944,10 @@ def _request(
         request = self._build_request(options, retries_taken=retries_taken)
         self._prepare_request(request)
 
+        if options.idempotency_key:
+            # ensure the idempotency key is reused between requests
+            input_options.idempotency_key = options.idempotency_key
+
         kwargs: HttpxSendArgs = {}
         if self.custom_auth is not None:
             kwargs["auth"] = self.custom_auth
@@ -1475,6 +1480,10 @@ async def _request(
         request = self._build_request(options, retries_taken=retries_taken)
         await self._prepare_request(request)
 
+        if options.idempotency_key:
+            # ensure the idempotency key is reused between requests
+            input_options.idempotency_key = options.idempotency_key
+
         kwargs: HttpxSendArgs = {}
         if self.custom_auth is not None:
             kwargs["auth"] = self.custom_auth

From a8b0e062c53925ff56a197915fd690e5129d8c3d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 23:36:45 +0000
Subject: [PATCH 100/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b0699969..08e82c45 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.14"
+  ".": "0.1.0-alpha.15"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index e4307cda..6d0ec848 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.14"
+version = "0.1.0-alpha.15"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 15097538..d4919cf4 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.14"  # x-release-please-version
+__version__ = "0.1.0-alpha.15"  # x-release-please-version

From 17b36437163075abc0dcdcc4e5269ac1cc11c089 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 16 Apr 2025 20:16:07 +0000
Subject: [PATCH 101/227] feat(api): api update

---
 .stats.yml                                        | 2 +-
 src/codex/resources/projects/clusters.py          | 8 ++++++--
 src/codex/types/projects/cluster_list_params.py   | 4 +++-
 src/codex/types/projects/cluster_list_response.py | 4 ++++
 src/codex/types/projects/entry.py                 | 4 ++++
 tests/api_resources/projects/test_clusters.py     | 2 ++
 6 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index ab9fbe2f..c1c522e8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 2e10455457751c9efb36adc4399c684d
+openapi_spec_hash: 6311021a3aba7ac56cc3b474762945c0
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index f376c0b1..e35e8ae8 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -50,10 +50,11 @@ def list(
         self,
         project_id: str,
         *,
+        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
         | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -87,6 +88,7 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
+                        "eval_issue_types": eval_issue_types,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
@@ -162,10 +164,11 @@ def list(
         self,
         project_id: str,
         *,
+        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
         | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -199,6 +202,7 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
+                        "eval_issue_types": eval_issue_types,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index b272d806..d1d818e3 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -9,12 +9,14 @@
 
 
 class ClusterListParams(TypedDict, total=False):
+    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]]
+
     limit: int
 
     offset: int
 
     order: Literal["asc", "desc"]
 
-    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
 
     states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index 33bc18a7..c149fbdb 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -140,6 +140,10 @@ class ClusterListResponse(BaseModel):
 
     draft_answer_last_edited: Optional[datetime] = None
 
+    eval_issue_type: Optional[str] = None
+
+    eval_score: Optional[float] = None
+
     frequency_count: Optional[int] = None
     """number of times the entry matched for a /query request"""
 
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 24d57c89..638fe352 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -138,5 +138,9 @@ class Entry(BaseModel):
 
     draft_answer_last_edited: Optional[datetime] = None
 
+    eval_issue_type: Optional[str] = None
+
+    eval_score: Optional[float] = None
+
     frequency_count: Optional[int] = None
     """number of times the entry matched for a /query request"""
diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py
index 5a464970..53706c6c 100644
--- a/tests/api_resources/projects/test_clusters.py
+++ b/tests/api_resources/projects/test_clusters.py
@@ -31,6 +31,7 @@ def test_method_list(self, client: Codex) -> None:
     def test_method_list_with_all_params(self, client: Codex) -> None:
         cluster = client.projects.clusters.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            eval_issue_types=["hallucination"],
             limit=1,
             offset=0,
             order="asc",
@@ -144,6 +145,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         cluster = await async_client.projects.clusters.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            eval_issue_types=["hallucination"],
             limit=1,
             offset=0,
             order="asc",

From a488e268e99c6d724536625ec4b383ccff44dbca Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 17 Apr 2025 02:12:31 +0000
Subject: [PATCH 102/227] chore(internal): bump pyright version

---
 pyproject.toml              | 2 +-
 requirements-dev.lock       | 2 +-
 src/codex/_base_client.py   | 6 +++++-
 src/codex/_models.py        | 1 -
 src/codex/_utils/_typing.py | 2 +-
 tests/conftest.py           | 2 +-
 tests/test_models.py        | 2 +-
 7 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6d0ec848..cf867da1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,7 +42,7 @@ Repository = "https://github.com/cleanlab/codex-python"
 managed = true
 # version pins are in requirements-dev.lock
 dev-dependencies = [
-    "pyright>=1.1.359",
+    "pyright==1.1.399",
     "mypy",
     "respx",
     "pytest",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 5b7189d6..7fcfe657 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -69,7 +69,7 @@ pydantic-core==2.27.1
     # via pydantic
 pygments==2.18.0
     # via rich
-pyright==1.1.392.post0
+pyright==1.1.399
 pytest==8.3.3
     # via pytest-asyncio
 pytest-asyncio==0.24.0
diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 564ab8a7..41366043 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -98,7 +98,11 @@
 _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
 
 if TYPE_CHECKING:
-    from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+    from httpx._config import (
+        DEFAULT_TIMEOUT_CONFIG,  # pyright: ignore[reportPrivateImportUsage]
+    )
+
+    HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
 else:
     try:
         from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
diff --git a/src/codex/_models.py b/src/codex/_models.py
index 34935716..58b9263e 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -19,7 +19,6 @@
 )
 
 import pydantic
-import pydantic.generics
 from pydantic.fields import FieldInfo
 
 from ._types import (
diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py
index 1958820f..1bac9542 100644
--- a/src/codex/_utils/_typing.py
+++ b/src/codex/_utils/_typing.py
@@ -110,7 +110,7 @@ class MyResponse(Foo[_T]):
     ```
     """
     cls = cast(object, get_origin(typ) or typ)
-    if cls in generic_bases:
+    if cls in generic_bases:  # pyright: ignore[reportUnnecessaryContains]
         # we're given the class directly
         return extract_type_arg(typ, index)
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 8823ef7a..c93053b1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,7 +10,7 @@
 from codex import Codex, AsyncCodex
 
 if TYPE_CHECKING:
-    from _pytest.fixtures import FixtureRequest
+    from _pytest.fixtures import FixtureRequest  # pyright: ignore[reportPrivateImportUsage]
 
 pytest.register_assert_rewrite("tests.utils")
 
diff --git a/tests/test_models.py b/tests/test_models.py
index 73e6d67c..fb9d99b1 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -832,7 +832,7 @@ class B(BaseModel):
 
 @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
 def test_type_alias_type() -> None:
-    Alias = TypeAliasType("Alias", str)
+    Alias = TypeAliasType("Alias", str)  # pyright: ignore
 
     class Model(BaseModel):
         alias: Alias

From 1c475ba19f678a7e4c6feee9c6e3b6122bfed1ae Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 17 Apr 2025 02:13:02 +0000
Subject: [PATCH 103/227] chore(internal): base client updates

---
 src/codex/_base_client.py | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 41366043..69be76db 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -119,6 +119,7 @@ class PageInfo:
 
     url: URL | NotGiven
     params: Query | NotGiven
+    json: Body | NotGiven
 
     @overload
     def __init__(
@@ -134,19 +135,30 @@ def __init__(
         params: Query,
     ) -> None: ...
 
+    @overload
+    def __init__(
+        self,
+        *,
+        json: Body,
+    ) -> None: ...
+
     def __init__(
         self,
         *,
         url: URL | NotGiven = NOT_GIVEN,
+        json: Body | NotGiven = NOT_GIVEN,
         params: Query | NotGiven = NOT_GIVEN,
     ) -> None:
         self.url = url
+        self.json = json
         self.params = params
 
     @override
     def __repr__(self) -> str:
         if self.url:
             return f"{self.__class__.__name__}(url={self.url})"
+        if self.json:
+            return f"{self.__class__.__name__}(json={self.json})"
         return f"{self.__class__.__name__}(params={self.params})"
 
 
@@ -195,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
             options.url = str(url)
             return options
 
+        if not isinstance(info.json, NotGiven):
+            if not is_mapping(info.json):
+                raise TypeError("Pagination is only supported with mappings")
+
+            if not options.json_data:
+                options.json_data = {**info.json}
+            else:
+                if not is_mapping(options.json_data):
+                    raise TypeError("Pagination is only supported with mappings")
+
+                options.json_data = {**options.json_data, **info.json}
+            return options
+
         raise ValueError("Unexpected PageInfo state")
 
 

From 3d06a2b7e271dfd663b082aa6b8d04f5b9e7bf52 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 18 Apr 2025 22:21:35 +0000
Subject: [PATCH 104/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 08e82c45..7e56fe29 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.15"
+  ".": "0.1.0-alpha.16"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index cf867da1..3b48a5bb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.15"
+version = "0.1.0-alpha.16"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index d4919cf4..b99310c3 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.15"  # x-release-please-version
+__version__ = "0.1.0-alpha.16"  # x-release-please-version

From 0bce1e1cd33071e999df7580af81073e2d4235fe Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 19 Apr 2025 02:14:16 +0000
Subject: [PATCH 105/227] chore(internal): update models test

---
 tests/test_models.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/test_models.py b/tests/test_models.py
index fb9d99b1..c96609ce 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -492,12 +492,15 @@ class Model(BaseModel):
         resource_id: Optional[str] = None
 
     m = Model.construct()
+    assert m.resource_id is None
     assert "resource_id" not in m.model_fields_set
 
     m = Model.construct(resource_id=None)
+    assert m.resource_id is None
     assert "resource_id" in m.model_fields_set
 
     m = Model.construct(resource_id="foo")
+    assert m.resource_id == "foo"
     assert "resource_id" in m.model_fields_set
 
 

From eb7b90ed7dfd25c5ab3a7a42c3d3917cd3118c6c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sun, 20 Apr 2025 23:16:04 +0000
Subject: [PATCH 106/227] feat(api): api update

---
 .stats.yml                                      | 2 +-
 src/codex/resources/projects/clusters.py        | 6 ++++--
 src/codex/types/projects/cluster_list_params.py | 2 +-
 3 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index c1c522e8..f033bbda 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 36
-openapi_spec_hash: 6311021a3aba7ac56cc3b474762945c0
+openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
 config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index e35e8ae8..584cde18 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -50,7 +50,8 @@ def list(
         self,
         project_id: str,
         *,
-        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN,
+        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
+        | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
@@ -164,7 +165,8 @@ def list(
         self,
         project_id: str,
         *,
-        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN,
+        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
+        | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index d1d818e3..fa84a6b0 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -9,7 +9,7 @@
 
 
 class ClusterListParams(TypedDict, total=False):
-    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]]
+    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
 
     limit: int
 

From 1f1f5accb70873ff984b938c7d17be119b23e6c1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 17:41:55 +0000
Subject: [PATCH 107/227] feat(api): add project increment_queries and other
 recent endpoints

---
 .stats.yml                                    |   4 +-
 api.md                                        |  15 +-
 .../resources/organizations/organizations.py  | 158 ++++++++
 src/codex/resources/projects/entries.py       | 289 +++++++++++++-
 src/codex/resources/projects/projects.py      |  78 ++++
 src/codex/types/__init__.py                   |   4 +
 .../organization_list_members_response.py     |  17 +
 ...anization_retrieve_permissions_response.py |   8 +
 src/codex/types/projects/__init__.py          |   2 +
 .../types/projects/entry_notify_sme_params.py |  21 +
 .../projects/entry_notify_sme_response.py     |  14 +
 tests/api_resources/projects/test_entries.py  | 366 +++++++++++++++++-
 tests/api_resources/test_organizations.py     | 174 ++++++++-
 tests/api_resources/test_projects.py          |  84 ++++
 14 files changed, 1227 insertions(+), 7 deletions(-)
 create mode 100644 src/codex/types/organization_list_members_response.py
 create mode 100644 src/codex/types/organization_retrieve_permissions_response.py
 create mode 100644 src/codex/types/projects/entry_notify_sme_params.py
 create mode 100644 src/codex/types/projects/entry_notify_sme_response.py

diff --git a/.stats.yml b/.stats.yml
index f033bbda..d78252d8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 36
+configured_endpoints: 42
 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
-config_hash: adbedb6317fca6f566f54564cc341846
+config_hash: 93ac12138700569dc57329400410c0fd
diff --git a/api.md b/api.md
index e9dd565d..5a314a2d 100644
--- a/api.md
+++ b/api.md
@@ -16,12 +16,18 @@ Methods:
 Types:
 
 ```python
-from codex.types import OrganizationSchemaPublic
+from codex.types import (
+    OrganizationSchemaPublic,
+    OrganizationListMembersResponse,
+    OrganizationRetrievePermissionsResponse,
+)
 ```
 
 Methods:
 
 - <code title="get /api/organizations/{organization_id}">client.organizations.<a href="./src/codex/resources/organizations/organizations.py">retrieve</a>(organization_id) -> <a href="./src/codex/types/organization_schema_public.py">OrganizationSchemaPublic</a></code>
+- <code title="get /api/organizations/{organization_id}/members">client.organizations.<a href="./src/codex/resources/organizations/organizations.py">list_members</a>(organization_id) -> <a href="./src/codex/types/organization_list_members_response.py">OrganizationListMembersResponse</a></code>
+- <code title="get /api/organizations/{organization_id}/permissions">client.organizations.<a href="./src/codex/resources/organizations/organizations.py">retrieve_permissions</a>(organization_id) -> <a href="./src/codex/types/organization_retrieve_permissions_response.py">OrganizationRetrievePermissionsResponse</a></code>
 
 ## Billing
 
@@ -140,6 +146,7 @@ from codex.types import (
     ProjectRetrieveResponse,
     ProjectListResponse,
     ProjectExportResponse,
+    ProjectIncrementQueriesResponse,
 )
 ```
 
@@ -151,6 +158,7 @@ Methods:
 - <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
+- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
 
 ## AccessKeys
 
@@ -179,7 +187,7 @@ Methods:
 Types:
 
 ```python
-from codex.types.projects import Entry, EntryQueryResponse
+from codex.types.projects import Entry, EntryNotifySmeResponse, EntryQueryResponse
 ```
 
 Methods:
@@ -188,7 +196,10 @@ Methods:
 - <code title="get /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="put /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
+- <code title="post /api/projects/{project_id}/entries/{entry_id}/notifications">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">notify_sme</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_notify_sme_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_notify_sme_response.py">EntryNotifySmeResponse</a></code>
+- <code title="put /api/projects/{project_id}/entries/{entry_id}/publish_draft_answer">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">publish_draft_answer</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 - <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_query_response.py">EntryQueryResponse</a></code>
+- <code title="put /api/projects/{project_id}/entries/{entry_id}/unpublish_answer">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">unpublish_answer</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
 
 ## Clusters
 
diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py
index 025ecba4..f1eb4d5e 100644
--- a/src/codex/resources/organizations/organizations.py
+++ b/src/codex/resources/organizations/organizations.py
@@ -23,6 +23,8 @@
     AsyncBillingResourceWithStreamingResponse,
 )
 from ...types.organization_schema_public import OrganizationSchemaPublic
+from ...types.organization_list_members_response import OrganizationListMembersResponse
+from ...types.organization_retrieve_permissions_response import OrganizationRetrievePermissionsResponse
 
 __all__ = ["OrganizationsResource", "AsyncOrganizationsResource"]
 
@@ -84,6 +86,72 @@ def retrieve(
             cast_to=OrganizationSchemaPublic,
         )
 
+    def list_members(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationListMembersResponse:
+        """
+        Get a list of organization members with their names and emails.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/members",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationListMembersResponse,
+        )
+
+    def retrieve_permissions(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationRetrievePermissionsResponse:
+        """
+        Get the user's permissions for this organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return self._get(
+            f"/api/organizations/{organization_id}/permissions",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationRetrievePermissionsResponse,
+        )
+
 
 class AsyncOrganizationsResource(AsyncAPIResource):
     @cached_property
@@ -142,6 +210,72 @@ async def retrieve(
             cast_to=OrganizationSchemaPublic,
         )
 
+    async def list_members(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationListMembersResponse:
+        """
+        Get a list of organization members with their names and emails.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/members",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationListMembersResponse,
+        )
+
+    async def retrieve_permissions(
+        self,
+        organization_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> OrganizationRetrievePermissionsResponse:
+        """
+        Get the user's permissions for this organization.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not organization_id:
+            raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}")
+        return await self._get(
+            f"/api/organizations/{organization_id}/permissions",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=OrganizationRetrievePermissionsResponse,
+        )
+
 
 class OrganizationsResourceWithRawResponse:
     def __init__(self, organizations: OrganizationsResource) -> None:
@@ -150,6 +284,12 @@ def __init__(self, organizations: OrganizationsResource) -> None:
         self.retrieve = to_raw_response_wrapper(
             organizations.retrieve,
         )
+        self.list_members = to_raw_response_wrapper(
+            organizations.list_members,
+        )
+        self.retrieve_permissions = to_raw_response_wrapper(
+            organizations.retrieve_permissions,
+        )
 
     @cached_property
     def billing(self) -> BillingResourceWithRawResponse:
@@ -163,6 +303,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None:
         self.retrieve = async_to_raw_response_wrapper(
             organizations.retrieve,
         )
+        self.list_members = async_to_raw_response_wrapper(
+            organizations.list_members,
+        )
+        self.retrieve_permissions = async_to_raw_response_wrapper(
+            organizations.retrieve_permissions,
+        )
 
     @cached_property
     def billing(self) -> AsyncBillingResourceWithRawResponse:
@@ -176,6 +322,12 @@ def __init__(self, organizations: OrganizationsResource) -> None:
         self.retrieve = to_streamed_response_wrapper(
             organizations.retrieve,
         )
+        self.list_members = to_streamed_response_wrapper(
+            organizations.list_members,
+        )
+        self.retrieve_permissions = to_streamed_response_wrapper(
+            organizations.retrieve_permissions,
+        )
 
     @cached_property
     def billing(self) -> BillingResourceWithStreamingResponse:
@@ -189,6 +341,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None:
         self.retrieve = async_to_streamed_response_wrapper(
             organizations.retrieve,
         )
+        self.list_members = async_to_streamed_response_wrapper(
+            organizations.list_members,
+        )
+        self.retrieve_permissions = async_to_streamed_response_wrapper(
+            organizations.retrieve_permissions,
+        )
 
     @cached_property
     def billing(self) -> AsyncBillingResourceWithStreamingResponse:
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index f0fa39cb..f88e2243 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -21,9 +21,10 @@
     async_to_streamed_response_wrapper,
 )
 from ..._base_client import make_request_options
-from ...types.projects import entry_query_params, entry_create_params, entry_update_params
+from ...types.projects import entry_query_params, entry_create_params, entry_update_params, entry_notify_sme_params
 from ...types.projects.entry import Entry
 from ...types.projects.entry_query_response import EntryQueryResponse
+from ...types.projects.entry_notify_sme_response import EntryNotifySmeResponse
 
 __all__ = ["EntriesResource", "AsyncEntriesResource"]
 
@@ -229,6 +230,92 @@ def delete(
             cast_to=NoneType,
         )
 
+    def notify_sme(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        email: str,
+        view_context: entry_notify_sme_params.ViewContext,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EntryNotifySmeResponse:
+        """
+        Notify a subject matter expert to review and answer a specific entry.
+
+        Returns: SMENotificationResponse with status and notification details
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/entries/{entry_id}/notifications",
+            body=maybe_transform(
+                {
+                    "email": email,
+                    "view_context": view_context,
+                },
+                entry_notify_sme_params.EntryNotifySmeParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EntryNotifySmeResponse,
+        )
+
+    def publish_draft_answer(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """Promote a draft answer to a published answer for a knowledge entry.
+
+        This always
+        results in the entry's draft answer being removed. If the entry already has a
+        published answer, it will be overwritten and permanently lost.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
     def query(
         self,
         project_id: str,
@@ -291,6 +378,45 @@ def query(
             cast_to=EntryQueryResponse,
         )
 
+    def unpublish_answer(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """Unpublish an answer for a knowledge entry.
+
+        This always results in the entry's
+        answer being removed. If the entry does not already have a draft answer, the
+        current answer will be retained as the draft answer.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
 
 class AsyncEntriesResource(AsyncAPIResource):
     @cached_property
@@ -493,6 +619,92 @@ async def delete(
             cast_to=NoneType,
         )
 
+    async def notify_sme(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        email: str,
+        view_context: entry_notify_sme_params.ViewContext,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EntryNotifySmeResponse:
+        """
+        Notify a subject matter expert to review and answer a specific entry.
+
+        Returns: SMENotificationResponse with status and notification details
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/entries/{entry_id}/notifications",
+            body=await async_maybe_transform(
+                {
+                    "email": email,
+                    "view_context": view_context,
+                },
+                entry_notify_sme_params.EntryNotifySmeParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EntryNotifySmeResponse,
+        )
+
+    async def publish_draft_answer(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """Promote a draft answer to a published answer for a knowledge entry.
+
+        This always
+        results in the entry's draft answer being removed. If the entry already has a
+        published answer, it will be overwritten and permanently lost.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
     async def query(
         self,
         project_id: str,
@@ -557,6 +769,45 @@ async def query(
             cast_to=EntryQueryResponse,
         )
 
+    async def unpublish_answer(
+        self,
+        entry_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> Entry:
+        """Unpublish an answer for a knowledge entry.
+
+        This always results in the entry's
+        answer being removed. If the entry does not already have a draft answer, the
+        current answer will be retained as the draft answer.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not entry_id:
+            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
+        return await self._put(
+            f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=Entry,
+        )
+
 
 class EntriesResourceWithRawResponse:
     def __init__(self, entries: EntriesResource) -> None:
@@ -574,9 +825,18 @@ def __init__(self, entries: EntriesResource) -> None:
         self.delete = to_raw_response_wrapper(
             entries.delete,
         )
+        self.notify_sme = to_raw_response_wrapper(
+            entries.notify_sme,
+        )
+        self.publish_draft_answer = to_raw_response_wrapper(
+            entries.publish_draft_answer,
+        )
         self.query = to_raw_response_wrapper(
             entries.query,
         )
+        self.unpublish_answer = to_raw_response_wrapper(
+            entries.unpublish_answer,
+        )
 
 
 class AsyncEntriesResourceWithRawResponse:
@@ -595,9 +855,18 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.delete = async_to_raw_response_wrapper(
             entries.delete,
         )
+        self.notify_sme = async_to_raw_response_wrapper(
+            entries.notify_sme,
+        )
+        self.publish_draft_answer = async_to_raw_response_wrapper(
+            entries.publish_draft_answer,
+        )
         self.query = async_to_raw_response_wrapper(
             entries.query,
         )
+        self.unpublish_answer = async_to_raw_response_wrapper(
+            entries.unpublish_answer,
+        )
 
 
 class EntriesResourceWithStreamingResponse:
@@ -616,9 +885,18 @@ def __init__(self, entries: EntriesResource) -> None:
         self.delete = to_streamed_response_wrapper(
             entries.delete,
         )
+        self.notify_sme = to_streamed_response_wrapper(
+            entries.notify_sme,
+        )
+        self.publish_draft_answer = to_streamed_response_wrapper(
+            entries.publish_draft_answer,
+        )
         self.query = to_streamed_response_wrapper(
             entries.query,
         )
+        self.unpublish_answer = to_streamed_response_wrapper(
+            entries.unpublish_answer,
+        )
 
 
 class AsyncEntriesResourceWithStreamingResponse:
@@ -637,6 +915,15 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.delete = async_to_streamed_response_wrapper(
             entries.delete,
         )
+        self.notify_sme = async_to_streamed_response_wrapper(
+            entries.notify_sme,
+        )
+        self.publish_draft_answer = async_to_streamed_response_wrapper(
+            entries.publish_draft_answer,
+        )
         self.query = async_to_streamed_response_wrapper(
             entries.query,
         )
+        self.unpublish_answer = async_to_streamed_response_wrapper(
+            entries.unpublish_answer,
+        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index ccc77268..47639a5a 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -324,6 +324,39 @@ def export(
             cast_to=object,
         )
 
+    def increment_queries(
+        self,
+        project_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Increment the queries metric for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/increment_queries",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
 
 class AsyncProjectsResource(AsyncAPIResource):
     @cached_property
@@ -596,6 +629,39 @@ async def export(
             cast_to=object,
         )
 
+    async def increment_queries(
+        self,
+        project_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> object:
+        """
+        Increment the queries metric for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/increment_queries",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=object,
+        )
+
 
 class ProjectsResourceWithRawResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -619,6 +685,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_raw_response_wrapper(
             projects.export,
         )
+        self.increment_queries = to_raw_response_wrapper(
+            projects.increment_queries,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
@@ -655,6 +724,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_raw_response_wrapper(
             projects.export,
         )
+        self.increment_queries = async_to_raw_response_wrapper(
+            projects.increment_queries,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
@@ -691,6 +763,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_streamed_response_wrapper(
             projects.export,
         )
+        self.increment_queries = to_streamed_response_wrapper(
+            projects.increment_queries,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
@@ -727,6 +802,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_streamed_response_wrapper(
             projects.export,
         )
+        self.increment_queries = async_to_streamed_response_wrapper(
+            projects.increment_queries,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 6c184372..3e65a680 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -16,3 +16,7 @@
 from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
+from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse
+from .organization_retrieve_permissions_response import (
+    OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse,
+)
diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py
new file mode 100644
index 00000000..37897d56
--- /dev/null
+++ b/src/codex/types/organization_list_members_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .._models import BaseModel
+
+__all__ = ["OrganizationListMembersResponse", "OrganizationListMembersResponseItem"]
+
+
+class OrganizationListMembersResponseItem(BaseModel):
+    email: str
+
+    name: str
+
+
+OrganizationListMembersResponse: TypeAlias = List[OrganizationListMembersResponseItem]
diff --git a/src/codex/types/organization_retrieve_permissions_response.py b/src/codex/types/organization_retrieve_permissions_response.py
new file mode 100644
index 00000000..ee7b0671
--- /dev/null
+++ b/src/codex/types/organization_retrieve_permissions_response.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["OrganizationRetrievePermissionsResponse"]
+
+OrganizationRetrievePermissionsResponse: TypeAlias = Dict[str, bool]
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 2733406c..2b69e570 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -10,9 +10,11 @@
 from .entry_update_params import EntryUpdateParams as EntryUpdateParams
 from .entry_query_response import EntryQueryResponse as EntryQueryResponse
 from .cluster_list_response import ClusterListResponse as ClusterListResponse
+from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
+from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse
 from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse
 from .access_key_retrieve_project_id_response import (
     AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py
new file mode 100644
index 00000000..409f8bc5
--- /dev/null
+++ b/src/codex/types/projects/entry_notify_sme_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["EntryNotifySmeParams", "ViewContext"]
+
+
+class EntryNotifySmeParams(TypedDict, total=False):
+    project_id: Required[str]
+
+    email: Required[str]
+
+    view_context: Required[ViewContext]
+
+
+class ViewContext(TypedDict, total=False):
+    page: Required[int]
+
+    filter: Literal["unanswered", "answered", "all", "hallucination", "search_failure", "unhelpful", "difficult_query"]
diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py
new file mode 100644
index 00000000..dd05a6cf
--- /dev/null
+++ b/src/codex/types/projects/entry_notify_sme_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from ..._models import BaseModel
+
+__all__ = ["EntryNotifySmeResponse"]
+
+
+class EntryNotifySmeResponse(BaseModel):
+    entry_id: str
+
+    recipient_email: str
+
+    status: str
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index ca7eecbb..31a5e408 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -9,7 +9,11 @@
 
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
-from codex.types.projects import Entry, EntryQueryResponse
+from codex.types.projects import (
+    Entry,
+    EntryQueryResponse,
+    EntryNotifySmeResponse,
+)
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -247,6 +251,134 @@ def test_path_params_delete(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_notify_sme(self, client: Codex) -> None:
+        entry = client.projects.entries.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        )
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_notify_sme_with_all_params(self, client: Codex) -> None:
+        entry = client.projects.entries.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={
+                "page": 0,
+                "filter": "unanswered",
+            },
+        )
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_notify_sme(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_notify_sme(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_notify_sme(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.notify_sme(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                email="email",
+                view_context={"page": 0},
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.notify_sme(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                email="email",
+                view_context={"page": 0},
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_publish_draft_answer(self, client: Codex) -> None:
+        entry = client.projects.entries.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_publish_draft_answer(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_publish_draft_answer(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_publish_draft_answer(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.publish_draft_answer(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.publish_draft_answer(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_query(self, client: Codex) -> None:
@@ -308,6 +440,58 @@ def test_path_params_query(self, client: Codex) -> None:
                 question="question",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_unpublish_answer(self, client: Codex) -> None:
+        entry = client.projects.entries.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_unpublish_answer(self, client: Codex) -> None:
+        response = client.projects.entries.with_raw_response.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_unpublish_answer(self, client: Codex) -> None:
+        with client.projects.entries.with_streaming_response.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_unpublish_answer(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.entries.with_raw_response.unpublish_answer(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            client.projects.entries.with_raw_response.unpublish_answer(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
 
 class TestAsyncEntries:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -542,6 +726,134 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_notify_sme(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        )
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_notify_sme_with_all_params(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={
+                "page": 0,
+                "filter": "unanswered",
+            },
+        )
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_notify_sme(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_notify_sme(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.notify_sme(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            view_context={"page": 0},
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_notify_sme(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.notify_sme(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                email="email",
+                view_context={"page": 0},
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.notify_sme(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                email="email",
+                view_context={"page": 0},
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_publish_draft_answer(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_publish_draft_answer(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_publish_draft_answer(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.publish_draft_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.publish_draft_answer(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.publish_draft_answer(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_query(self, async_client: AsyncCodex) -> None:
@@ -602,3 +914,55 @@ async def test_path_params_query(self, async_client: AsyncCodex) -> None:
                 project_id="",
                 question="question",
             )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_unpublish_answer(self, async_client: AsyncCodex) -> None:
+        entry = await async_client.projects.entries.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_unpublish_answer(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.entries.with_raw_response.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        entry = await response.parse()
+        assert_matches_type(Entry, entry, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_unpublish_answer(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.entries.with_streaming_response.unpublish_answer(
+            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            entry = await response.parse()
+            assert_matches_type(Entry, entry, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_unpublish_answer(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.unpublish_answer(
+                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
+            await async_client.projects.entries.with_raw_response.unpublish_answer(
+                entry_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index 1a13f77a..c665e85c 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -8,7 +8,11 @@
 import pytest
 
 from codex import Codex, AsyncCodex
-from codex.types import OrganizationSchemaPublic
+from codex.types import (
+    OrganizationSchemaPublic,
+    OrganizationListMembersResponse,
+    OrganizationRetrievePermissionsResponse,
+)
 from tests.utils import assert_matches_type
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -59,6 +63,90 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 "",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_members(self, client: Codex) -> None:
+        organization = client.organizations.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_members(self, client: Codex) -> None:
+        response = client.organizations.with_raw_response.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_members(self, client: Codex) -> None:
+        with client.organizations.with_streaming_response.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list_members(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.with_raw_response.list_members(
+                "",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_permissions(self, client: Codex) -> None:
+        organization = client.organizations.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve_permissions(self, client: Codex) -> None:
+        response = client.organizations.with_raw_response.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = response.parse()
+        assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve_permissions(self, client: Codex) -> None:
+        with client.organizations.with_streaming_response.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = response.parse()
+            assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve_permissions(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            client.organizations.with_raw_response.retrieve_permissions(
+                "",
+            )
+
 
 class TestAsyncOrganizations:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -104,3 +192,87 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
             await async_client.organizations.with_raw_response.retrieve(
                 "",
             )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_members(self, async_client: AsyncCodex) -> None:
+        organization = await async_client.organizations.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.with_raw_response.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_members(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.with_streaming_response.list_members(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list_members(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.with_raw_response.list_members(
+                "",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> None:
+        organization = await async_client.organizations.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) -> None:
+        response = await async_client.organizations.with_raw_response.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        organization = await response.parse()
+        assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve_permissions(self, async_client: AsyncCodex) -> None:
+        async with async_client.organizations.with_streaming_response.retrieve_permissions(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            organization = await response.parse()
+            assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve_permissions(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
+            await async_client.organizations.with_raw_response.retrieve_permissions(
+                "",
+            )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 1e2fccbc..bfa657d2 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -323,6 +323,48 @@ def test_path_params_export(self, client: Codex) -> None:
                 "",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_increment_queries(self, client: Codex) -> None:
+        project = client.projects.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_increment_queries(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_increment_queries(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_increment_queries(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.increment_queries(
+                "",
+            )
+
 
 class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -628,3 +670,45 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None:
             await async_client.projects.with_raw_response.export(
                 "",
             )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_increment_queries(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.increment_queries(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(object, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.increment_queries(
+                "",
+            )

From c5d26f8f913a877c30bc2fcccea86dbb5199f210 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 17:48:01 +0000
Subject: [PATCH 108/227] feat(api): add project increment_queries and other
 recent endpoints

---
 .stats.yml                                     |  2 +-
 api.md                                         | 12 +++---------
 src/codex/resources/users/myself/api_key.py    | 10 +++++-----
 src/codex/resources/users/myself/myself.py     | 10 +++++-----
 src/codex/resources/users/users.py             | 10 +++++-----
 src/codex/types/__init__.py                    |  1 -
 src/codex/types/users/__init__.py              |  1 +
 .../{user.py => users/user_schema_public.py}   |  6 +++---
 tests/api_resources/test_users.py              | 18 +++++++++---------
 .../api_resources/users/myself/test_api_key.py | 15 +++++++--------
 tests/api_resources/users/test_myself.py       | 14 +++++++-------
 11 files changed, 46 insertions(+), 53 deletions(-)
 rename src/codex/types/{user.py => users/user_schema_public.py} (80%)

diff --git a/.stats.yml b/.stats.yml
index d78252d8..aa1535bd 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 42
 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
-config_hash: 93ac12138700569dc57329400410c0fd
+config_hash: 2d88a0a41f5faca603ff2789a116d988
diff --git a/api.md b/api.md
index 5a314a2d..617cdd24 100644
--- a/api.md
+++ b/api.md
@@ -83,15 +83,9 @@ Methods:
 
 # Users
 
-Types:
-
-```python
-from codex.types import User
-```
-
 Methods:
 
-- <code title="patch /api/users/activate_account">client.users.<a href="./src/codex/resources/users/users.py">activate_account</a>(\*\*<a href="src/codex/types/user_activate_account_params.py">params</a>) -> <a href="./src/codex/types/user.py">User</a></code>
+- <code title="patch /api/users/activate_account">client.users.<a href="./src/codex/resources/users/users.py">activate_account</a>(\*\*<a href="src/codex/types/user_activate_account_params.py">params</a>) -> <a href="./src/codex/types/users/user_schema_public.py">UserSchemaPublic</a></code>
 
 ## Myself
 
@@ -103,13 +97,13 @@ from codex.types.users import UserSchema, UserSchemaPublic
 
 Methods:
 
-- <code title="get /api/users/myself">client.users.myself.<a href="./src/codex/resources/users/myself/myself.py">retrieve</a>() -> <a href="./src/codex/types/user.py">User</a></code>
+- <code title="get /api/users/myself">client.users.myself.<a href="./src/codex/resources/users/myself/myself.py">retrieve</a>() -> <a href="./src/codex/types/users/user_schema_public.py">UserSchemaPublic</a></code>
 
 ### APIKey
 
 Methods:
 
-- <code title="get /api/users/myself/api-key">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">retrieve</a>() -> <a href="./src/codex/types/user.py">User</a></code>
+- <code title="get /api/users/myself/api-key">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">retrieve</a>() -> <a href="./src/codex/types/users/user_schema_public.py">UserSchemaPublic</a></code>
 - <code title="post /api/users/myself/api-key/refresh">client.users.myself.api_key.<a href="./src/codex/resources/users/myself/api_key.py">refresh</a>() -> <a href="./src/codex/types/users/user_schema.py">UserSchema</a></code>
 
 ### Organizations
diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py
index 87f647d3..72f1502b 100644
--- a/src/codex/resources/users/myself/api_key.py
+++ b/src/codex/resources/users/myself/api_key.py
@@ -13,9 +13,9 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ....types.user import User
 from ...._base_client import make_request_options
 from ....types.users.user_schema import UserSchema
+from ....types.users.user_schema_public import UserSchemaPublic
 
 __all__ = ["APIKeyResource", "AsyncAPIKeyResource"]
 
@@ -49,14 +49,14 @@ def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """Get user when authenticated with API key."""
         return self._get(
             "/api/users/myself/api-key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
     def refresh(
@@ -108,14 +108,14 @@ async def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """Get user when authenticated with API key."""
         return await self._get(
             "/api/users/myself/api-key",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
     async def refresh(
diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py
index 2cc4868b..3ee27229 100644
--- a/src/codex/resources/users/myself/myself.py
+++ b/src/codex/resources/users/myself/myself.py
@@ -21,7 +21,6 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ....types.user import User
 from .organizations import (
     OrganizationsResource,
     AsyncOrganizationsResource,
@@ -31,6 +30,7 @@
     AsyncOrganizationsResourceWithStreamingResponse,
 )
 from ...._base_client import make_request_options
+from ....types.users.user_schema_public import UserSchemaPublic
 
 __all__ = ["MyselfResource", "AsyncMyselfResource"]
 
@@ -72,14 +72,14 @@ def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """Get user info for frontend."""
         return self._get(
             "/api/users/myself",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
 
@@ -120,14 +120,14 @@ async def retrieve(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """Get user info for frontend."""
         return await self._get(
             "/api/users/myself",
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
 
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index a7d9d2ab..c276f950 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -21,7 +21,6 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ...types.user import User
 from .verification import (
     VerificationResource,
     AsyncVerificationResource,
@@ -39,6 +38,7 @@
     AsyncMyselfResourceWithStreamingResponse,
 )
 from ..._base_client import make_request_options
+from ...types.users.user_schema_public import UserSchemaPublic
 
 __all__ = ["UsersResource", "AsyncUsersResource"]
 
@@ -87,7 +87,7 @@ def activate_account(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """
         Activate an authenticated user's account
 
@@ -117,7 +117,7 @@ def activate_account(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
 
@@ -165,7 +165,7 @@ async def activate_account(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> User:
+    ) -> UserSchemaPublic:
         """
         Activate an authenticated user's account
 
@@ -195,7 +195,7 @@ async def activate_account(
             options=make_request_options(
                 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
             ),
-            cast_to=User,
+            cast_to=UserSchemaPublic,
         )
 
 
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 3e65a680..008c4a67 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-from .user import User as User
 from .tlm_score_params import TlmScoreParams as TlmScoreParams
 from .tlm_prompt_params import TlmPromptParams as TlmPromptParams
 from .tlm_score_response import TlmScoreResponse as TlmScoreResponse
diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py
index 438bc6f3..23dbc910 100644
--- a/src/codex/types/users/__init__.py
+++ b/src/codex/types/users/__init__.py
@@ -3,4 +3,5 @@
 from __future__ import annotations
 
 from .user_schema import UserSchema as UserSchema
+from .user_schema_public import UserSchemaPublic as UserSchemaPublic
 from .verification_resend_response import VerificationResendResponse as VerificationResendResponse
diff --git a/src/codex/types/user.py b/src/codex/types/users/user_schema_public.py
similarity index 80%
rename from src/codex/types/user.py
rename to src/codex/types/users/user_schema_public.py
index 3d7ec233..181113b0 100644
--- a/src/codex/types/user.py
+++ b/src/codex/types/users/user_schema_public.py
@@ -2,12 +2,12 @@
 
 from typing import Optional
 
-from .._models import BaseModel
+from ..._models import BaseModel
 
-__all__ = ["User"]
+__all__ = ["UserSchemaPublic"]
 
 
-class User(BaseModel):
+class UserSchemaPublic(BaseModel):
     id: str
 
     api_key: str
diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py
index 7e78b99e..101d0f66 100644
--- a/tests/api_resources/test_users.py
+++ b/tests/api_resources/test_users.py
@@ -8,9 +8,9 @@
 import pytest
 
 from codex import Codex, AsyncCodex
-from codex.types import User
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
+from codex.types.users import UserSchemaPublic
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -25,7 +25,7 @@ def test_method_activate_account(self, client: Codex) -> None:
             first_name="first_name",
             last_name="last_name",
         )
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -39,7 +39,7 @@ def test_method_activate_account_with_all_params(self, client: Codex) -> None:
             phone_number="phone_number",
             user_provided_company_name="user_provided_company_name",
         )
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -52,7 +52,7 @@ def test_raw_response_activate_account(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         user = response.parse()
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -65,7 +65,7 @@ def test_streaming_response_activate_account(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             user = response.parse()
-            assert_matches_type(User, user, path=["response"])
+            assert_matches_type(UserSchemaPublic, user, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -80,7 +80,7 @@ async def test_method_activate_account(self, async_client: AsyncCodex) -> None:
             first_name="first_name",
             last_name="last_name",
         )
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -94,7 +94,7 @@ async def test_method_activate_account_with_all_params(self, async_client: Async
             phone_number="phone_number",
             user_provided_company_name="user_provided_company_name",
         )
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -107,7 +107,7 @@ async def test_raw_response_activate_account(self, async_client: AsyncCodex) ->
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         user = await response.parse()
-        assert_matches_type(User, user, path=["response"])
+        assert_matches_type(UserSchemaPublic, user, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -120,6 +120,6 @@ async def test_streaming_response_activate_account(self, async_client: AsyncCode
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             user = await response.parse()
-            assert_matches_type(User, user, path=["response"])
+            assert_matches_type(UserSchemaPublic, user, path=["response"])
 
         assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index a58477bc..c6ed0209 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -8,9 +8,8 @@
 import pytest
 
 from codex import Codex, AsyncCodex
-from codex.types import User
 from tests.utils import assert_matches_type
-from codex.types.users import UserSchema
+from codex.types.users import UserSchema, UserSchemaPublic
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -22,7 +21,7 @@ class TestAPIKey:
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         api_key = client.users.myself.api_key.retrieve()
-        assert_matches_type(User, api_key, path=["response"])
+        assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -32,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         api_key = response.parse()
-        assert_matches_type(User, api_key, path=["response"])
+        assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -42,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             api_key = response.parse()
-            assert_matches_type(User, api_key, path=["response"])
+            assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -82,7 +81,7 @@ class TestAsyncAPIKey:
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         api_key = await async_client.users.myself.api_key.retrieve()
-        assert_matches_type(User, api_key, path=["response"])
+        assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -92,7 +91,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         api_key = await response.parse()
-        assert_matches_type(User, api_key, path=["response"])
+        assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -102,7 +101,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             api_key = await response.parse()
-            assert_matches_type(User, api_key, path=["response"])
+            assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index 174f2cec..63123275 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -8,8 +8,8 @@
 import pytest
 
 from codex import Codex, AsyncCodex
-from codex.types import User
 from tests.utils import assert_matches_type
+from codex.types.users import UserSchemaPublic
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -21,7 +21,7 @@ class TestMyself:
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         myself = client.users.myself.retrieve()
-        assert_matches_type(User, myself, path=["response"])
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -31,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         myself = response.parse()
-        assert_matches_type(User, myself, path=["response"])
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -41,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             myself = response.parse()
-            assert_matches_type(User, myself, path=["response"])
+            assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -53,7 +53,7 @@ class TestAsyncMyself:
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await async_client.users.myself.retrieve()
-        assert_matches_type(User, myself, path=["response"])
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -63,7 +63,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         myself = await response.parse()
-        assert_matches_type(User, myself, path=["response"])
+        assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -73,6 +73,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             myself = await response.parse()
-            assert_matches_type(User, myself, path=["response"])
+            assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
         assert cast(Any, response.is_closed) is True

From 4299a96385535d570406f5930797ba47e5d53302 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 17:59:42 +0000
Subject: [PATCH 109/227] feat(api): add project increment_queries and other
 recent endpoints

---
 .stats.yml                                    |  2 +-
 src/codex/_client.py                          | 71 -------------------
 src/codex/resources/projects/access_keys.py   |  4 ++
 src/codex/resources/projects/clusters.py      |  4 +-
 src/codex/resources/projects/entries.py       | 12 +++-
 src/codex/resources/projects/projects.py      |  8 +--
 src/codex/types/project_list_params.py        |  6 +-
 .../projects/access_key_create_params.py      |  2 +
 .../types/projects/entry_create_params.py     |  2 +
 .../types/projects/entry_query_params.py      |  2 +
 .../projects/test_access_keys.py              |  2 +
 tests/api_resources/projects/test_entries.py  |  4 ++
 tests/api_resources/test_projects.py          | 28 +++-----
 13 files changed, 44 insertions(+), 103 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index aa1535bd..fd7bea9a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 42
-openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
+openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1
 config_hash: 2d88a0a41f5faca603ff2789a116d988
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 2641513f..f035421c 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -13,7 +13,6 @@
 from ._types import (
     NOT_GIVEN,
     Omit,
-    Headers,
     Timeout,
     NotGiven,
     Transport,
@@ -151,25 +150,6 @@ def __init__(
     def qs(self) -> Querystring:
         return Querystring(array_format="comma")
 
-    @property
-    @override
-    def auth_headers(self) -> dict[str, str]:
-        return {**self._authenticated_api_key, **self._public_access_key}
-
-    @property
-    def _authenticated_api_key(self) -> dict[str, str]:
-        api_key = self.api_key
-        if api_key is None:
-            return {}
-        return {"X-API-Key": api_key}
-
-    @property
-    def _public_access_key(self) -> dict[str, str]:
-        access_key = self.access_key
-        if access_key is None:
-            return {}
-        return {"X-Access-Key": access_key}
-
     @property
     @override
     def default_headers(self) -> dict[str, str | Omit]:
@@ -179,22 +159,6 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
-    @override
-    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
-        if self.api_key and headers.get("X-API-Key"):
-            return
-        if isinstance(custom_headers.get("X-API-Key"), Omit):
-            return
-
-        if self.access_key and headers.get("X-Access-Key"):
-            return
-        if isinstance(custom_headers.get("X-Access-Key"), Omit):
-            return
-
-        raise TypeError(
-            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
-        )
-
     def copy(
         self,
         *,
@@ -379,25 +343,6 @@ def __init__(
     def qs(self) -> Querystring:
         return Querystring(array_format="comma")
 
-    @property
-    @override
-    def auth_headers(self) -> dict[str, str]:
-        return {**self._authenticated_api_key, **self._public_access_key}
-
-    @property
-    def _authenticated_api_key(self) -> dict[str, str]:
-        api_key = self.api_key
-        if api_key is None:
-            return {}
-        return {"X-API-Key": api_key}
-
-    @property
-    def _public_access_key(self) -> dict[str, str]:
-        access_key = self.access_key
-        if access_key is None:
-            return {}
-        return {"X-Access-Key": access_key}
-
     @property
     @override
     def default_headers(self) -> dict[str, str | Omit]:
@@ -407,22 +352,6 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
-    @override
-    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
-        if self.api_key and headers.get("X-API-Key"):
-            return
-        if isinstance(custom_headers.get("X-API-Key"), Omit):
-            return
-
-        if self.access_key and headers.get("X-Access-Key"):
-            return
-        if isinstance(custom_headers.get("X-Access-Key"), Omit):
-            return
-
-        raise TypeError(
-            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
-        )
-
     def copy(
         self,
         *,
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 61987399..9ba2e131 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -57,6 +57,7 @@ def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -85,6 +86,7 @@ def create(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -346,6 +348,7 @@ async def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -374,6 +377,7 @@ async def create(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 584cde18..eabbe60e 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -115,7 +115,7 @@ def list_variants(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> ClusterListVariantsResponse:
         """
-        Get Cluster Variants Route
+        Get Cluster Variants
 
         Args:
           extra_headers: Send extra headers
@@ -230,7 +230,7 @@ async def list_variants(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> ClusterListVariantsResponse:
         """
-        Get Cluster Variants Route
+        Get Cluster Variants
 
         Args:
           extra_headers: Send extra headers
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index f88e2243..bdfee06d 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -57,6 +57,7 @@ def create(
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -85,6 +86,7 @@ def create(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -323,6 +325,7 @@ def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -335,7 +338,7 @@ def query(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EntryQueryResponse:
         """
-        Query Entries Route
+        Query Entries
 
         Args:
           extra_headers: Send extra headers
@@ -351,6 +354,7 @@ def query(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -446,6 +450,7 @@ async def create(
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -474,6 +479,7 @@ async def create(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -712,6 +718,7 @@ async def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -724,7 +731,7 @@ async def query(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EntryQueryResponse:
         """
-        Query Entries Route
+        Query Entries
 
         Args:
           extra_headers: Send extra headers
@@ -740,6 +747,7 @@ async def query(
         extra_headers = {
             **strip_not_given(
                 {
+                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 47639a5a..76237fb6 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -208,11 +208,11 @@ def update(
     def list(
         self,
         *,
-        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -243,11 +243,11 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
-                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
@@ -513,11 +513,11 @@ async def update(
     async def list(
         self,
         *,
-        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -548,11 +548,11 @@ async def list(
                 timeout=timeout,
                 query=await async_maybe_transform(
                     {
-                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
index c2589598..0ab3b84b 100644
--- a/src/codex/types/project_list_params.py
+++ b/src/codex/types/project_list_params.py
@@ -3,14 +3,12 @@
 from __future__ import annotations
 
 from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
 
 __all__ = ["ProjectListParams"]
 
 
 class ProjectListParams(TypedDict, total=False):
-    organization_id: Required[str]
-
     include_entry_counts: bool
 
     limit: int
@@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False):
 
     order: Literal["asc", "desc"]
 
+    organization_id: str
+
     query: Optional[str]
 
     sort: Literal["created_at", "updated_at"]
diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py
index cf5f00fb..5035836d 100644
--- a/src/codex/types/projects/access_key_create_params.py
+++ b/src/codex/types/projects/access_key_create_params.py
@@ -18,6 +18,8 @@ class AccessKeyCreateParams(TypedDict, total=False):
 
     expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
 
+    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
index f06846bb..490b0e83 100644
--- a/src/codex/types/projects/entry_create_params.py
+++ b/src/codex/types/projects/entry_create_params.py
@@ -19,6 +19,8 @@ class EntryCreateParams(TypedDict, total=False):
 
     draft_answer: Optional[str]
 
+    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index d58b7bfa..db1aec77 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -17,6 +17,8 @@ class EntryQueryParams(TypedDict, total=False):
 
     client_metadata: Optional[object]
 
+    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index ad4ee5e4..bcf3bb7a 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -399,6 +400,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 31a5e408..710d2146 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             answer="answer",
             client_query_metadata=[{}],
             draft_answer="draft_answer",
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -396,6 +397,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
             question="question",
             use_llm_matching=True,
             client_metadata={},
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -514,6 +516,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             answer="answer",
             client_query_metadata=[{}],
             draft_answer="draft_answer",
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -871,6 +874,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
             question="question",
             use_llm_matching=True,
             client_metadata={},
+            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index bfa657d2..d71d0e19 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
-        project = client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        project = client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.with_raw_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        response = client.projects.with_raw_response.list()
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.with_streaming_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
+        with client.projects.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
@@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
-        project = await async_client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        project = await async_client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.with_raw_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        response = await async_client.projects.with_raw_response.list()
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.with_streaming_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
+        async with async_client.projects.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 

From a2ef2b1418d9a7f394cce2151ee7cb6ad084af45 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 18:17:09 +0000
Subject: [PATCH 110/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/_client.py                          | 71 +++++++++++++++++++
 src/codex/resources/projects/access_keys.py   |  4 --
 src/codex/resources/projects/clusters.py      |  4 +-
 src/codex/resources/projects/entries.py       | 12 +---
 src/codex/resources/projects/projects.py      |  8 +--
 src/codex/types/project_list_params.py        |  6 +-
 .../projects/access_key_create_params.py      |  2 -
 .../types/projects/entry_create_params.py     |  2 -
 .../types/projects/entry_query_params.py      |  2 -
 .../projects/test_access_keys.py              |  2 -
 tests/api_resources/projects/test_entries.py  |  4 --
 tests/api_resources/test_projects.py          | 28 +++++---
 13 files changed, 103 insertions(+), 44 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index fd7bea9a..aa1535bd 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 42
-openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1
+openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
 config_hash: 2d88a0a41f5faca603ff2789a116d988
diff --git a/src/codex/_client.py b/src/codex/_client.py
index f035421c..2641513f 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -13,6 +13,7 @@
 from ._types import (
     NOT_GIVEN,
     Omit,
+    Headers,
     Timeout,
     NotGiven,
     Transport,
@@ -150,6 +151,25 @@ def __init__(
     def qs(self) -> Querystring:
         return Querystring(array_format="comma")
 
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        return {**self._authenticated_api_key, **self._public_access_key}
+
+    @property
+    def _authenticated_api_key(self) -> dict[str, str]:
+        api_key = self.api_key
+        if api_key is None:
+            return {}
+        return {"X-API-Key": api_key}
+
+    @property
+    def _public_access_key(self) -> dict[str, str]:
+        access_key = self.access_key
+        if access_key is None:
+            return {}
+        return {"X-Access-Key": access_key}
+
     @property
     @override
     def default_headers(self) -> dict[str, str | Omit]:
@@ -159,6 +179,22 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
+    @override
+    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.api_key and headers.get("X-API-Key"):
+            return
+        if isinstance(custom_headers.get("X-API-Key"), Omit):
+            return
+
+        if self.access_key and headers.get("X-Access-Key"):
+            return
+        if isinstance(custom_headers.get("X-Access-Key"), Omit):
+            return
+
+        raise TypeError(
+            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+        )
+
     def copy(
         self,
         *,
@@ -343,6 +379,25 @@ def __init__(
     def qs(self) -> Querystring:
         return Querystring(array_format="comma")
 
+    @property
+    @override
+    def auth_headers(self) -> dict[str, str]:
+        return {**self._authenticated_api_key, **self._public_access_key}
+
+    @property
+    def _authenticated_api_key(self) -> dict[str, str]:
+        api_key = self.api_key
+        if api_key is None:
+            return {}
+        return {"X-API-Key": api_key}
+
+    @property
+    def _public_access_key(self) -> dict[str, str]:
+        access_key = self.access_key
+        if access_key is None:
+            return {}
+        return {"X-Access-Key": access_key}
+
     @property
     @override
     def default_headers(self) -> dict[str, str | Omit]:
@@ -352,6 +407,22 @@ def default_headers(self) -> dict[str, str | Omit]:
             **self._custom_headers,
         }
 
+    @override
+    def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.api_key and headers.get("X-API-Key"):
+            return
+        if isinstance(custom_headers.get("X-API-Key"), Omit):
+            return
+
+        if self.access_key and headers.get("X-Access-Key"):
+            return
+        if isinstance(custom_headers.get("X-Access-Key"), Omit):
+            return
+
+        raise TypeError(
+            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+        )
+
     def copy(
         self,
         *,
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 9ba2e131..61987399 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -57,7 +57,6 @@ def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -86,7 +85,6 @@ def create(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -348,7 +346,6 @@ async def create(
         name: str,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -377,7 +374,6 @@ async def create(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index eabbe60e..584cde18 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -115,7 +115,7 @@ def list_variants(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> ClusterListVariantsResponse:
         """
-        Get Cluster Variants
+        Get Cluster Variants Route
 
         Args:
           extra_headers: Send extra headers
@@ -230,7 +230,7 @@ async def list_variants(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> ClusterListVariantsResponse:
         """
-        Get Cluster Variants
+        Get Cluster Variants Route
 
         Args:
           extra_headers: Send extra headers
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index bdfee06d..f88e2243 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -57,7 +57,6 @@ def create(
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -86,7 +85,6 @@ def create(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -325,7 +323,6 @@ def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -338,7 +335,7 @@ def query(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EntryQueryResponse:
         """
-        Query Entries
+        Query Entries Route
 
         Args:
           extra_headers: Send extra headers
@@ -354,7 +351,6 @@ def query(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -450,7 +446,6 @@ async def create(
         answer: Optional[str] | NotGiven = NOT_GIVEN,
         client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
         draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -479,7 +474,6 @@ async def create(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
@@ -718,7 +712,6 @@ async def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
-        x_access_key: str | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -731,7 +724,7 @@ async def query(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EntryQueryResponse:
         """
-        Query Entries
+        Query Entries Route
 
         Args:
           extra_headers: Send extra headers
@@ -747,7 +740,6 @@ async def query(
         extra_headers = {
             **strip_not_given(
                 {
-                    "x-access-key": x_access_key,
                     "x-client-library-version": x_client_library_version,
                     "x-integration-type": x_integration_type,
                     "x-source": x_source,
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 76237fb6..47639a5a 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -208,11 +208,11 @@ def update(
     def list(
         self,
         *,
+        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -243,11 +243,11 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
+                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
-                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
@@ -513,11 +513,11 @@ async def update(
     async def list(
         self,
         *,
+        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -548,11 +548,11 @@ async def list(
                 timeout=timeout,
                 query=await async_maybe_transform(
                     {
+                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
-                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
index 0ab3b84b..c2589598 100644
--- a/src/codex/types/project_list_params.py
+++ b/src/codex/types/project_list_params.py
@@ -3,12 +3,14 @@
 from __future__ import annotations
 
 from typing import Optional
-from typing_extensions import Literal, TypedDict
+from typing_extensions import Literal, Required, TypedDict
 
 __all__ = ["ProjectListParams"]
 
 
 class ProjectListParams(TypedDict, total=False):
+    organization_id: Required[str]
+
     include_entry_counts: bool
 
     limit: int
@@ -17,8 +19,6 @@ class ProjectListParams(TypedDict, total=False):
 
     order: Literal["asc", "desc"]
 
-    organization_id: str
-
     query: Optional[str]
 
     sort: Literal["created_at", "updated_at"]
diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py
index 5035836d..cf5f00fb 100644
--- a/src/codex/types/projects/access_key_create_params.py
+++ b/src/codex/types/projects/access_key_create_params.py
@@ -18,8 +18,6 @@ class AccessKeyCreateParams(TypedDict, total=False):
 
     expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
 
-    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
-
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
index 490b0e83..f06846bb 100644
--- a/src/codex/types/projects/entry_create_params.py
+++ b/src/codex/types/projects/entry_create_params.py
@@ -19,8 +19,6 @@ class EntryCreateParams(TypedDict, total=False):
 
     draft_answer: Optional[str]
 
-    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
-
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index db1aec77..d58b7bfa 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -17,8 +17,6 @@ class EntryQueryParams(TypedDict, total=False):
 
     client_metadata: Optional[object]
 
-    x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")]
-
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index bcf3bb7a..ad4ee5e4 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -400,7 +399,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             name="name",
             description="description",
             expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 710d2146..31a5e408 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             answer="answer",
             client_query_metadata=[{}],
             draft_answer="draft_answer",
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -397,7 +396,6 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
             question="question",
             use_llm_matching=True,
             client_metadata={},
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -516,7 +514,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             answer="answer",
             client_query_metadata=[{}],
             draft_answer="draft_answer",
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -874,7 +871,6 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
             question="question",
             use_llm_matching=True,
             client_metadata={},
-            x_access_key="x-access-key",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index d71d0e19..bfa657d2 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -194,18 +194,20 @@ def test_path_params_update(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
-        project = client.projects.list()
+        project = client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -214,7 +216,9 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.with_raw_response.list()
+        response = client.projects.with_raw_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -224,7 +228,9 @@ def test_raw_response_list(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.with_streaming_response.list() as response:
+        with client.projects.with_streaming_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
@@ -536,18 +542,20 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
-        project = await async_client.projects.list()
+        project = await async_client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -556,7 +564,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.with_raw_response.list()
+        response = await async_client.projects.with_raw_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -566,7 +576,9 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.with_streaming_response.list() as response:
+        async with async_client.projects.with_streaming_response.list(
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 

From 30952e1d34a979f5de4e4ee612e0d5eb11443bbb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 23:16:17 +0000
Subject: [PATCH 111/227] feat(api): api update

---
 .stats.yml                               |  2 +-
 src/codex/resources/projects/projects.py |  8 +++----
 src/codex/types/project_list_params.py   |  6 ++---
 tests/api_resources/test_projects.py     | 28 +++++++-----------------
 4 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index aa1535bd..42e47500 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 42
-openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d
+openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12
 config_hash: 2d88a0a41f5faca603ff2789a116d988
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 47639a5a..76237fb6 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -208,11 +208,11 @@ def update(
     def list(
         self,
         *,
-        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -243,11 +243,11 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
-                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
@@ -513,11 +513,11 @@ async def update(
     async def list(
         self,
         *,
-        organization_id: str,
         include_entry_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        organization_id: str | NotGiven = NOT_GIVEN,
         query: Optional[str] | NotGiven = NOT_GIVEN,
         sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -548,11 +548,11 @@ async def list(
                 timeout=timeout,
                 query=await async_maybe_transform(
                     {
-                        "organization_id": organization_id,
                         "include_entry_counts": include_entry_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "organization_id": organization_id,
                         "query": query,
                         "sort": sort,
                     },
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
index c2589598..0ab3b84b 100644
--- a/src/codex/types/project_list_params.py
+++ b/src/codex/types/project_list_params.py
@@ -3,14 +3,12 @@
 from __future__ import annotations
 
 from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
 
 __all__ = ["ProjectListParams"]
 
 
 class ProjectListParams(TypedDict, total=False):
-    organization_id: Required[str]
-
     include_entry_counts: bool
 
     limit: int
@@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False):
 
     order: Literal["asc", "desc"]
 
+    organization_id: str
+
     query: Optional[str]
 
     sort: Literal["created_at", "updated_at"]
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index bfa657d2..d71d0e19 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_method_list(self, client: Codex) -> None:
-        project = client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        project = client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.with_raw_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        response = client.projects.with_raw_response.list()
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.with_streaming_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
+        with client.projects.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
@@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
-        project = await async_client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        project = await async_client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             include_entry_counts=True,
             limit=0,
             offset=0,
             order="asc",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             query="query",
             sort="created_at",
         )
@@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.with_raw_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        response = await async_client.projects.with_raw_response.list()
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.with_streaming_response.list(
-            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
+        async with async_client.projects.with_streaming_response.list() as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 

From 419f26bfb8c9acd7c45d6881dbed365d07146ae6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:25:10 +0000
Subject: [PATCH 112/227] chore(ci): add timeout thresholds for CI jobs

---
 .github/workflows/ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e8b72361..1e4dab9d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,7 @@ on:
 
 jobs:
   lint:
+    timeout-minutes: 10
     name: lint
     runs-on: ubuntu-latest
     steps:

From 825b36271a9985b00ebaab7b37608d8b09984f75 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:25:42 +0000
Subject: [PATCH 113/227] chore(internal): import reformatting

---
 src/codex/_client.py                        | 5 +----
 src/codex/resources/projects/access_keys.py | 6 +-----
 src/codex/resources/projects/entries.py     | 6 +-----
 src/codex/resources/projects/projects.py    | 5 +----
 src/codex/resources/tlm.py                  | 5 +----
 src/codex/resources/users/users.py          | 5 +----
 6 files changed, 6 insertions(+), 26 deletions(-)

diff --git a/src/codex/_client.py b/src/codex/_client.py
index 2641513f..7464f6dc 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -20,10 +20,7 @@
     ProxiesTypes,
     RequestOptions,
 )
-from ._utils import (
-    is_given,
-    get_async_library,
-)
+from ._utils import is_given, get_async_library
 from ._version import __version__
 from .resources import tlm, health
 from ._streaming import Stream as Stream, AsyncStream as AsyncStream
diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py
index 61987399..15190030 100644
--- a/src/codex/resources/projects/access_keys.py
+++ b/src/codex/resources/projects/access_keys.py
@@ -8,11 +8,7 @@
 import httpx
 
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import (
-    maybe_transform,
-    strip_not_given,
-    async_maybe_transform,
-)
+from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index f88e2243..a9e690b9 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -7,11 +7,7 @@
 import httpx
 
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import (
-    maybe_transform,
-    strip_not_given,
-    async_maybe_transform,
-)
+from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 76237fb6..95bcd3a3 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -17,10 +17,7 @@
     AsyncEntriesResourceWithStreamingResponse,
 )
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import (
-    maybe_transform,
-    async_maybe_transform,
-)
+from ..._utils import maybe_transform, async_maybe_transform
 from .clusters import (
     ClustersResource,
     AsyncClustersResource,
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index c6585d0d..78f97e2e 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -9,10 +9,7 @@
 
 from ..types import tlm_score_params, tlm_prompt_params
 from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from .._utils import (
-    maybe_transform,
-    async_maybe_transform,
-)
+from .._utils import maybe_transform, async_maybe_transform
 from .._compat import cached_property
 from .._resource import SyncAPIResource, AsyncAPIResource
 from .._response import (
diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py
index c276f950..d207a96d 100644
--- a/src/codex/resources/users/users.py
+++ b/src/codex/resources/users/users.py
@@ -9,10 +9,7 @@
 
 from ...types import user_activate_account_params
 from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._utils import (
-    maybe_transform,
-    async_maybe_transform,
-)
+from ..._utils import maybe_transform, async_maybe_transform
 from ..._compat import cached_property
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (

From cb23e13617963afa0864b3a176b82111e939cb32 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:27:01 +0000
Subject: [PATCH 114/227] chore(internal): fix list file params

---
 src/codex/_utils/_utils.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py
index e5811bba..ea3cf3f2 100644
--- a/src/codex/_utils/_utils.py
+++ b/src/codex/_utils/_utils.py
@@ -72,8 +72,16 @@ def _extract_items(
         from .._files import assert_is_file_content
 
         # We have exhausted the path, return the entry we found.
-        assert_is_file_content(obj, key=flattened_key)
         assert flattened_key is not None
+
+        if is_list(obj):
+            files: list[tuple[str, FileTypes]] = []
+            for entry in obj:
+                assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
+                files.append((flattened_key + "[]", cast(FileTypes, entry)))
+            return files
+
+        assert_is_file_content(obj, key=flattened_key)
         return [(flattened_key, cast(FileTypes, obj))]
 
     index += 1

From daaace047284e762fd8a4d90f3c5c3aed6678e78 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:27:27 +0000
Subject: [PATCH 115/227] chore(internal): refactor retries to not use
 recursion

---
 src/codex/_base_client.py | 414 ++++++++++++++++----------------------
 1 file changed, 175 insertions(+), 239 deletions(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 69be76db..0bf6ca7f 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -437,8 +437,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
         headers = httpx.Headers(headers_dict)
 
         idempotency_header = self._idempotency_header
-        if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
-            options.idempotency_key = options.idempotency_key or self._idempotency_key()
+        if idempotency_header and options.idempotency_key and idempotency_header not in headers:
             headers[idempotency_header] = options.idempotency_key
 
         # Don't set these headers if they were already set or removed by the caller. We check
@@ -903,7 +902,6 @@ def request(
         self,
         cast_to: Type[ResponseT],
         options: FinalRequestOptions,
-        remaining_retries: Optional[int] = None,
         *,
         stream: Literal[True],
         stream_cls: Type[_StreamT],
@@ -914,7 +912,6 @@ def request(
         self,
         cast_to: Type[ResponseT],
         options: FinalRequestOptions,
-        remaining_retries: Optional[int] = None,
         *,
         stream: Literal[False] = False,
     ) -> ResponseT: ...
@@ -924,7 +921,6 @@ def request(
         self,
         cast_to: Type[ResponseT],
         options: FinalRequestOptions,
-        remaining_retries: Optional[int] = None,
         *,
         stream: bool = False,
         stream_cls: Type[_StreamT] | None = None,
@@ -934,125 +930,109 @@ def request(
         self,
         cast_to: Type[ResponseT],
         options: FinalRequestOptions,
-        remaining_retries: Optional[int] = None,
         *,
         stream: bool = False,
         stream_cls: type[_StreamT] | None = None,
     ) -> ResponseT | _StreamT:
-        if remaining_retries is not None:
-            retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
-        else:
-            retries_taken = 0
-
-        return self._request(
-            cast_to=cast_to,
-            options=options,
-            stream=stream,
-            stream_cls=stream_cls,
-            retries_taken=retries_taken,
-        )
+        cast_to = self._maybe_override_cast_to(cast_to, options)
 
-    def _request(
-        self,
-        *,
-        cast_to: Type[ResponseT],
-        options: FinalRequestOptions,
-        retries_taken: int,
-        stream: bool,
-        stream_cls: type[_StreamT] | None,
-    ) -> ResponseT | _StreamT:
         # create a copy of the options we were given so that if the
         # options are mutated later & we then retry, the retries are
         # given the original options
         input_options = model_copy(options)
-
-        cast_to = self._maybe_override_cast_to(cast_to, options)
-        options = self._prepare_options(options)
-
-        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
-        request = self._build_request(options, retries_taken=retries_taken)
-        self._prepare_request(request)
-
-        if options.idempotency_key:
+        if input_options.idempotency_key is None and input_options.method.lower() != "get":
             # ensure the idempotency key is reused between requests
-            input_options.idempotency_key = options.idempotency_key
+            input_options.idempotency_key = self._idempotency_key()
 
-        kwargs: HttpxSendArgs = {}
-        if self.custom_auth is not None:
-            kwargs["auth"] = self.custom_auth
+        response: httpx.Response | None = None
+        max_retries = input_options.get_max_retries(self.max_retries)
 
-        log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+        retries_taken = 0
+        for retries_taken in range(max_retries + 1):
+            options = model_copy(input_options)
+            options = self._prepare_options(options)
 
-        try:
-            response = self._client.send(
-                request,
-                stream=stream or self._should_stream_response_body(request=request),
-                **kwargs,
-            )
-        except httpx.TimeoutException as err:
-            log.debug("Encountered httpx.TimeoutException", exc_info=True)
+            remaining_retries = max_retries - retries_taken
+            request = self._build_request(options, retries_taken=retries_taken)
+            self._prepare_request(request)
 
-            if remaining_retries > 0:
-                return self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                    response_headers=None,
-                )
+            kwargs: HttpxSendArgs = {}
+            if self.custom_auth is not None:
+                kwargs["auth"] = self.custom_auth
 
-            log.debug("Raising timeout error")
-            raise APITimeoutError(request=request) from err
-        except Exception as err:
-            log.debug("Encountered Exception", exc_info=True)
+            log.debug("Sending HTTP Request: %s %s", request.method, request.url)
 
-            if remaining_retries > 0:
-                return self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                    response_headers=None,
+            response = None
+            try:
+                response = self._client.send(
+                    request,
+                    stream=stream or self._should_stream_response_body(request=request),
+                    **kwargs,
                 )
+            except httpx.TimeoutException as err:
+                log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+                if remaining_retries > 0:
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising timeout error")
+                raise APITimeoutError(request=request) from err
+            except Exception as err:
+                log.debug("Encountered Exception", exc_info=True)
+
+                if remaining_retries > 0:
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising connection error")
+                raise APIConnectionError(request=request) from err
+
+            log.debug(
+                'HTTP Response: %s %s "%i %s" %s',
+                request.method,
+                request.url,
+                response.status_code,
+                response.reason_phrase,
+                response.headers,
+            )
 
-            log.debug("Raising connection error")
-            raise APIConnectionError(request=request) from err
-
-        log.debug(
-            'HTTP Response: %s %s "%i %s" %s',
-            request.method,
-            request.url,
-            response.status_code,
-            response.reason_phrase,
-            response.headers,
-        )
+            try:
+                response.raise_for_status()
+            except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+                log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+                if remaining_retries > 0 and self._should_retry(err.response):
+                    err.response.close()
+                    self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=response,
+                    )
+                    continue
 
-        try:
-            response.raise_for_status()
-        except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
-            log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
-
-            if remaining_retries > 0 and self._should_retry(err.response):
-                err.response.close()
-                return self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    response_headers=err.response.headers,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                )
+                # If the response is streamed then we need to explicitly read the response
+                # to completion before attempting to access the response text.
+                if not err.response.is_closed:
+                    err.response.read()
 
-            # If the response is streamed then we need to explicitly read the response
-            # to completion before attempting to access the response text.
-            if not err.response.is_closed:
-                err.response.read()
+                log.debug("Re-raising status error")
+                raise self._make_status_error_from_response(err.response) from None
 
-            log.debug("Re-raising status error")
-            raise self._make_status_error_from_response(err.response) from None
+            break
 
+        assert response is not None, "could not resolve response (should never happen)"
         return self._process_response(
             cast_to=cast_to,
             options=options,
@@ -1062,37 +1042,20 @@ def _request(
             retries_taken=retries_taken,
         )
 
-    def _retry_request(
-        self,
-        options: FinalRequestOptions,
-        cast_to: Type[ResponseT],
-        *,
-        retries_taken: int,
-        response_headers: httpx.Headers | None,
-        stream: bool,
-        stream_cls: type[_StreamT] | None,
-    ) -> ResponseT | _StreamT:
-        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+    def _sleep_for_retry(
+        self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+    ) -> None:
+        remaining_retries = max_retries - retries_taken
         if remaining_retries == 1:
             log.debug("1 retry left")
         else:
             log.debug("%i retries left", remaining_retries)
 
-        timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
         log.info("Retrying request to %s in %f seconds", options.url, timeout)
 
-        # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
-        # different thread if necessary.
         time.sleep(timeout)
 
-        return self._request(
-            options=options,
-            cast_to=cast_to,
-            retries_taken=retries_taken + 1,
-            stream=stream,
-            stream_cls=stream_cls,
-        )
-
     def _process_response(
         self,
         *,
@@ -1436,7 +1399,6 @@ async def request(
         options: FinalRequestOptions,
         *,
         stream: Literal[False] = False,
-        remaining_retries: Optional[int] = None,
     ) -> ResponseT: ...
 
     @overload
@@ -1447,7 +1409,6 @@ async def request(
         *,
         stream: Literal[True],
         stream_cls: type[_AsyncStreamT],
-        remaining_retries: Optional[int] = None,
     ) -> _AsyncStreamT: ...
 
     @overload
@@ -1458,7 +1419,6 @@ async def request(
         *,
         stream: bool,
         stream_cls: type[_AsyncStreamT] | None = None,
-        remaining_retries: Optional[int] = None,
     ) -> ResponseT | _AsyncStreamT: ...
 
     async def request(
@@ -1468,120 +1428,111 @@ async def request(
         *,
         stream: bool = False,
         stream_cls: type[_AsyncStreamT] | None = None,
-        remaining_retries: Optional[int] = None,
-    ) -> ResponseT | _AsyncStreamT:
-        if remaining_retries is not None:
-            retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
-        else:
-            retries_taken = 0
-
-        return await self._request(
-            cast_to=cast_to,
-            options=options,
-            stream=stream,
-            stream_cls=stream_cls,
-            retries_taken=retries_taken,
-        )
-
-    async def _request(
-        self,
-        cast_to: Type[ResponseT],
-        options: FinalRequestOptions,
-        *,
-        stream: bool,
-        stream_cls: type[_AsyncStreamT] | None,
-        retries_taken: int,
     ) -> ResponseT | _AsyncStreamT:
         if self._platform is None:
             # `get_platform` can make blocking IO calls so we
             # execute it earlier while we are in an async context
             self._platform = await asyncify(get_platform)()
 
+        cast_to = self._maybe_override_cast_to(cast_to, options)
+
         # create a copy of the options we were given so that if the
         # options are mutated later & we then retry, the retries are
         # given the original options
         input_options = model_copy(options)
-
-        cast_to = self._maybe_override_cast_to(cast_to, options)
-        options = await self._prepare_options(options)
-
-        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
-        request = self._build_request(options, retries_taken=retries_taken)
-        await self._prepare_request(request)
-
-        if options.idempotency_key:
+        if input_options.idempotency_key is None and input_options.method.lower() != "get":
             # ensure the idempotency key is reused between requests
-            input_options.idempotency_key = options.idempotency_key
+            input_options.idempotency_key = self._idempotency_key()
 
-        kwargs: HttpxSendArgs = {}
-        if self.custom_auth is not None:
-            kwargs["auth"] = self.custom_auth
+        response: httpx.Response | None = None
+        max_retries = input_options.get_max_retries(self.max_retries)
 
-        try:
-            response = await self._client.send(
-                request,
-                stream=stream or self._should_stream_response_body(request=request),
-                **kwargs,
-            )
-        except httpx.TimeoutException as err:
-            log.debug("Encountered httpx.TimeoutException", exc_info=True)
+        retries_taken = 0
+        for retries_taken in range(max_retries + 1):
+            options = model_copy(input_options)
+            options = await self._prepare_options(options)
 
-            if remaining_retries > 0:
-                return await self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                    response_headers=None,
-                )
+            remaining_retries = max_retries - retries_taken
+            request = self._build_request(options, retries_taken=retries_taken)
+            await self._prepare_request(request)
 
-            log.debug("Raising timeout error")
-            raise APITimeoutError(request=request) from err
-        except Exception as err:
-            log.debug("Encountered Exception", exc_info=True)
+            kwargs: HttpxSendArgs = {}
+            if self.custom_auth is not None:
+                kwargs["auth"] = self.custom_auth
 
-            if remaining_retries > 0:
-                return await self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                    response_headers=None,
-                )
+            log.debug("Sending HTTP Request: %s %s", request.method, request.url)
 
-            log.debug("Raising connection error")
-            raise APIConnectionError(request=request) from err
+            response = None
+            try:
+                response = await self._client.send(
+                    request,
+                    stream=stream or self._should_stream_response_body(request=request),
+                    **kwargs,
+                )
+            except httpx.TimeoutException as err:
+                log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+                if remaining_retries > 0:
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising timeout error")
+                raise APITimeoutError(request=request) from err
+            except Exception as err:
+                log.debug("Encountered Exception", exc_info=True)
+
+                if remaining_retries > 0:
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=None,
+                    )
+                    continue
+
+                log.debug("Raising connection error")
+                raise APIConnectionError(request=request) from err
+
+            log.debug(
+                'HTTP Response: %s %s "%i %s" %s',
+                request.method,
+                request.url,
+                response.status_code,
+                response.reason_phrase,
+                response.headers,
+            )
 
-        log.debug(
-            'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
-        )
+            try:
+                response.raise_for_status()
+            except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
+                log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+                if remaining_retries > 0 and self._should_retry(err.response):
+                    await err.response.aclose()
+                    await self._sleep_for_retry(
+                        retries_taken=retries_taken,
+                        max_retries=max_retries,
+                        options=input_options,
+                        response=response,
+                    )
+                    continue
 
-        try:
-            response.raise_for_status()
-        except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
-            log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
-
-            if remaining_retries > 0 and self._should_retry(err.response):
-                await err.response.aclose()
-                return await self._retry_request(
-                    input_options,
-                    cast_to,
-                    retries_taken=retries_taken,
-                    response_headers=err.response.headers,
-                    stream=stream,
-                    stream_cls=stream_cls,
-                )
+                # If the response is streamed then we need to explicitly read the response
+                # to completion before attempting to access the response text.
+                if not err.response.is_closed:
+                    await err.response.aread()
 
-            # If the response is streamed then we need to explicitly read the response
-            # to completion before attempting to access the response text.
-            if not err.response.is_closed:
-                await err.response.aread()
+                log.debug("Re-raising status error")
+                raise self._make_status_error_from_response(err.response) from None
 
-            log.debug("Re-raising status error")
-            raise self._make_status_error_from_response(err.response) from None
+            break
 
+        assert response is not None, "could not resolve response (should never happen)"
         return await self._process_response(
             cast_to=cast_to,
             options=options,
@@ -1591,35 +1542,20 @@ async def _request(
             retries_taken=retries_taken,
         )
 
-    async def _retry_request(
-        self,
-        options: FinalRequestOptions,
-        cast_to: Type[ResponseT],
-        *,
-        retries_taken: int,
-        response_headers: httpx.Headers | None,
-        stream: bool,
-        stream_cls: type[_AsyncStreamT] | None,
-    ) -> ResponseT | _AsyncStreamT:
-        remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+    async def _sleep_for_retry(
+        self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+    ) -> None:
+        remaining_retries = max_retries - retries_taken
         if remaining_retries == 1:
             log.debug("1 retry left")
         else:
             log.debug("%i retries left", remaining_retries)
 
-        timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+        timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
         log.info("Retrying request to %s in %f seconds", options.url, timeout)
 
         await anyio.sleep(timeout)
 
-        return await self._request(
-            options=options,
-            cast_to=cast_to,
-            retries_taken=retries_taken + 1,
-            stream=stream,
-            stream_cls=stream_cls,
-        )
-
     async def _process_response(
         self,
         *,

From 0e5083c23afb29fded40fa35d71938bb9a3edf80 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:28:00 +0000
Subject: [PATCH 116/227] fix(pydantic v1): more robust ModelField.annotation
 check

---
 src/codex/_models.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index 58b9263e..798956f1 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -626,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
                 # Note: if one variant defines an alias then they all should
                 discriminator_alias = field_info.alias
 
-                if field_info.annotation and is_literal_type(field_info.annotation):
-                    for entry in get_args(field_info.annotation):
+                if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
+                    for entry in get_args(annotation):
                         if isinstance(entry, str):
                             mapping[entry] = variant
 

From a1469f82a18840cddf5cd343a434276119c2dff2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 14:16:17 +0000
Subject: [PATCH 117/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 42e47500..185dd58f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 42
-openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12
+openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7
 config_hash: 2d88a0a41f5faca603ff2789a116d988

From 13f2a9da6a90261442fc164749acc7bd47cf5de3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 15:53:25 +0000
Subject: [PATCH 118/227] feat(api): add proj analytics endpoint

---
 .stats.yml                                    |   4 +-
 api.md                                        |   2 +
 src/codex/resources/projects/projects.py      | 126 +++++++++++++++++-
 src/codex/types/__init__.py                   |   2 +
 .../project_retrieve_analytics_params.py      |  18 +++
 .../project_retrieve_analytics_response.py    |  43 ++++++
 tests/api_resources/test_projects.py          | 107 +++++++++++++++
 7 files changed, 299 insertions(+), 3 deletions(-)
 create mode 100644 src/codex/types/project_retrieve_analytics_params.py
 create mode 100644 src/codex/types/project_retrieve_analytics_response.py

diff --git a/.stats.yml b/.stats.yml
index 185dd58f..230d2377 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 42
+configured_endpoints: 43
 openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7
-config_hash: 2d88a0a41f5faca603ff2789a116d988
+config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/api.md b/api.md
index 617cdd24..f240632f 100644
--- a/api.md
+++ b/api.md
@@ -141,6 +141,7 @@ from codex.types import (
     ProjectListResponse,
     ProjectExportResponse,
     ProjectIncrementQueriesResponse,
+    ProjectRetrieveAnalyticsResponse,
 )
 ```
 
@@ -153,6 +154,7 @@ Methods:
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
 - <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
+- <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
 
 ## AccessKeys
 
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 95bcd3a3..6ac036c0 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -7,7 +7,12 @@
 
 import httpx
 
-from ...types import project_list_params, project_create_params, project_update_params
+from ...types import (
+    project_list_params,
+    project_create_params,
+    project_update_params,
+    project_retrieve_analytics_params,
+)
 from .entries import (
     EntriesResource,
     AsyncEntriesResource,
@@ -46,6 +51,7 @@
 from ...types.project_list_response import ProjectListResponse
 from ...types.project_return_schema import ProjectReturnSchema
 from ...types.project_retrieve_response import ProjectRetrieveResponse
+from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse
 
 __all__ = ["ProjectsResource", "AsyncProjectsResource"]
 
@@ -354,6 +360,59 @@ def increment_queries(
             cast_to=object,
         )
 
+    def retrieve_analytics(
+        self,
+        project_id: str,
+        *,
+        end: int | NotGiven = NOT_GIVEN,
+        sme_limit: int | NotGiven = NOT_GIVEN,
+        start: int | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectRetrieveAnalyticsResponse:
+        """
+        Get Project Analytics Route
+
+        Args:
+          end: End timestamp in seconds since epoch
+
+          sme_limit: Limit the number of top SME contributors to return.
+
+          start: Start timestamp in seconds since epoch
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/analytics/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "end": end,
+                        "sme_limit": sme_limit,
+                        "start": start,
+                    },
+                    project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams,
+                ),
+            ),
+            cast_to=ProjectRetrieveAnalyticsResponse,
+        )
+
 
 class AsyncProjectsResource(AsyncAPIResource):
     @cached_property
@@ -659,6 +718,59 @@ async def increment_queries(
             cast_to=object,
         )
 
+    async def retrieve_analytics(
+        self,
+        project_id: str,
+        *,
+        end: int | NotGiven = NOT_GIVEN,
+        sme_limit: int | NotGiven = NOT_GIVEN,
+        start: int | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectRetrieveAnalyticsResponse:
+        """
+        Get Project Analytics Route
+
+        Args:
+          end: End timestamp in seconds since epoch
+
+          sme_limit: Limit the number of top SME contributors to return.
+
+          start: Start timestamp in seconds since epoch
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/analytics/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "end": end,
+                        "sme_limit": sme_limit,
+                        "start": start,
+                    },
+                    project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams,
+                ),
+            ),
+            cast_to=ProjectRetrieveAnalyticsResponse,
+        )
+
 
 class ProjectsResourceWithRawResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -685,6 +797,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.increment_queries = to_raw_response_wrapper(
             projects.increment_queries,
         )
+        self.retrieve_analytics = to_raw_response_wrapper(
+            projects.retrieve_analytics,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
@@ -724,6 +839,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.increment_queries = async_to_raw_response_wrapper(
             projects.increment_queries,
         )
+        self.retrieve_analytics = async_to_raw_response_wrapper(
+            projects.retrieve_analytics,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
@@ -763,6 +881,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.increment_queries = to_streamed_response_wrapper(
             projects.increment_queries,
         )
+        self.retrieve_analytics = to_streamed_response_wrapper(
+            projects.retrieve_analytics,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
@@ -802,6 +923,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.increment_queries = async_to_streamed_response_wrapper(
             projects.increment_queries,
         )
+        self.retrieve_analytics = async_to_streamed_response_wrapper(
+            projects.retrieve_analytics,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 008c4a67..53d1ab6b 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -15,7 +15,9 @@
 from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
+from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams
 from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse
+from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse
 from .organization_retrieve_permissions_response import (
     OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse,
 )
diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py
new file mode 100644
index 00000000..aadb4159
--- /dev/null
+++ b/src/codex/types/project_retrieve_analytics_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectRetrieveAnalyticsParams"]
+
+
+class ProjectRetrieveAnalyticsParams(TypedDict, total=False):
+    end: int
+    """End timestamp in seconds since epoch"""
+
+    sme_limit: int
+    """Limit the number of top SME contributors to return."""
+
+    start: int
+    """Start timestamp in seconds since epoch"""
diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py
new file mode 100644
index 00000000..f17975ec
--- /dev/null
+++ b/src/codex/types/project_retrieve_analytics_response.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List
+
+from .._models import BaseModel
+
+__all__ = [
+    "ProjectRetrieveAnalyticsResponse",
+    "AnswersPublished",
+    "AnswersPublishedAnswersByAuthor",
+    "BadResponses",
+    "Queries",
+]
+
+
+class AnswersPublishedAnswersByAuthor(BaseModel):
+    answers_published: int
+
+    name: str
+
+    user_id: str
+
+
+class AnswersPublished(BaseModel):
+    answers_by_author: List[AnswersPublishedAnswersByAuthor]
+
+
+class BadResponses(BaseModel):
+    responses_by_type: Dict[str, int]
+
+    total: int
+
+
+class Queries(BaseModel):
+    total: int
+
+
+class ProjectRetrieveAnalyticsResponse(BaseModel):
+    answers_published: AnswersPublished
+
+    bad_responses: BadResponses
+
+    queries: Queries
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index d71d0e19..3a8f0ec8 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -12,6 +12,7 @@
     ProjectListResponse,
     ProjectReturnSchema,
     ProjectRetrieveResponse,
+    ProjectRetrieveAnalyticsResponse,
 )
 from tests.utils import assert_matches_type
 
@@ -359,6 +360,59 @@ def test_path_params_increment_queries(self, client: Codex) -> None:
                 "",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_analytics(self, client: Codex) -> None:
+        project = client.projects.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None:
+        project = client.projects.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            end=0,
+            sme_limit=1,
+            start=0,
+        )
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve_analytics(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve_analytics(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve_analytics(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.retrieve_analytics(
+                project_id="",
+            )
+
 
 class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -700,3 +754,56 @@ async def test_path_params_increment_queries(self, async_client: AsyncCodex) ->
             await async_client.projects.with_raw_response.increment_queries(
                 "",
             )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve_analytics_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            end=0,
+            sme_limit=1,
+            start=0,
+        )
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.retrieve_analytics(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.retrieve_analytics(
+                project_id="",
+            )

From 92c72a790a8b0cab55e3b3a8e19f2c0cd0aed005 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 17:08:50 +0000
Subject: [PATCH 119/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 7e56fe29..e2f2c074 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.16"
+  ".": "0.1.0-alpha.17"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 3b48a5bb..24f75152 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.16"
+version = "0.1.0-alpha.17"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index b99310c3..264d49e0 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.16"  # x-release-please-version
+__version__ = "0.1.0-alpha.17"  # x-release-please-version

From d96e28e8ff2fd2b72fb5121c4cb814cfa21e15ee Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 20:16:28 +0000
Subject: [PATCH 120/227] feat(api): api update

---
 .stats.yml                                             | 2 +-
 src/codex/resources/projects/projects.py               | 8 --------
 src/codex/types/project_retrieve_analytics_params.py   | 3 ---
 src/codex/types/project_retrieve_analytics_response.py | 9 ++++++++-
 tests/api_resources/test_projects.py                   | 2 --
 5 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 230d2377..9e3b9924 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7
+openapi_spec_hash: 1d2bab7a30685ae6ba2f0a98abf2dd54
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 6ac036c0..bd50a684 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -365,7 +365,6 @@ def retrieve_analytics(
         project_id: str,
         *,
         end: int | NotGiven = NOT_GIVEN,
-        sme_limit: int | NotGiven = NOT_GIVEN,
         start: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -380,8 +379,6 @@ def retrieve_analytics(
         Args:
           end: End timestamp in seconds since epoch
 
-          sme_limit: Limit the number of top SME contributors to return.
-
           start: Start timestamp in seconds since epoch
 
           extra_headers: Send extra headers
@@ -404,7 +401,6 @@ def retrieve_analytics(
                 query=maybe_transform(
                     {
                         "end": end,
-                        "sme_limit": sme_limit,
                         "start": start,
                     },
                     project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams,
@@ -723,7 +719,6 @@ async def retrieve_analytics(
         project_id: str,
         *,
         end: int | NotGiven = NOT_GIVEN,
-        sme_limit: int | NotGiven = NOT_GIVEN,
         start: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -738,8 +733,6 @@ async def retrieve_analytics(
         Args:
           end: End timestamp in seconds since epoch
 
-          sme_limit: Limit the number of top SME contributors to return.
-
           start: Start timestamp in seconds since epoch
 
           extra_headers: Send extra headers
@@ -762,7 +755,6 @@ async def retrieve_analytics(
                 query=await async_maybe_transform(
                     {
                         "end": end,
-                        "sme_limit": sme_limit,
                         "start": start,
                     },
                     project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams,
diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py
index aadb4159..c5f9a48c 100644
--- a/src/codex/types/project_retrieve_analytics_params.py
+++ b/src/codex/types/project_retrieve_analytics_params.py
@@ -11,8 +11,5 @@ class ProjectRetrieveAnalyticsParams(TypedDict, total=False):
     end: int
     """End timestamp in seconds since epoch"""
 
-    sme_limit: int
-    """Limit the number of top SME contributors to return."""
-
     start: int
     """Start timestamp in seconds since epoch"""
diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py
index f17975ec..dce14505 100644
--- a/src/codex/types/project_retrieve_analytics_response.py
+++ b/src/codex/types/project_retrieve_analytics_response.py
@@ -9,6 +9,7 @@
     "AnswersPublished",
     "AnswersPublishedAnswersByAuthor",
     "BadResponses",
+    "BadResponsesResponsesByType",
     "Queries",
 ]
 
@@ -25,8 +26,14 @@ class AnswersPublished(BaseModel):
     answers_by_author: List[AnswersPublishedAnswersByAuthor]
 
 
+class BadResponsesResponsesByType(BaseModel):
+    num_prevented: int
+
+    total: int
+
+
 class BadResponses(BaseModel):
-    responses_by_type: Dict[str, int]
+    responses_by_type: Dict[str, BadResponsesResponsesByType]
 
     total: int
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 3a8f0ec8..772a7b29 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -374,7 +374,6 @@ def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None:
         project = client.projects.retrieve_analytics(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             end=0,
-            sme_limit=1,
             start=0,
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
@@ -769,7 +768,6 @@ async def test_method_retrieve_analytics_with_all_params(self, async_client: Asy
         project = await async_client.projects.retrieve_analytics(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             end=0,
-            sme_limit=1,
             start=0,
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])

From e255acd5c4777cb488aa63b7599d1a3f34459018 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:12:17 +0000
Subject: [PATCH 121/227] chore(internal): minor formatting changes

---
 src/codex/types/health_check_response.py                         | 1 -
 .../organizations/billing/organization_billing_setup_intent.py   | 1 -
 .../types/organizations/organization_billing_invoices_schema.py  | 1 -
 .../types/organizations/organization_billing_usage_schema.py     | 1 -
 .../types/projects/access_key_retrieve_project_id_response.py    | 1 -
 src/codex/types/projects/entry_notify_sme_response.py            | 1 -
 6 files changed, 6 deletions(-)

diff --git a/src/codex/types/health_check_response.py b/src/codex/types/health_check_response.py
index 7a2655d3..b3b7958a 100644
--- a/src/codex/types/health_check_response.py
+++ b/src/codex/types/health_check_response.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from .._models import BaseModel
 
 __all__ = ["HealthCheckResponse"]
diff --git a/src/codex/types/organizations/billing/organization_billing_setup_intent.py b/src/codex/types/organizations/billing/organization_billing_setup_intent.py
index dfd07d6b..25b3f553 100644
--- a/src/codex/types/organizations/billing/organization_billing_setup_intent.py
+++ b/src/codex/types/organizations/billing/organization_billing_setup_intent.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from ...._models import BaseModel
 
 __all__ = ["OrganizationBillingSetupIntent"]
diff --git a/src/codex/types/organizations/organization_billing_invoices_schema.py b/src/codex/types/organizations/organization_billing_invoices_schema.py
index e588ded0..64b2959a 100644
--- a/src/codex/types/organizations/organization_billing_invoices_schema.py
+++ b/src/codex/types/organizations/organization_billing_invoices_schema.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from ..._models import BaseModel
 
 __all__ = ["OrganizationBillingInvoicesSchema"]
diff --git a/src/codex/types/organizations/organization_billing_usage_schema.py b/src/codex/types/organizations/organization_billing_usage_schema.py
index 8369fb31..05129502 100644
--- a/src/codex/types/organizations/organization_billing_usage_schema.py
+++ b/src/codex/types/organizations/organization_billing_usage_schema.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from ..._models import BaseModel
 
 __all__ = ["OrganizationBillingUsageSchema"]
diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py
index 22775083..e204ccbc 100644
--- a/src/codex/types/projects/access_key_retrieve_project_id_response.py
+++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from ..._models import BaseModel
 
 __all__ = ["AccessKeyRetrieveProjectIDResponse"]
diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py
index dd05a6cf..b3c5b373 100644
--- a/src/codex/types/projects/entry_notify_sme_response.py
+++ b/src/codex/types/projects/entry_notify_sme_response.py
@@ -1,6 +1,5 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-
 from ..._models import BaseModel
 
 __all__ = ["EntryNotifySmeResponse"]

From a3af8c3cb52de0a24a89f7908597d430381051bb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:12:45 +0000
Subject: [PATCH 122/227] chore(internal): codegen related update

---
 .github/workflows/ci.yml             | 14 +++++++-------
 .github/workflows/publish-pypi.yml   |  2 +-
 .github/workflows/release-doctor.yml |  2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e4dab9d..cab1a968 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,18 @@
 name: CI
 on:
   push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-      - next
+    branches-ignore:
+      - 'generated'
+      - 'codegen/**'
+      - 'integrated/**'
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
 
 jobs:
   lint:
     timeout-minutes: 10
     name: lint
-    runs-on: ubuntu-latest
+    runs-on: depot-ubuntu-24.04
     steps:
       - uses: actions/checkout@v4
 
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 7c078aa3..6ae114ba 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -11,7 +11,7 @@ on:
 jobs:
   publish:
     name: publish
-    runs-on: ubuntu-latest
+    runs-on: depot-ubuntu-24.04
 
     steps:
       - uses: actions/checkout@v4
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 2ce3cdc2..1c7b27dc 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   release_doctor:
     name: release doctor
-    runs-on: ubuntu-latest
+    runs-on: depot-ubuntu-24.04
     if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
 
     steps:

From 0bf5b7c001b32b7f446fe72a3d36cdae877758f0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:13:10 +0000
Subject: [PATCH 123/227] chore(ci): only use depot for staging repos

---
 .github/workflows/ci.yml             | 2 +-
 .github/workflows/publish-pypi.yml   | 2 +-
 .github/workflows/release-doctor.yml | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cab1a968..fc62f784 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
   lint:
     timeout-minutes: 10
     name: lint
-    runs-on: depot-ubuntu-24.04
+    runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
     steps:
       - uses: actions/checkout@v4
 
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 6ae114ba..7c078aa3 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -11,7 +11,7 @@ on:
 jobs:
   publish:
     name: publish
-    runs-on: depot-ubuntu-24.04
+    runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v4
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 1c7b27dc..2ce3cdc2 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   release_doctor:
     name: release doctor
-    runs-on: depot-ubuntu-24.04
+    runs-on: ubuntu-latest
     if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
 
     steps:

From e6a7b74993a02b7ced3bbb10a44a44f59a68bcca Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:14:20 +0000
Subject: [PATCH 124/227] chore: broadly detect json family of content-type
 headers

---
 src/codex/_response.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codex/_response.py b/src/codex/_response.py
index 6e7c1cdf..e4ddf703 100644
--- a/src/codex/_response.py
+++ b/src/codex/_response.py
@@ -233,7 +233,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
         # split is required to handle cases where additional information is included
         # in the response, e.g. application/json; charset=utf-8
         content_type, *_ = response.headers.get("content-type", "*").split(";")
-        if content_type != "application/json":
+        if not content_type.endswith("json"):
             if is_basemodel(cast_to):
                 try:
                     data = response.json()

From 32906a582d4be4c659d4d6cb183e1aa9218f4c73 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 14:16:27 +0000
Subject: [PATCH 125/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/clusters.py      | 28 +++++++++++-
 .../project_retrieve_analytics_response.py    |  2 +
 .../types/projects/cluster_list_params.py     | 14 +++++-
 .../types/projects/cluster_list_response.py   | 44 +++++++++++++++++++
 src/codex/types/projects/entry.py             | 44 +++++++++++++++++++
 .../types/projects/entry_query_response.py    | 40 +++++++++++++++++
 tests/api_resources/projects/test_clusters.py |  2 +
 8 files changed, 172 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 9e3b9924..7a2f56a6 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: 1d2bab7a30685ae6ba2f0a98abf2dd54
+openapi_spec_hash: b7beefbd38b4fcdd191cdb81a18a023b
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 584cde18..fea7e28e 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -52,10 +52,21 @@ def list(
         *,
         eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
         | NotGiven = NOT_GIVEN,
+        instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
+        sort: Optional[
+            Literal[
+                "created_at",
+                "answered_at",
+                "cluster_frequency_count",
+                "custom_rank",
+                "eval_score",
+                "html_format_score",
+                "content_structure_score",
+            ]
+        ]
         | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -90,6 +101,7 @@ def list(
                 query=maybe_transform(
                     {
                         "eval_issue_types": eval_issue_types,
+                        "instruction_adherence_failure": instruction_adherence_failure,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
@@ -167,10 +179,21 @@ def list(
         *,
         eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
         | NotGiven = NOT_GIVEN,
+        instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
+        sort: Optional[
+            Literal[
+                "created_at",
+                "answered_at",
+                "cluster_frequency_count",
+                "custom_rank",
+                "eval_score",
+                "html_format_score",
+                "content_structure_score",
+            ]
+        ]
         | NotGiven = NOT_GIVEN,
         states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -205,6 +228,7 @@ def list(
                 query=maybe_transform(
                     {
                         "eval_issue_types": eval_issue_types,
+                        "instruction_adherence_failure": instruction_adherence_failure,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py
index dce14505..b1e5d85e 100644
--- a/src/codex/types/project_retrieve_analytics_response.py
+++ b/src/codex/types/project_retrieve_analytics_response.py
@@ -17,6 +17,8 @@
 class AnswersPublishedAnswersByAuthor(BaseModel):
     answers_published: int
 
+    email: str
+
     name: str
 
     user_id: str
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index fa84a6b0..cff2b85e 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -11,12 +11,24 @@
 class ClusterListParams(TypedDict, total=False):
     eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
 
+    instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]]
+
     limit: int
 
     offset: int
 
     order: Literal["asc", "desc"]
 
-    sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]]
+    sort: Optional[
+        Literal[
+            "created_at",
+            "answered_at",
+            "cluster_frequency_count",
+            "custom_rank",
+            "eval_score",
+            "html_format_score",
+            "content_structure_score",
+        ]
+    ]
 
     states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index c149fbdb..2e8b5426 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -9,13 +9,31 @@
 __all__ = [
     "ClusterListResponse",
     "ManagedMetadata",
+    "ManagedMetadataContentStructureScores",
     "ManagedMetadataContextSufficiency",
+    "ManagedMetadataHTMLFormatScores",
     "ManagedMetadataQueryEaseCustomized",
     "ManagedMetadataResponseHelpfulness",
     "ManagedMetadataTrustworthiness",
 ]
 
 
+class ManagedMetadataContentStructureScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataContextSufficiency(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -32,6 +50,22 @@ class ManagedMetadataContextSufficiency(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class ManagedMetadataHTMLFormatScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataQueryEaseCustomized(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -93,6 +127,9 @@ class ManagedMetadata(BaseModel):
     latest_location: Optional[str] = None
     """The most recent location string."""
 
+    content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
     """Holds a list of scores and computes aggregate statistics."""
 
@@ -100,6 +137,9 @@ class ManagedMetadata(BaseModel):
 
     entry_points: Optional[List[str]] = None
 
+    html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     llm_responses: Optional[List[str]] = None
 
     locations: Optional[List[str]] = None
@@ -136,6 +176,8 @@ class ClusterListResponse(BaseModel):
 
     client_query_metadata: Optional[List[object]] = None
 
+    content_structure_score: Optional[float] = None
+
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
@@ -147,4 +189,6 @@ class ClusterListResponse(BaseModel):
     frequency_count: Optional[int] = None
     """number of times the entry matched for a /query request"""
 
+    html_format_score: Optional[float] = None
+
     representative_entry_id: Optional[str] = None
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 638fe352..eb2a2217 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -9,13 +9,31 @@
 __all__ = [
     "Entry",
     "ManagedMetadata",
+    "ManagedMetadataContentStructureScores",
     "ManagedMetadataContextSufficiency",
+    "ManagedMetadataHTMLFormatScores",
     "ManagedMetadataQueryEaseCustomized",
     "ManagedMetadataResponseHelpfulness",
     "ManagedMetadataTrustworthiness",
 ]
 
 
+class ManagedMetadataContentStructureScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataContextSufficiency(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -32,6 +50,22 @@ class ManagedMetadataContextSufficiency(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class ManagedMetadataHTMLFormatScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataQueryEaseCustomized(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -93,6 +127,9 @@ class ManagedMetadata(BaseModel):
     latest_location: Optional[str] = None
     """The most recent location string."""
 
+    content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
     """Holds a list of scores and computes aggregate statistics."""
 
@@ -100,6 +137,9 @@ class ManagedMetadata(BaseModel):
 
     entry_points: Optional[List[str]] = None
 
+    html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     llm_responses: Optional[List[str]] = None
 
     locations: Optional[List[str]] = None
@@ -134,6 +174,8 @@ class Entry(BaseModel):
 
     client_query_metadata: Optional[List[object]] = None
 
+    content_structure_score: Optional[float] = None
+
     draft_answer: Optional[str] = None
 
     draft_answer_last_edited: Optional[datetime] = None
@@ -144,3 +186,5 @@ class Entry(BaseModel):
 
     frequency_count: Optional[int] = None
     """number of times the entry matched for a /query request"""
+
+    html_format_score: Optional[float] = None
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
index 26db1d83..318636b9 100644
--- a/src/codex/types/projects/entry_query_response.py
+++ b/src/codex/types/projects/entry_query_response.py
@@ -8,13 +8,31 @@
     "EntryQueryResponse",
     "Entry",
     "EntryManagedMetadata",
+    "EntryManagedMetadataContentStructureScores",
     "EntryManagedMetadataContextSufficiency",
+    "EntryManagedMetadataHTMLFormatScores",
     "EntryManagedMetadataQueryEaseCustomized",
     "EntryManagedMetadataResponseHelpfulness",
     "EntryManagedMetadataTrustworthiness",
 ]
 
 
+class EntryManagedMetadataContentStructureScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class EntryManagedMetadataContextSufficiency(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -31,6 +49,22 @@ class EntryManagedMetadataContextSufficiency(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class EntryManagedMetadataHTMLFormatScores(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class EntryManagedMetadataQueryEaseCustomized(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -92,6 +126,9 @@ class EntryManagedMetadata(BaseModel):
     latest_location: Optional[str] = None
     """The most recent location string."""
 
+    content_structure_scores: Optional[EntryManagedMetadataContentStructureScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None
     """Holds a list of scores and computes aggregate statistics."""
 
@@ -99,6 +136,9 @@ class EntryManagedMetadata(BaseModel):
 
     entry_points: Optional[List[str]] = None
 
+    html_format_scores: Optional[EntryManagedMetadataHTMLFormatScores] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     llm_responses: Optional[List[str]] = None
 
     locations: Optional[List[str]] = None
diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py
index 53706c6c..496236f8 100644
--- a/tests/api_resources/projects/test_clusters.py
+++ b/tests/api_resources/projects/test_clusters.py
@@ -32,6 +32,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         cluster = client.projects.clusters.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             eval_issue_types=["hallucination"],
+            instruction_adherence_failure="html_format",
             limit=1,
             offset=0,
             order="asc",
@@ -146,6 +147,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         cluster = await async_client.projects.clusters.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             eval_issue_types=["hallucination"],
+            instruction_adherence_failure="html_format",
             limit=1,
             offset=0,
             order="asc",

From f08afc157ddc4481ab2ec37b6f03fb2ebd7b5b33 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 23:16:35 +0000
Subject: [PATCH 126/227] feat(api): api update

---
 .release-please-manifest.json                 |  2 +-
 .stats.yml                                    |  2 +-
 api.md                                        |  2 +-
 pyproject.toml                                |  2 +-
 src/codex/_version.py                         |  2 +-
 src/codex/resources/projects/projects.py      | 17 ++++++++--
 src/codex/types/__init__.py                   |  1 +
 .../types/project_increment_queries_params.py | 11 ++++++
 tests/api_resources/test_projects.py          | 34 ++++++++++++++-----
 9 files changed, 58 insertions(+), 15 deletions(-)
 create mode 100644 src/codex/types/project_increment_queries_params.py

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index e2f2c074..3cf71e62 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.17"
+  ".": "0.1.0-alpha.18"
 }
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 7a2f56a6..4a4a129e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: b7beefbd38b4fcdd191cdb81a18a023b
+openapi_spec_hash: 51dd9bdb04307116617d3eefe3237755
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/api.md b/api.md
index f240632f..3bd2cf4f 100644
--- a/api.md
+++ b/api.md
@@ -153,7 +153,7 @@ Methods:
 - <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
-- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
+- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
 - <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
 
 ## AccessKeys
diff --git a/pyproject.toml b/pyproject.toml
index 24f75152..ff2faf37 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.17"
+version = "0.1.0-alpha.18"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 264d49e0..29c60372 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.17"  # x-release-please-version
+__version__ = "0.1.0-alpha.18"  # x-release-please-version
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index bd50a684..7c676b6d 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -11,6 +11,7 @@
     project_list_params,
     project_create_params,
     project_update_params,
+    project_increment_queries_params,
     project_retrieve_analytics_params,
 )
 from .entries import (
@@ -331,6 +332,7 @@ def increment_queries(
         self,
         project_id: str,
         *,
+        count: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -355,7 +357,11 @@ def increment_queries(
         return self._post(
             f"/api/projects/{project_id}/increment_queries",
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform({"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams),
             ),
             cast_to=object,
         )
@@ -685,6 +691,7 @@ async def increment_queries(
         self,
         project_id: str,
         *,
+        count: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -709,7 +716,13 @@ async def increment_queries(
         return await self._post(
             f"/api/projects/{project_id}/increment_queries",
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams
+                ),
             ),
             cast_to=object,
         )
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 53d1ab6b..7f18b9c1 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -15,6 +15,7 @@
 from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
+from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams
 from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams
 from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse
 from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse
diff --git a/src/codex/types/project_increment_queries_params.py b/src/codex/types/project_increment_queries_params.py
new file mode 100644
index 00000000..f6043a76
--- /dev/null
+++ b/src/codex/types/project_increment_queries_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectIncrementQueriesParams"]
+
+
+class ProjectIncrementQueriesParams(TypedDict, total=False):
+    count: int
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 772a7b29..40f40eb7 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -322,7 +322,16 @@ def test_path_params_export(self, client: Codex) -> None:
     @parametrize
     def test_method_increment_queries(self, client: Codex) -> None:
         project = client.projects.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_increment_queries_with_all_params(self, client: Codex) -> None:
+        project = client.projects.increment_queries(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            count=0,
         )
         assert_matches_type(object, project, path=["response"])
 
@@ -330,7 +339,7 @@ def test_method_increment_queries(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_increment_queries(self, client: Codex) -> None:
         response = client.projects.with_raw_response.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -342,7 +351,7 @@ def test_raw_response_increment_queries(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_increment_queries(self, client: Codex) -> None:
         with client.projects.with_streaming_response.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -357,7 +366,7 @@ def test_streaming_response_increment_queries(self, client: Codex) -> None:
     def test_path_params_increment_queries(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             client.projects.with_raw_response.increment_queries(
-                "",
+                project_id="",
             )
 
     @pytest.mark.skip()
@@ -716,7 +725,16 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_increment_queries(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(object, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.increment_queries(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            count=0,
         )
         assert_matches_type(object, project, path=["response"])
 
@@ -724,7 +742,7 @@ async def test_method_increment_queries(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -736,7 +754,7 @@ async def test_raw_response_increment_queries(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.increment_queries(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -751,7 +769,7 @@ async def test_streaming_response_increment_queries(self, async_client: AsyncCod
     async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             await async_client.projects.with_raw_response.increment_queries(
-                "",
+                project_id="",
             )
 
     @pytest.mark.skip()

From fdb4753fc81b3fb06e2f16034f2734a4e2f3af9c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 19:16:21 +0000
Subject: [PATCH 127/227] feat(api): api update

---
 .stats.yml                                  | 2 +-
 src/codex/types/users/user_schema.py        | 2 ++
 src/codex/types/users/user_schema_public.py | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 4a4a129e..4fd111cc 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: 51dd9bdb04307116617d3eefe3237755
+openapi_spec_hash: 6d2d01f4951c677a47cffe973084413e
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py
index b1665f21..d22c9563 100644
--- a/src/codex/types/users/user_schema.py
+++ b/src/codex/types/users/user_schema.py
@@ -23,6 +23,8 @@ class UserSchema(BaseModel):
 
     email: str
 
+    email_verified: bool
+
     updated_at: datetime
 
     user_provided_company_name: Optional[str] = None
diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py
index 181113b0..d5e1d9bf 100644
--- a/src/codex/types/users/user_schema_public.py
+++ b/src/codex/types/users/user_schema_public.py
@@ -14,7 +14,7 @@ class UserSchemaPublic(BaseModel):
 
     email: str
 
-    email_verified: Optional[bool] = None
+    email_verified: bool
 
     first_name: Optional[str] = None
 

From f402cad3cff3175741531cd63498e38c8d54d601 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 14:17:04 +0000
Subject: [PATCH 128/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/entries.py       | 12 ++++++
 .../types/projects/entry_query_params.py      | 39 ++++++++++++++++++-
 tests/api_resources/projects/test_entries.py  | 12 ++++++
 4 files changed, 62 insertions(+), 3 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 4fd111cc..a1b247c4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: 6d2d01f4951c677a47cffe973084413e
+openapi_spec_hash: 3873591605b529e6ae298fc7f04d4ba1
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index a9e690b9..346dd353 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -319,6 +319,7 @@ def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -334,6 +335,10 @@ def query(
         Query Entries Route
 
         Args:
+          client_metadata: Deprecated: Use query_metadata instead
+
+          query_metadata: Optional logging data that can be provided by the client.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -361,6 +366,7 @@ def query(
                 {
                     "question": question,
                     "client_metadata": client_metadata,
+                    "query_metadata": query_metadata,
                 },
                 entry_query_params.EntryQueryParams,
             ),
@@ -708,6 +714,7 @@ async def query(
         question: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -723,6 +730,10 @@ async def query(
         Query Entries Route
 
         Args:
+          client_metadata: Deprecated: Use query_metadata instead
+
+          query_metadata: Optional logging data that can be provided by the client.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -750,6 +761,7 @@ async def query(
                 {
                     "question": question,
                     "client_metadata": client_metadata,
+                    "query_metadata": query_metadata,
                 },
                 entry_query_params.EntryQueryParams,
             ),
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index d58b7bfa..1edabbed 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -2,12 +2,12 @@
 
 from __future__ import annotations
 
-from typing import Optional
+from typing import Dict, List, Union, Iterable, Optional
 from typing_extensions import Required, Annotated, TypedDict
 
 from ..._utils import PropertyInfo
 
-__all__ = ["EntryQueryParams"]
+__all__ = ["EntryQueryParams", "QueryMetadata", "QueryMetadataContextUnionMember3"]
 
 
 class EntryQueryParams(TypedDict, total=False):
@@ -16,6 +16,10 @@ class EntryQueryParams(TypedDict, total=False):
     use_llm_matching: bool
 
     client_metadata: Optional[object]
+    """Deprecated: Use query_metadata instead"""
+
+    query_metadata: Optional[QueryMetadata]
+    """Optional logging data that can be provided by the client."""
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
@@ -24,3 +28,34 @@ class EntryQueryParams(TypedDict, total=False):
     x_source: Annotated[str, PropertyInfo(alias="x-source")]
 
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
+
+
+class QueryMetadataContextUnionMember3(TypedDict, total=False):
+    content: Required[str]
+    """The actual content/text of the document."""
+
+    id: Optional[str]
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str]
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]]
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str]
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryMetadata(TypedDict, total=False):
+    context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None]
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object]
+    """Arbitrary metadata supplied by the user/system"""
+
+    eval_scores: Optional[Dict[str, float]]
+    """Evaluation scores for the original response"""
+
+    evaluated_response: Optional[str]
+    """The response being evaluated from the RAG system(before any remediation)"""
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 31a5e408..73a45ad4 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -396,6 +396,12 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
             question="question",
             use_llm_matching=True,
             client_metadata={},
+            query_metadata={
+                "context": "string",
+                "custom_metadata": {},
+                "eval_scores": {"foo": 0},
+                "evaluated_response": "evaluated_response",
+            },
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -871,6 +877,12 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
             question="question",
             use_llm_matching=True,
             client_metadata={},
+            query_metadata={
+                "context": "string",
+                "custom_metadata": {},
+                "eval_scores": {"foo": 0},
+                "evaluated_response": "evaluated_response",
+            },
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",

From 9a1695b09bea5438bf73eb151927d845e5c66d5d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 5 May 2025 18:16:50 +0000
Subject: [PATCH 129/227] feat(api): api update

---
 .stats.yml                           |   2 +-
 src/codex/resources/tlm.py           | 272 ++++++++++++++-------------
 src/codex/types/tlm_prompt_params.py |  67 +++----
 src/codex/types/tlm_score_params.py  |  67 +++----
 4 files changed, 215 insertions(+), 193 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index a1b247c4..bcd9e36c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: 3873591605b529e6ae298fc7f04d4ba1
+openapi_spec_hash: f0d588a39e2040ff516a5cff26c4ab58
 config_hash: 5e459b33c53ffa6e554087a779bdb790
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index 78f97e2e..5e4cd7e1 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -97,59 +97,63 @@ def prompt(
               You can set custom values for these arguments regardless of the quality preset
               specified.
 
-              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
               "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
               "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
               "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o1",
-              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-              "gpt-4o-mini".
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
                   If you experience token/rate limit errors while using TLM, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-                  This parameter must be between 1 and 20.
-                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-                  This consistency helps quantify the epistemic uncertainty associated with
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-                  generated and self-evaluate this response.
-                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches answers that are obviously incorrect/bad.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-                  similarity between sampled responses considered by the model in the consistency assessment.
-                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-                  Set this to "string" to improve latency/costs.
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-                  when considering alternative possible responses and double-checking responses.
-                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
                   log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
                   For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
                   The expected input format is a list of dictionaries, where each dictionary has the following keys:
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -234,59 +238,63 @@ def score(
               You can set custom values for these arguments regardless of the quality preset
               specified.
 
-              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
               "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
               "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
               "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o1",
-              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-              "gpt-4o-mini".
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
                   If you experience token/rate limit errors while using TLM, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-                  This parameter must be between 1 and 20.
-                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-                  This consistency helps quantify the epistemic uncertainty associated with
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-                  generated and self-evaluate this response.
-                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches answers that are obviously incorrect/bad.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-                  similarity between sampled responses considered by the model in the consistency assessment.
-                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-                  Set this to "string" to improve latency/costs.
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-                  when considering alternative possible responses and double-checking responses.
-                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
                   log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
                   For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
                   The expected input format is a list of dictionaries, where each dictionary has the following keys:
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -387,59 +395,63 @@ async def prompt(
               You can set custom values for these arguments regardless of the quality preset
               specified.
 
-              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
               "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
               "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
               "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o1",
-              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-              "gpt-4o-mini".
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
                   If you experience token/rate limit errors while using TLM, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-                  This parameter must be between 1 and 20.
-                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-                  This consistency helps quantify the epistemic uncertainty associated with
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-                  generated and self-evaluate this response.
-                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches answers that are obviously incorrect/bad.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-                  similarity between sampled responses considered by the model in the consistency assessment.
-                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-                  Set this to "string" to improve latency/costs.
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-                  when considering alternative possible responses and double-checking responses.
-                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
                   log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
                   For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
                   The expected input format is a list of dictionaries, where each dictionary has the following keys:
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -524,59 +536,63 @@ async def score(
               You can set custom values for these arguments regardless of the quality preset
               specified.
 
-              Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-              "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
               "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
               "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
               "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o1",
-              "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-              "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-              "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-              "gpt-4o-mini".
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
                   If you experience token/rate limit errors while using TLM, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-                  TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-                  This parameter must be between 1 and 20.
-                  When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-                  This consistency helps quantify the epistemic uncertainty associated with
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-                  TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-                  generated and self-evaluate this response.
-                  Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-                  Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches answers that are obviously incorrect/bad.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-                  similarity between sampled responses considered by the model in the consistency assessment.
-                  Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-                  "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-                  Set this to "string" to improve latency/costs.
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-                  when considering alternative possible responses and double-checking responses.
-                  Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
                   log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
                   For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
                   The expected input format is a list of dictionaries, where each dictionary has the following keys:
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 860f1a77..94536055 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -48,61 +48,64 @@ class TlmPromptParams(TypedDict, total=False):
     You can set custom values for these arguments regardless of the quality preset
     specified.
 
-    Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-    "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
     "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
     "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
     "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-    faster models yield faster/cheaper results). - Models still in beta: "o1",
-    "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-    "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-    "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-    "gpt-4o-mini".
+    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+    "gpt-4.1-nano", "nova-micro".
 
         max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
         If you experience token/rate limit errors while using TLM, try lowering this number.
         For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-        TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-        Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-        This parameter must be between 1 and 20.
-        When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-        num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-        This consistency helps quantify the epistemic uncertainty associated with
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        Measuring consistency helps quantify the epistemic uncertainty associated with
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-        TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+        TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-        generated and self-evaluate this response.
-        Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-        Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches answers that are obviously incorrect/bad.
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-        similarity between sampled responses considered by the model in the consistency assessment.
-        Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-        "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-        Set this to "string" to improve latency/costs.
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+        Supported similarity measures include: "semantic" (based on natural language inference),
+        "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+        "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-        when considering alternative possible responses and double-checking responses.
-        Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
         log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
         For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
+    """The quality preset to use for the TLM or Trustworthy RAG API."""
 
     task: Optional[str]
 
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index 213da422..a0d90175 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -50,61 +50,64 @@ class TlmScoreParams(TypedDict, total=False):
     You can set custom values for these arguments regardless of the quality preset
     specified.
 
-    Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview",
-    "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet",
+    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
     "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
     "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
     "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-    faster models yield faster/cheaper results). - Models still in beta: "o1",
-    "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite",
-    "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1",
-    "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro",
-    "gpt-4o-mini".
+    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+    "gpt-4.1-nano", "nova-micro".
 
         max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes.
+        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
         If you experience token/rate limit errors while using TLM, try lowering this number.
         For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM.
-        TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-        Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens).
-        This parameter must be between 1 and 20.
-        When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it.
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
 
-        num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency.
-        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes.
-        This consistency helps quantify the epistemic uncertainty associated with
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        Measuring consistency helps quantify the epistemic uncertainty associated with
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
-        TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible.
+        TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it
-        generated and self-evaluate this response.
-        Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes.
-        Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches answers that are obviously incorrect/bad.
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures
-        similarity between sampled responses considered by the model in the consistency assessment.
-        Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap),
-        "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model).
-        Set this to "string" to improve latency/costs.
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+        Supported similarity measures include: "semantic" (based on natural language inference),
+        "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+        "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
 
-        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens)
-        when considering alternative possible responses and double-checking responses.
-        Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs.
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
 
         log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
         For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
 
-        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria.
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
+    """The quality preset to use for the TLM or Trustworthy RAG API."""
 
     task: Optional[str]
 

From a5e9f6ecc3ba04b17238fa822be1f14dc96c5bbf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 7 May 2025 17:16:56 +0000
Subject: [PATCH 130/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index bcd9e36c..d087a84d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 43
-openapi_spec_hash: f0d588a39e2040ff516a5cff26c4ab58
+openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd
 config_hash: 5e459b33c53ffa6e554087a779bdb790

From a5bbebe9f374a323ea3cb6d48b8a8e320de43a5c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 7 May 2025 17:36:40 +0000
Subject: [PATCH 131/227] feat(api): add validate endpoint

---
 .stats.yml                                   |   4 +-
 api.md                                       |   2 +
 src/codex/resources/projects/projects.py     | 378 ++++++++++++++++++-
 src/codex/types/__init__.py                  |   2 +
 src/codex/types/project_validate_params.py   | 169 +++++++++
 src/codex/types/project_validate_response.py |  36 ++
 tests/api_resources/test_projects.py         | 195 ++++++++++
 7 files changed, 782 insertions(+), 4 deletions(-)
 create mode 100644 src/codex/types/project_validate_params.py
 create mode 100644 src/codex/types/project_validate_response.py

diff --git a/.stats.yml b/.stats.yml
index d087a84d..f01e1b9f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 43
+configured_endpoints: 44
 openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd
-config_hash: 5e459b33c53ffa6e554087a779bdb790
+config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/api.md b/api.md
index 3bd2cf4f..f3a2ea14 100644
--- a/api.md
+++ b/api.md
@@ -142,6 +142,7 @@ from codex.types import (
     ProjectExportResponse,
     ProjectIncrementQueriesResponse,
     ProjectRetrieveAnalyticsResponse,
+    ProjectValidateResponse,
 )
 ```
 
@@ -155,6 +156,7 @@ Methods:
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
 - <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
 - <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
+- <code title="post /api/projects/{project_id}/validate">client.projects.<a href="./src/codex/resources/projects/projects.py">validate</a>(project_id, \*\*<a href="src/codex/types/project_validate_params.py">params</a>) -> <a href="./src/codex/types/project_validate_response.py">ProjectValidateResponse</a></code>
 
 ## AccessKeys
 
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 7c676b6d..b8fbaf7e 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from typing import Optional
+from typing import Dict, List, Optional
 from typing_extensions import Literal
 
 import httpx
@@ -11,6 +11,7 @@
     project_list_params,
     project_create_params,
     project_update_params,
+    project_validate_params,
     project_increment_queries_params,
     project_retrieve_analytics_params,
 )
@@ -23,7 +24,7 @@
     AsyncEntriesResourceWithStreamingResponse,
 )
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import maybe_transform, async_maybe_transform
+from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
 from .clusters import (
     ClustersResource,
     AsyncClustersResource,
@@ -52,6 +53,7 @@
 from ...types.project_list_response import ProjectListResponse
 from ...types.project_return_schema import ProjectReturnSchema
 from ...types.project_retrieve_response import ProjectRetrieveResponse
+from ...types.project_validate_response import ProjectValidateResponse
 from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse
 
 __all__ = ["ProjectsResource", "AsyncProjectsResource"]
@@ -415,6 +417,186 @@ def retrieve_analytics(
             cast_to=ProjectRetrieveAnalyticsResponse,
         )
 
+    def validate(
+        self,
+        project_id: str,
+        *,
+        context: str,
+        prompt: str,
+        query: str,
+        response: str,
+        use_llm_matching: bool | NotGiven = NOT_GIVEN,
+        bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
+        options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectValidateResponse:
+        """
+        Evaluate whether a response, given the provided query and context, is
+        potentially bad. If the response is flagged as bad, a lookup is performed to
+        find an alternate expert answer. If there is no expert answer available, this
+        query will be recorded in the project for SMEs to answer.
+
+        Args:
+          custom_metadata: Arbitrary metadata supplied by the user/system
+
+          eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM
+              will be used to generate scores.
+
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
+        return self._post(
+            f"/api/projects/{project_id}/validate",
+            body=maybe_transform(
+                {
+                    "context": context,
+                    "prompt": prompt,
+                    "query": query,
+                    "response": response,
+                    "bad_response_thresholds": bad_response_thresholds,
+                    "constrain_outputs": constrain_outputs,
+                    "custom_metadata": custom_metadata,
+                    "eval_scores": eval_scores,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                project_validate_params.ProjectValidateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {"use_llm_matching": use_llm_matching}, project_validate_params.ProjectValidateParams
+                ),
+            ),
+            cast_to=ProjectValidateResponse,
+        )
+
 
 class AsyncProjectsResource(AsyncAPIResource):
     @cached_property
@@ -776,6 +958,186 @@ async def retrieve_analytics(
             cast_to=ProjectRetrieveAnalyticsResponse,
         )
 
+    async def validate(
+        self,
+        project_id: str,
+        *,
+        context: str,
+        prompt: str,
+        query: str,
+        response: str,
+        use_llm_matching: bool | NotGiven = NOT_GIVEN,
+        bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN,
+        constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
+        eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
+        options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        task: Optional[str] | NotGiven = NOT_GIVEN,
+        x_client_library_version: str | NotGiven = NOT_GIVEN,
+        x_integration_type: str | NotGiven = NOT_GIVEN,
+        x_source: str | NotGiven = NOT_GIVEN,
+        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectValidateResponse:
+        """
+        Evaluate whether a response, given the provided query and context, is
+        potentially bad. If the response is flagged as bad, a lookup is performed to
+        find an alternate expert answer. If there is no expert answer available, this
+        query will be recorded in the project for SMEs to answer.
+
+        Args:
+          custom_metadata: Arbitrary metadata supplied by the user/system
+
+          eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM
+              will be used to generate scores.
+
+          options: Typed dict of advanced configuration options for the Trustworthy Language Model.
+              Many of these configurations are determined by the quality preset selected
+              (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+              Specifying TLMOptions values directly overrides any default values set from the
+              quality preset.
+
+              For all options described below, higher settings will lead to longer runtimes
+              and may consume more tokens internally. You may not be able to run long prompts
+              (or prompts with long responses) in your account, unless your token/rate limits
+              are increased. If you hit token limit issues, try lower/less expensive
+              TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+              increase your limits.
+
+              The default values corresponding to each quality preset are:
+
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a cheaper self-reflection will be used to compute the
+                trustworthiness score.
+
+              By default, the TLM uses the "medium" quality preset. The default base LLM
+              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+              You can set custom values for these arguments regardless of the quality preset
+              specified.
+
+              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+              "gpt-4.1-nano", "nova-micro".
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
+                  If you experience token/rate limit errors while using TLM, try lowering this number.
+                  For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  Measuring consistency helps quantify the epistemic uncertainty associated with
+                  strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+                  TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
+
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
+
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+                  "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
+
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+          quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        extra_headers = {
+            **strip_not_given(
+                {
+                    "x-client-library-version": x_client_library_version,
+                    "x-integration-type": x_integration_type,
+                    "x-source": x_source,
+                    "x-stainless-package-version": x_stainless_package_version,
+                }
+            ),
+            **(extra_headers or {}),
+        }
+        return await self._post(
+            f"/api/projects/{project_id}/validate",
+            body=await async_maybe_transform(
+                {
+                    "context": context,
+                    "prompt": prompt,
+                    "query": query,
+                    "response": response,
+                    "bad_response_thresholds": bad_response_thresholds,
+                    "constrain_outputs": constrain_outputs,
+                    "custom_metadata": custom_metadata,
+                    "eval_scores": eval_scores,
+                    "options": options,
+                    "quality_preset": quality_preset,
+                    "task": task,
+                },
+                project_validate_params.ProjectValidateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {"use_llm_matching": use_llm_matching}, project_validate_params.ProjectValidateParams
+                ),
+            ),
+            cast_to=ProjectValidateResponse,
+        )
+
 
 class ProjectsResourceWithRawResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -805,6 +1167,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.retrieve_analytics = to_raw_response_wrapper(
             projects.retrieve_analytics,
         )
+        self.validate = to_raw_response_wrapper(
+            projects.validate,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
@@ -847,6 +1212,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.retrieve_analytics = async_to_raw_response_wrapper(
             projects.retrieve_analytics,
         )
+        self.validate = async_to_raw_response_wrapper(
+            projects.validate,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
@@ -889,6 +1257,9 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.retrieve_analytics = to_streamed_response_wrapper(
             projects.retrieve_analytics,
         )
+        self.validate = to_streamed_response_wrapper(
+            projects.validate,
+        )
 
     @cached_property
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
@@ -931,6 +1302,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.retrieve_analytics = async_to_streamed_response_wrapper(
             projects.retrieve_analytics,
         )
+        self.validate = async_to_streamed_response_wrapper(
+            projects.validate,
+        )
 
     @cached_property
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 7f18b9c1..8e0cc4a4 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -12,7 +12,9 @@
 from .project_list_response import ProjectListResponse as ProjectListResponse
 from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
 from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .project_validate_params import ProjectValidateParams as ProjectValidateParams
 from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
+from .project_validate_response import ProjectValidateResponse as ProjectValidateResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
 from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
new file mode 100644
index 00000000..f6214cbe
--- /dev/null
+++ b/src/codex/types/project_validate_params.py
@@ -0,0 +1,169 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, List, Iterable, Optional
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ProjectValidateParams", "BadResponseThresholds", "Options"]
+
+
+class ProjectValidateParams(TypedDict, total=False):
+    context: Required[str]
+
+    prompt: Required[str]
+
+    query: Required[str]
+
+    response: Required[str]
+
+    use_llm_matching: bool
+
+    bad_response_thresholds: BadResponseThresholds
+
+    constrain_outputs: Optional[List[str]]
+
+    custom_metadata: Optional[object]
+    """Arbitrary metadata supplied by the user/system"""
+
+    eval_scores: Optional[Dict[str, float]]
+    """Evaluation scores to use for flagging a response as bad.
+
+    If not provided, TLM will be used to generate scores.
+    """
+
+    options: Optional[Options]
+    """
+    Typed dict of advanced configuration options for the Trustworthy Language Model.
+    Many of these configurations are determined by the quality preset selected
+    (learn about quality presets in the TLM [initialization method](./#class-tlm)).
+    Specifying TLMOptions values directly overrides any default values set from the
+    quality preset.
+
+    For all options described below, higher settings will lead to longer runtimes
+    and may consume more tokens internally. You may not be able to run long prompts
+    (or prompts with long responses) in your account, unless your token/rate limits
+    are increased. If you hit token limit issues, try lower/less expensive
+    TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to
+    increase your limits.
+
+    The default values corresponding to each quality preset are:
+
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a cheaper self-reflection will be used to compute the
+      trustworthiness score.
+
+    By default, the TLM uses the "medium" quality preset. The default base LLM
+    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
+    You can set custom values for these arguments regardless of the quality preset
+    specified.
+
+    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
+    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
+    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
+    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
+    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
+    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
+    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
+    "gpt-4.1-nano", "nova-micro".
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
+        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
+        If you experience token/rate limit errors while using TLM, try lowering this number.
+        For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
+
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
+        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
+        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        Measuring consistency helps quantify the epistemic uncertainty associated with
+        strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
+        TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
+
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
+
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
+        Supported similarity measures include: "semantic" (based on natural language inference),
+        "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
+        "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
+
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+    """
+
+    quality_preset: Literal["best", "high", "medium", "low", "base"]
+    """The quality preset to use for the TLM or Trustworthy RAG API."""
+
+    task: Optional[str]
+
+    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
+
+    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
+
+    x_source: Annotated[str, PropertyInfo(alias="x-source")]
+
+    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
+
+
+class BadResponseThresholds(TypedDict, total=False):
+    context_sufficiency: Optional[float]
+
+    query_ease: Optional[float]
+
+    response_helpfulness: Optional[float]
+
+    trustworthiness: Optional[float]
+
+
+class Options(TypedDict, total=False):
+    custom_eval_criteria: Iterable[object]
+
+    log: List[str]
+
+    max_tokens: int
+
+    model: str
+
+    num_candidate_responses: int
+
+    num_consistency_samples: int
+
+    reasoning_effort: str
+
+    similarity_measure: str
+
+    use_self_reflection: bool
diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py
new file mode 100644
index 00000000..e2104360
--- /dev/null
+++ b/src/codex/types/project_validate_response.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from .._models import BaseModel
+
+__all__ = ["ProjectValidateResponse", "EvalScores"]
+
+
+class EvalScores(BaseModel):
+    is_bad: bool
+
+    score: Optional[float] = None
+
+    log: Optional[object] = None
+
+
+class ProjectValidateResponse(BaseModel):
+    eval_scores: Dict[str, EvalScores]
+    """
+    Evaluation scores for the original response along with a boolean flag, `is_bad`,
+    indicating whether the score is below the threshold.
+    """
+
+    expert_answer: Optional[str] = None
+    """
+    Alternate SME-provided answer from Codex if the response was flagged as bad and
+    an answer was found in the Codex Project, or None otherwise.
+    """
+
+    is_bad_response: bool
+    """True if the response is flagged as potentially bad, False otherwise.
+
+    When True, a lookup is performed, which logs this query in the project for SMEs
+    to answer, if it does not already exist.
+    """
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 40f40eb7..f7ca6e01 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -12,6 +12,7 @@
     ProjectListResponse,
     ProjectReturnSchema,
     ProjectRetrieveResponse,
+    ProjectValidateResponse,
     ProjectRetrieveAnalyticsResponse,
 )
 from tests.utils import assert_matches_type
@@ -421,6 +422,103 @@ def test_path_params_retrieve_analytics(self, client: Codex) -> None:
                 project_id="",
             )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_validate(self, client: Codex) -> None:
+        project = client.projects.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        )
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_validate_with_all_params(self, client: Codex) -> None:
+        project = client.projects.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+            use_llm_matching=True,
+            bad_response_thresholds={
+                "context_sufficiency": 0,
+                "query_ease": 0,
+                "response_helpfulness": 0,
+                "trustworthiness": 0,
+            },
+            constrain_outputs=["string"],
+            custom_metadata={},
+            eval_scores={"foo": 0},
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_validate(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_validate(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_validate(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.validate(
+                project_id="",
+                context="context",
+                prompt="prompt",
+                query="query",
+                response="response",
+            )
+
 
 class TestAsyncProjects:
     parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -823,3 +921,100 @@ async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) ->
             await async_client.projects.with_raw_response.retrieve_analytics(
                 project_id="",
             )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_validate(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        )
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+            use_llm_matching=True,
+            bad_response_thresholds={
+                "context_sufficiency": 0,
+                "query_ease": 0,
+                "response_helpfulness": 0,
+                "trustworthiness": 0,
+            },
+            constrain_outputs=["string"],
+            custom_metadata={},
+            eval_scores={"foo": 0},
+            options={
+                "custom_eval_criteria": [{}],
+                "log": ["string"],
+                "max_tokens": 0,
+                "model": "model",
+                "num_candidate_responses": 0,
+                "num_consistency_samples": 0,
+                "reasoning_effort": "reasoning_effort",
+                "similarity_measure": "similarity_measure",
+                "use_self_reflection": True,
+            },
+            quality_preset="best",
+            task="task",
+            x_client_library_version="x-client-library-version",
+            x_integration_type="x-integration-type",
+            x_source="x-source",
+            x_stainless_package_version="x-stainless-package-version",
+        )
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_validate(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.validate(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            context="context",
+            prompt="prompt",
+            query="query",
+            response="response",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectValidateResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.validate(
+                project_id="",
+                context="context",
+                prompt="prompt",
+                query="query",
+                response="response",
+            )

From 59053e68d8fb359f2c65fcd1f460bfebd781ee35 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 7 May 2025 18:36:24 +0000
Subject: [PATCH 132/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3cf71e62..b386befd 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.18"
+  ".": "0.1.0-alpha.19"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index ff2faf37..b288d3ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.18"
+version = "0.1.0-alpha.19"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 29c60372..87d42e64 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.18"  # x-release-please-version
+__version__ = "0.1.0-alpha.19"  # x-release-please-version

From 2dcaf678b95e7fb119409e5b28c138ce280664c3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 8 May 2025 20:16:50 +0000
Subject: [PATCH 133/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index f01e1b9f..f3d66113 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd
+openapi_spec_hash: 3b6849447c1956c43e53ce519056090e
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 9881bd21d15f23b73e02f16177d573656a10d4f3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 9 May 2025 02:44:01 +0000
Subject: [PATCH 134/227] chore(internal): avoid errors for isinstance checks
 on proxies

---
 src/codex/_utils/_proxy.py     |  5 ++++-
 tests/test_utils/test_proxy.py | 11 +++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/codex/_utils/_proxy.py b/src/codex/_utils/_proxy.py
index ffd883e9..0f239a33 100644
--- a/src/codex/_utils/_proxy.py
+++ b/src/codex/_utils/_proxy.py
@@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]:
     @property  # type: ignore
     @override
     def __class__(self) -> type:  # pyright: ignore
-        proxied = self.__get_proxied__()
+        try:
+            proxied = self.__get_proxied__()
+        except Exception:
+            return type(self)
         if issubclass(type(proxied), LazyProxy):
             return type(proxied)
         return proxied.__class__
diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py
index 1277e93e..0a34f4d9 100644
--- a/tests/test_utils/test_proxy.py
+++ b/tests/test_utils/test_proxy.py
@@ -21,3 +21,14 @@ def test_recursive_proxy() -> None:
     assert dir(proxy) == []
     assert type(proxy).__name__ == "RecursiveLazyProxy"
     assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy"
+
+
+def test_isinstance_does_not_error() -> None:
+    class AlwaysErrorProxy(LazyProxy[Any]):
+        @override
+        def __load__(self) -> Any:
+            raise RuntimeError("Mocking missing dependency")
+
+    proxy = AlwaysErrorProxy()
+    assert not isinstance(proxy, dict)
+    assert isinstance(proxy, LazyProxy)

From 8b1b0da7a20c73ed1f205e31b862d7a301f98e17 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 10 May 2025 02:33:35 +0000
Subject: [PATCH 135/227] fix(package): support direct resource imports

---
 src/codex/__init__.py                |  5 +++++
 src/codex/_utils/_resources_proxy.py | 24 ++++++++++++++++++++++++
 2 files changed, 29 insertions(+)
 create mode 100644 src/codex/_utils/_resources_proxy.py

diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index d6dffe2c..5c5f678c 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -1,5 +1,7 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
+import typing as _t
+
 from . import types
 from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
 from ._utils import file_from_path
@@ -80,6 +82,9 @@
     "DefaultAsyncHttpxClient",
 ]
 
+if not _t.TYPE_CHECKING:
+    from ._utils._resources_proxy import resources as resources
+
 _setup_logging()
 
 # Update the __module__ attribute for exported symbols so that
diff --git a/src/codex/_utils/_resources_proxy.py b/src/codex/_utils/_resources_proxy.py
new file mode 100644
index 00000000..346e8681
--- /dev/null
+++ b/src/codex/_utils/_resources_proxy.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+from typing import Any
+from typing_extensions import override
+
+from ._proxy import LazyProxy
+
+
+class ResourcesProxy(LazyProxy[Any]):
+    """A proxy for the `codex.resources` module.
+
+    This is used so that we can lazily import `codex.resources` only when
+    needed *and* so that users can just import `codex` and reference `codex.resources`
+    """
+
+    @override
+    def __load__(self) -> Any:
+        import importlib
+
+        mod = importlib.import_module("codex.resources")
+        return mod
+
+
+resources = ResourcesProxy().__as_proxied__()

From 2c5294df9c841d0dc156f39699717e97d0962833 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 12 May 2025 14:16:48 +0000
Subject: [PATCH 136/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index f3d66113..a0ce2326 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 3b6849447c1956c43e53ce519056090e
+openapi_spec_hash: 62dd5958a2e393554eebfff1f9ec4e9c
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 157909ede529c43a38c6d5b1831fe02626a55d50 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 13 May 2025 19:16:54 +0000
Subject: [PATCH 137/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index a0ce2326..59d48ea4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 62dd5958a2e393554eebfff1f9ec4e9c
+openapi_spec_hash: 8ffde9b129ffc5edd4c4f8c9d866d869
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 7652365c55705a5fb2300b039b91d02893b24233 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 14 May 2025 19:16:46 +0000
Subject: [PATCH 138/227] feat(api): api update

---
 .stats.yml                                   |  2 +-
 src/codex/resources/projects/projects.py     | 22 +++++++++++++-------
 src/codex/types/project_validate_params.py   | 22 +++++++-------------
 src/codex/types/project_validate_response.py |  4 ++--
 tests/api_resources/test_projects.py         | 14 ++-----------
 5 files changed, 27 insertions(+), 37 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 59d48ea4..b18a9ded 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 8ffde9b129ffc5edd4c4f8c9d866d869
+openapi_spec_hash: 056bc3805c2373563a6585103edd5cb8
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index b8fbaf7e..6195d1a4 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -426,8 +426,8 @@ def validate(
         query: str,
         response: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
-        bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
@@ -451,10 +451,13 @@ def validate(
         query will be recorded in the project for SMEs to answer.
 
         Args:
+          custom_eval_thresholds: Optional custom thresholds for specific evals. Keys should match with the keys
+              in the `eval_scores` dictionary.
+
           custom_metadata: Arbitrary metadata supplied by the user/system
 
-          eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM
-              will be used to generate scores.
+          eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
+              be used to generate scores.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -575,8 +578,8 @@ def validate(
                     "prompt": prompt,
                     "query": query,
                     "response": response,
-                    "bad_response_thresholds": bad_response_thresholds,
                     "constrain_outputs": constrain_outputs,
+                    "custom_eval_thresholds": custom_eval_thresholds,
                     "custom_metadata": custom_metadata,
                     "eval_scores": eval_scores,
                     "options": options,
@@ -967,8 +970,8 @@ async def validate(
         query: str,
         response: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
-        bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
@@ -992,10 +995,13 @@ async def validate(
         query will be recorded in the project for SMEs to answer.
 
         Args:
+          custom_eval_thresholds: Optional custom thresholds for specific evals. Keys should match with the keys
+              in the `eval_scores` dictionary.
+
           custom_metadata: Arbitrary metadata supplied by the user/system
 
-          eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM
-              will be used to generate scores.
+          eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
+              be used to generate scores.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -1116,8 +1122,8 @@ async def validate(
                     "prompt": prompt,
                     "query": query,
                     "response": response,
-                    "bad_response_thresholds": bad_response_thresholds,
                     "constrain_outputs": constrain_outputs,
+                    "custom_eval_thresholds": custom_eval_thresholds,
                     "custom_metadata": custom_metadata,
                     "eval_scores": eval_scores,
                     "options": options,
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index f6214cbe..a855aa6f 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -7,7 +7,7 @@
 
 from .._utils import PropertyInfo
 
-__all__ = ["ProjectValidateParams", "BadResponseThresholds", "Options"]
+__all__ = ["ProjectValidateParams", "Options"]
 
 
 class ProjectValidateParams(TypedDict, total=False):
@@ -21,15 +21,19 @@ class ProjectValidateParams(TypedDict, total=False):
 
     use_llm_matching: bool
 
-    bad_response_thresholds: BadResponseThresholds
-
     constrain_outputs: Optional[List[str]]
 
+    custom_eval_thresholds: Optional[Dict[str, float]]
+    """Optional custom thresholds for specific evals.
+
+    Keys should match with the keys in the `eval_scores` dictionary.
+    """
+
     custom_metadata: Optional[object]
     """Arbitrary metadata supplied by the user/system"""
 
     eval_scores: Optional[Dict[str, float]]
-    """Evaluation scores to use for flagging a response as bad.
+    """Scores assessing different aspects of the RAG system.
 
     If not provided, TLM will be used to generate scores.
     """
@@ -139,16 +143,6 @@ class ProjectValidateParams(TypedDict, total=False):
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
 
 
-class BadResponseThresholds(TypedDict, total=False):
-    context_sufficiency: Optional[float]
-
-    query_ease: Optional[float]
-
-    response_helpfulness: Optional[float]
-
-    trustworthiness: Optional[float]
-
-
 class Options(TypedDict, total=False):
     custom_eval_criteria: Iterable[object]
 
diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py
index e2104360..a88874da 100644
--- a/src/codex/types/project_validate_response.py
+++ b/src/codex/types/project_validate_response.py
@@ -8,7 +8,7 @@
 
 
 class EvalScores(BaseModel):
-    is_bad: bool
+    failed: bool
 
     score: Optional[float] = None
 
@@ -18,7 +18,7 @@ class EvalScores(BaseModel):
 class ProjectValidateResponse(BaseModel):
     eval_scores: Dict[str, EvalScores]
     """
-    Evaluation scores for the original response along with a boolean flag, `is_bad`,
+    Evaluation scores for the original response along with a boolean flag, `failed`,
     indicating whether the score is below the threshold.
     """
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index f7ca6e01..19e41a0a 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -444,13 +444,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             query="query",
             response="response",
             use_llm_matching=True,
-            bad_response_thresholds={
-                "context_sufficiency": 0,
-                "query_ease": 0,
-                "response_helpfulness": 0,
-                "trustworthiness": 0,
-            },
             constrain_outputs=["string"],
+            custom_eval_thresholds={"foo": 0},
             custom_metadata={},
             eval_scores={"foo": 0},
             options={
@@ -944,13 +939,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             query="query",
             response="response",
             use_llm_matching=True,
-            bad_response_thresholds={
-                "context_sufficiency": 0,
-                "query_ease": 0,
-                "response_helpfulness": 0,
-                "trustworthiness": 0,
-            },
             constrain_outputs=["string"],
+            custom_eval_thresholds={"foo": 0},
             custom_metadata={},
             eval_scores={"foo": 0},
             options={

From 272030b37f2ef1fa76a6cebd70278f7f74078271 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 14 May 2025 21:16:57 +0000
Subject: [PATCH 139/227] feat(api): api update

---
 .stats.yml                                          |  2 +-
 src/codex/resources/projects/clusters.py            |  8 ++++++--
 src/codex/types/projects/cluster_list_params.py     |  2 +-
 src/codex/types/projects/entry_notify_sme_params.py | 11 ++++++++++-
 4 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index b18a9ded..12a0365a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 056bc3805c2373563a6585103edd5cb8
+openapi_spec_hash: 9d81a4b0eca6d3629ba9d5432a65655c
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index fea7e28e..97124642 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -50,7 +50,9 @@ def list(
         self,
         project_id: str,
         *,
-        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
+        eval_issue_types: List[
+            Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]
+        ]
         | NotGiven = NOT_GIVEN,
         instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
@@ -177,7 +179,9 @@ def list(
         self,
         project_id: str,
         *,
-        eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
+        eval_issue_types: List[
+            Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]
+        ]
         | NotGiven = NOT_GIVEN,
         instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index cff2b85e..20284d84 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -9,7 +9,7 @@
 
 
 class ClusterListParams(TypedDict, total=False):
-    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]]
+    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
 
     instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]]
 
diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py
index 409f8bc5..3d06d1a6 100644
--- a/src/codex/types/projects/entry_notify_sme_params.py
+++ b/src/codex/types/projects/entry_notify_sme_params.py
@@ -18,4 +18,13 @@ class EntryNotifySmeParams(TypedDict, total=False):
 class ViewContext(TypedDict, total=False):
     page: Required[int]
 
-    filter: Literal["unanswered", "answered", "all", "hallucination", "search_failure", "unhelpful", "difficult_query"]
+    filter: Literal[
+        "unanswered",
+        "answered",
+        "all",
+        "hallucination",
+        "search_failure",
+        "unhelpful",
+        "difficult_query",
+        "unsupported",
+    ]

From 0ff32ac5f5eb5d4b2dbab2b32e3cd3735541da15 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 15 May 2025 03:15:04 +0000
Subject: [PATCH 140/227] chore(ci): upload sdks to package manager

---
 .github/workflows/ci.yml         | 24 ++++++++++++++++++++++++
 scripts/utils/upload-artifact.sh | 25 +++++++++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100755 scripts/utils/upload-artifact.sh

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fc62f784..c02247a3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,3 +29,27 @@ jobs:
 
       - name: Run lints
         run: ./scripts/lint
+
+  upload:
+    if: github.repository == 'stainless-sdks/codex-python'
+    timeout-minutes: 10
+    name: upload
+    permissions:
+      contents: read
+      id-token: write
+    runs-on: depot-ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Get GitHub OIDC Token
+        id: github-oidc
+        uses: actions/github-script@v6
+        with:
+          script: core.setOutput('github_token', await core.getIDToken());
+
+      - name: Upload tarball
+        env:
+          URL: https://pkg.stainless.com/s
+          AUTH: ${{ steps.github-oidc.outputs.github_token }}
+          SHA: ${{ github.sha }}
+        run: ./scripts/utils/upload-artifact.sh
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
new file mode 100755
index 00000000..ebb04789
--- /dev/null
+++ b/scripts/utils/upload-artifact.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -exuo pipefail
+
+RESPONSE=$(curl -X POST "$URL" \
+  -H "Authorization: Bearer $AUTH" \
+  -H "Content-Type: application/json")
+
+SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url')
+
+if [[ "$SIGNED_URL" == "null" ]]; then
+  echo -e "\033[31mFailed to get signed URL.\033[0m"
+  exit 1
+fi
+
+UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
+  -H "Content-Type: application/gzip" \
+  --data-binary @- "$SIGNED_URL" 2>&1)
+
+if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
+  echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
+  echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
+else
+  echo -e "\033[31mFailed to upload artifact.\033[0m"
+  exit 1
+fi

From 9a6a371e15c587dc3d35acdaad81085531ae87d8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 15 May 2025 17:10:51 +0000
Subject: [PATCH 141/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b386befd..fac14074 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.19"
+  ".": "0.1.0-alpha.20"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index b288d3ef..8a5a62c7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.19"
+version = "0.1.0-alpha.20"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 87d42e64..44d6131d 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.19"  # x-release-please-version
+__version__ = "0.1.0-alpha.20"  # x-release-please-version

From 1d13a34d9efb160a66e69697284e060a04aee341 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 15 May 2025 18:16:44 +0000
Subject: [PATCH 142/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 12a0365a..76c12f55 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 9d81a4b0eca6d3629ba9d5432a65655c
+openapi_spec_hash: 19d3afd940d8ed57b76401ef026e5f47
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 2c1712f57e52fd1b02e5b9779bb8de25862386c7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 16 May 2025 02:39:50 +0000
Subject: [PATCH 143/227] chore(ci): fix installation instructions

---
 scripts/utils/upload-artifact.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index ebb04789..8f922b51 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
 
 if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
   echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
-  echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
+  echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
 else
   echo -e "\033[31mFailed to upload artifact.\033[0m"
   exit 1

From b5ed20cef51870d4dd939dff518c312a7c66ac49 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 17 May 2025 02:50:02 +0000
Subject: [PATCH 144/227] chore(internal): codegen related update

---
 scripts/utils/upload-artifact.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index 8f922b51..62d150a4 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
 
 if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
   echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
-  echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
+  echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
 else
   echo -e "\033[31mFailed to upload artifact.\033[0m"
   exit 1

From 15ab506c9579aad2489058f16c587fc0ab2c741c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 21 May 2025 18:16:58 +0000
Subject: [PATCH 145/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 .../types/projects/cluster_list_response.py   | 20 +++++++++++++++++++
 src/codex/types/projects/entry.py             | 20 +++++++++++++++++++
 .../types/projects/entry_query_response.py    | 20 +++++++++++++++++++
 4 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 76c12f55..aac346a7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 19d3afd940d8ed57b76401ef026e5f47
+openapi_spec_hash: f25ca671adcc0b224451c721048d9220
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index 2e8b5426..1fc8bd5e 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -13,6 +13,7 @@
     "ManagedMetadataContextSufficiency",
     "ManagedMetadataHTMLFormatScores",
     "ManagedMetadataQueryEaseCustomized",
+    "ManagedMetadataResponseGroundedness",
     "ManagedMetadataResponseHelpfulness",
     "ManagedMetadataTrustworthiness",
 ]
@@ -82,6 +83,22 @@ class ManagedMetadataQueryEaseCustomized(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class ManagedMetadataResponseGroundedness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataResponseHelpfulness(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -147,6 +164,9 @@ class ManagedMetadata(BaseModel):
     query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
     """Holds a list of scores and computes aggregate statistics."""
 
+    response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
     """Holds a list of scores and computes aggregate statistics."""
 
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index eb2a2217..3f7a86da 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -13,6 +13,7 @@
     "ManagedMetadataContextSufficiency",
     "ManagedMetadataHTMLFormatScores",
     "ManagedMetadataQueryEaseCustomized",
+    "ManagedMetadataResponseGroundedness",
     "ManagedMetadataResponseHelpfulness",
     "ManagedMetadataTrustworthiness",
 ]
@@ -82,6 +83,22 @@ class ManagedMetadataQueryEaseCustomized(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class ManagedMetadataResponseGroundedness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class ManagedMetadataResponseHelpfulness(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -147,6 +164,9 @@ class ManagedMetadata(BaseModel):
     query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
     """Holds a list of scores and computes aggregate statistics."""
 
+    response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
     """Holds a list of scores and computes aggregate statistics."""
 
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
index 318636b9..cd5a4c97 100644
--- a/src/codex/types/projects/entry_query_response.py
+++ b/src/codex/types/projects/entry_query_response.py
@@ -12,6 +12,7 @@
     "EntryManagedMetadataContextSufficiency",
     "EntryManagedMetadataHTMLFormatScores",
     "EntryManagedMetadataQueryEaseCustomized",
+    "EntryManagedMetadataResponseGroundedness",
     "EntryManagedMetadataResponseHelpfulness",
     "EntryManagedMetadataTrustworthiness",
 ]
@@ -81,6 +82,22 @@ class EntryManagedMetadataQueryEaseCustomized(BaseModel):
     scores: Optional[List[float]] = None
 
 
+class EntryManagedMetadataResponseGroundedness(BaseModel):
+    average: Optional[float] = None
+    """The average of all scores."""
+
+    latest: Optional[float] = None
+    """The most recent score."""
+
+    max: Optional[float] = None
+    """The maximum score."""
+
+    min: Optional[float] = None
+    """The minimum score."""
+
+    scores: Optional[List[float]] = None
+
+
 class EntryManagedMetadataResponseHelpfulness(BaseModel):
     average: Optional[float] = None
     """The average of all scores."""
@@ -146,6 +163,9 @@ class EntryManagedMetadata(BaseModel):
     query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None
     """Holds a list of scores and computes aggregate statistics."""
 
+    response_groundedness: Optional[EntryManagedMetadataResponseGroundedness] = None
+    """Holds a list of scores and computes aggregate statistics."""
+
     response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None
     """Holds a list of scores and computes aggregate statistics."""
 

From 6e144060b73fb03f94616582a81045ca92eb11ef Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 21 May 2025 22:16:41 +0000
Subject: [PATCH 146/227] feat(api): api update

---
 .stats.yml                           | 2 +-
 tests/api_resources/test_projects.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index aac346a7..374e6728 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: f25ca671adcc0b224451c721048d9220
+openapi_spec_hash: 67d5aeebff72f48ee4730227ca0b47c2
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 19e41a0a..5c29fddf 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -204,7 +204,7 @@ def test_method_list(self, client: Codex) -> None:
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
             include_entry_counts=True,
-            limit=0,
+            limit=1,
             offset=0,
             order="asc",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
@@ -699,7 +699,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
             include_entry_counts=True,
-            limit=0,
+            limit=1,
             offset=0,
             order="asc",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",

From be3641fbf88d6305df84a2fc4903c8ceface327f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 22 May 2025 02:29:17 +0000
Subject: [PATCH 147/227] chore(docs): grammar improvements

---
 SECURITY.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/SECURITY.md b/SECURITY.md
index 9fc6ee28..07808285 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,11 +16,11 @@ before making any information public.
 ## Reporting Non-SDK Related Security Issues
 
 If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Codex please follow the respective company's security reporting guidelines.
+or products provided by Codex, please follow the respective company's security reporting guidelines.
 
 ### Codex Terms and Policies
 
-Please contact team@cleanlab.ai for any questions or concerns regarding security of our services.
+Please contact team@cleanlab.ai for any questions or concerns regarding the security of our services.
 
 ---
 

From 8f49e9fec86ad6b78f4aa9d91cefb04a8d4b11c8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 23 May 2025 19:16:40 +0000
Subject: [PATCH 148/227] feat(api): api update

---
 .stats.yml                                   |   2 +-
 README.md                                    |  66 +++++
 src/codex/types/project_create_params.py     | 285 ++++++++++++++++++-
 src/codex/types/project_list_response.py     | 285 ++++++++++++++++++-
 src/codex/types/project_retrieve_response.py | 284 +++++++++++++++++-
 src/codex/types/project_return_schema.py     | 284 +++++++++++++++++-
 src/codex/types/project_update_params.py     | 285 ++++++++++++++++++-
 tests/api_resources/test_projects.py         | 264 +++++++++++++++++
 8 files changed, 1742 insertions(+), 13 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 374e6728..e80f0e13 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 67d5aeebff72f48ee4730227ca0b47c2
+openapi_spec_hash: 0f1841fad65926e7ddfb22dd7a642b46
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/README.md b/README.md
index c2f168fe..4663d2fd 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,72 @@ client = Codex()
 project_return_schema = client.projects.create(
     config={
         "clustering_use_llm_matching": True,
+        "eval_config": {
+            "custom_evals": {
+                "evals": {
+                    "foo": {
+                        "criteria": "criteria",
+                        "eval_key": "eval_key",
+                        "name": "name",
+                        "context_identifier": "context_identifier",
+                        "enabled": True,
+                        "priority": 0,
+                        "query_identifier": "query_identifier",
+                        "response_identifier": "response_identifier",
+                        "should_escalate": True,
+                        "threshold": 0,
+                        "threshold_direction": "above",
+                    }
+                }
+            },
+            "default_evals": {
+                "context_sufficiency": {
+                    "eval_key": "eval_key",
+                    "name": "name",
+                    "enabled": True,
+                    "priority": 0,
+                    "should_escalate": True,
+                    "threshold": 0,
+                    "threshold_direction": "above",
+                },
+                "query_ease": {
+                    "eval_key": "eval_key",
+                    "name": "name",
+                    "enabled": True,
+                    "priority": 0,
+                    "should_escalate": True,
+                    "threshold": 0,
+                    "threshold_direction": "above",
+                },
+                "response_groundedness": {
+                    "eval_key": "eval_key",
+                    "name": "name",
+                    "enabled": True,
+                    "priority": 0,
+                    "should_escalate": True,
+                    "threshold": 0,
+                    "threshold_direction": "above",
+                },
+                "response_helpfulness": {
+                    "eval_key": "eval_key",
+                    "name": "name",
+                    "enabled": True,
+                    "priority": 0,
+                    "should_escalate": True,
+                    "threshold": 0,
+                    "threshold_direction": "above",
+                },
+                "trustworthiness": {
+                    "eval_key": "eval_key",
+                    "name": "name",
+                    "enabled": True,
+                    "priority": 0,
+                    "should_escalate": True,
+                    "threshold": 0,
+                    "threshold_direction": "above",
+                },
+            },
+        },
         "llm_matching_model": "llm_matching_model",
         "llm_matching_quality_preset": "llm_matching_quality_preset",
         "lower_llm_match_distance_threshold": 0,
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index ecdd194d..75892e0b 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -2,10 +2,22 @@
 
 from __future__ import annotations
 
-from typing import Optional
-from typing_extensions import Required, TypedDict
+from typing import Dict, Optional
+from typing_extensions import Literal, Required, TypedDict
 
-__all__ = ["ProjectCreateParams", "Config"]
+__all__ = [
+    "ProjectCreateParams",
+    "Config",
+    "ConfigEvalConfig",
+    "ConfigEvalConfigCustomEvals",
+    "ConfigEvalConfigCustomEvalsEvals",
+    "ConfigEvalConfigDefaultEvals",
+    "ConfigEvalConfigDefaultEvalsContextSufficiency",
+    "ConfigEvalConfigDefaultEvalsQueryEase",
+    "ConfigEvalConfigDefaultEvalsResponseGroundedness",
+    "ConfigEvalConfigDefaultEvalsResponseHelpfulness",
+    "ConfigEvalConfigDefaultEvalsTrustworthiness",
+]
 
 
 class ProjectCreateParams(TypedDict, total=False):
@@ -18,9 +30,276 @@ class ProjectCreateParams(TypedDict, total=False):
     description: Optional[str]
 
 
+class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
+    criteria: Required[str]
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigCustomEvals(TypedDict, total=False):
+    evals: Dict[str, ConfigEvalConfigCustomEvalsEvals]
+
+
+class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvals(TypedDict, total=False):
+    context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    query_ease: ConfigEvalConfigDefaultEvalsQueryEase
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_groundedness: ConfigEvalConfigDefaultEvalsResponseGroundedness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_helpfulness: ConfigEvalConfigDefaultEvalsResponseHelpfulness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    trustworthiness: ConfigEvalConfigDefaultEvalsTrustworthiness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+
+class ConfigEvalConfig(TypedDict, total=False):
+    custom_evals: ConfigEvalConfigCustomEvals
+    """Configuration for custom evaluation metrics."""
+
+    default_evals: ConfigEvalConfigDefaultEvals
+    """Configuration for default evaluation metrics."""
+
+
 class Config(TypedDict, total=False):
     clustering_use_llm_matching: bool
 
+    eval_config: ConfigEvalConfig
+    """Configuration for project-specific evaluation metrics"""
+
     llm_matching_model: str
 
     llm_matching_quality_preset: str
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 2b4fec42..59d3bf81 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -1,16 +1,297 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import List, Optional
+from typing import Dict, List, Optional
 from datetime import datetime
+from typing_extensions import Literal
 
 from .._models import BaseModel
 
-__all__ = ["ProjectListResponse", "Project", "ProjectConfig"]
+__all__ = [
+    "ProjectListResponse",
+    "Project",
+    "ProjectConfig",
+    "ProjectConfigEvalConfig",
+    "ProjectConfigEvalConfigCustomEvals",
+    "ProjectConfigEvalConfigCustomEvalsEvals",
+    "ProjectConfigEvalConfigDefaultEvals",
+    "ProjectConfigEvalConfigDefaultEvalsContextSufficiency",
+    "ProjectConfigEvalConfigDefaultEvalsQueryEase",
+    "ProjectConfigEvalConfigDefaultEvalsResponseGroundedness",
+    "ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness",
+    "ProjectConfigEvalConfigDefaultEvalsTrustworthiness",
+]
+
+
+class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel):
+    criteria: str
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigCustomEvals(BaseModel):
+    evals: Optional[Dict[str, ProjectConfigEvalConfigCustomEvalsEvals]] = None
+
+
+class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ProjectConfigEvalConfigDefaultEvals(BaseModel):
+    context_sufficiency: Optional[ProjectConfigEvalConfigDefaultEvalsContextSufficiency] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    query_ease: Optional[ProjectConfigEvalConfigDefaultEvalsQueryEase] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_groundedness: Optional[ProjectConfigEvalConfigDefaultEvalsResponseGroundedness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_helpfulness: Optional[ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    trustworthiness: Optional[ProjectConfigEvalConfigDefaultEvalsTrustworthiness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+
+class ProjectConfigEvalConfig(BaseModel):
+    custom_evals: Optional[ProjectConfigEvalConfigCustomEvals] = None
+    """Configuration for custom evaluation metrics."""
+
+    default_evals: Optional[ProjectConfigEvalConfigDefaultEvals] = None
+    """Configuration for default evaluation metrics."""
 
 
 class ProjectConfig(BaseModel):
     clustering_use_llm_matching: Optional[bool] = None
 
+    eval_config: Optional[ProjectConfigEvalConfig] = None
+    """Configuration for project-specific evaluation metrics"""
+
     llm_matching_model: Optional[str] = None
 
     llm_matching_quality_preset: Optional[str] = None
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index 62209d32..a631f0c2 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -1,16 +1,296 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Optional
+from typing import Dict, Optional
 from datetime import datetime
+from typing_extensions import Literal
 
 from .._models import BaseModel
 
-__all__ = ["ProjectRetrieveResponse", "Config"]
+__all__ = [
+    "ProjectRetrieveResponse",
+    "Config",
+    "ConfigEvalConfig",
+    "ConfigEvalConfigCustomEvals",
+    "ConfigEvalConfigCustomEvalsEvals",
+    "ConfigEvalConfigDefaultEvals",
+    "ConfigEvalConfigDefaultEvalsContextSufficiency",
+    "ConfigEvalConfigDefaultEvalsQueryEase",
+    "ConfigEvalConfigDefaultEvalsResponseGroundedness",
+    "ConfigEvalConfigDefaultEvalsResponseHelpfulness",
+    "ConfigEvalConfigDefaultEvalsTrustworthiness",
+]
+
+
+class ConfigEvalConfigCustomEvalsEvals(BaseModel):
+    criteria: str
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigCustomEvals(BaseModel):
+    evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None
+
+
+class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvals(BaseModel):
+    context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    query_ease: Optional[ConfigEvalConfigDefaultEvalsQueryEase] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_groundedness: Optional[ConfigEvalConfigDefaultEvalsResponseGroundedness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_helpfulness: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    trustworthiness: Optional[ConfigEvalConfigDefaultEvalsTrustworthiness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+
+class ConfigEvalConfig(BaseModel):
+    custom_evals: Optional[ConfigEvalConfigCustomEvals] = None
+    """Configuration for custom evaluation metrics."""
+
+    default_evals: Optional[ConfigEvalConfigDefaultEvals] = None
+    """Configuration for default evaluation metrics."""
 
 
 class Config(BaseModel):
     clustering_use_llm_matching: Optional[bool] = None
 
+    eval_config: Optional[ConfigEvalConfig] = None
+    """Configuration for project-specific evaluation metrics"""
+
     llm_matching_model: Optional[str] = None
 
     llm_matching_quality_preset: Optional[str] = None
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 51a6c1ad..7da2e615 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -1,16 +1,296 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Optional
+from typing import Dict, Optional
 from datetime import datetime
+from typing_extensions import Literal
 
 from .._models import BaseModel
 
-__all__ = ["ProjectReturnSchema", "Config"]
+__all__ = [
+    "ProjectReturnSchema",
+    "Config",
+    "ConfigEvalConfig",
+    "ConfigEvalConfigCustomEvals",
+    "ConfigEvalConfigCustomEvalsEvals",
+    "ConfigEvalConfigDefaultEvals",
+    "ConfigEvalConfigDefaultEvalsContextSufficiency",
+    "ConfigEvalConfigDefaultEvalsQueryEase",
+    "ConfigEvalConfigDefaultEvalsResponseGroundedness",
+    "ConfigEvalConfigDefaultEvalsResponseHelpfulness",
+    "ConfigEvalConfigDefaultEvalsTrustworthiness",
+]
+
+
+class ConfigEvalConfigCustomEvalsEvals(BaseModel):
+    criteria: str
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigCustomEvals(BaseModel):
+    evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None
+
+
+class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvals(BaseModel):
+    context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    query_ease: Optional[ConfigEvalConfigDefaultEvalsQueryEase] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_groundedness: Optional[ConfigEvalConfigDefaultEvalsResponseGroundedness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_helpfulness: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    trustworthiness: Optional[ConfigEvalConfigDefaultEvalsTrustworthiness] = None
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+
+class ConfigEvalConfig(BaseModel):
+    custom_evals: Optional[ConfigEvalConfigCustomEvals] = None
+    """Configuration for custom evaluation metrics."""
+
+    default_evals: Optional[ConfigEvalConfigDefaultEvals] = None
+    """Configuration for default evaluation metrics."""
 
 
 class Config(BaseModel):
     clustering_use_llm_matching: Optional[bool] = None
 
+    eval_config: Optional[ConfigEvalConfig] = None
+    """Configuration for project-specific evaluation metrics"""
+
     llm_matching_model: Optional[str] = None
 
     llm_matching_quality_preset: Optional[str] = None
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 0a5aa540..d58dd591 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -2,10 +2,22 @@
 
 from __future__ import annotations
 
-from typing import Optional
-from typing_extensions import Required, TypedDict
+from typing import Dict, Optional
+from typing_extensions import Literal, Required, TypedDict
 
-__all__ = ["ProjectUpdateParams", "Config"]
+__all__ = [
+    "ProjectUpdateParams",
+    "Config",
+    "ConfigEvalConfig",
+    "ConfigEvalConfigCustomEvals",
+    "ConfigEvalConfigCustomEvalsEvals",
+    "ConfigEvalConfigDefaultEvals",
+    "ConfigEvalConfigDefaultEvalsContextSufficiency",
+    "ConfigEvalConfigDefaultEvalsQueryEase",
+    "ConfigEvalConfigDefaultEvalsResponseGroundedness",
+    "ConfigEvalConfigDefaultEvalsResponseHelpfulness",
+    "ConfigEvalConfigDefaultEvalsTrustworthiness",
+]
 
 
 class ProjectUpdateParams(TypedDict, total=False):
@@ -16,9 +28,276 @@ class ProjectUpdateParams(TypedDict, total=False):
     description: Optional[str]
 
 
+class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
+    criteria: Required[str]
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigCustomEvals(TypedDict, total=False):
+    evals: Dict[str, ConfigEvalConfigCustomEvalsEvals]
+
+
+class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False):
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class ConfigEvalConfigDefaultEvals(TypedDict, total=False):
+    context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    query_ease: ConfigEvalConfigDefaultEvalsQueryEase
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_groundedness: ConfigEvalConfigDefaultEvalsResponseGroundedness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    response_helpfulness: ConfigEvalConfigDefaultEvalsResponseHelpfulness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+    trustworthiness: ConfigEvalConfigDefaultEvalsTrustworthiness
+    """A pre-configured evaluation metric from TrustworthyRAG or built into the system.
+
+    The evaluation criteria and identifiers are immutable and system-managed, while
+    other properties like thresholds and priorities can be configured.
+    """
+
+
+class ConfigEvalConfig(TypedDict, total=False):
+    custom_evals: ConfigEvalConfigCustomEvals
+    """Configuration for custom evaluation metrics."""
+
+    default_evals: ConfigEvalConfigDefaultEvals
+    """Configuration for default evaluation metrics."""
+
+
 class Config(TypedDict, total=False):
     clustering_use_llm_matching: bool
 
+    eval_config: ConfigEvalConfig
+    """Configuration for project-specific evaluation metrics"""
+
     llm_matching_model: str
 
     llm_matching_quality_preset: str
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 5c29fddf..d5e0e1cc 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -39,6 +39,72 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         project = client.projects.create(
             config={
                 "clustering_use_llm_matching": True,
+                "eval_config": {
+                    "custom_evals": {
+                        "evals": {
+                            "foo": {
+                                "criteria": "criteria",
+                                "eval_key": "eval_key",
+                                "name": "name",
+                                "context_identifier": "context_identifier",
+                                "enabled": True,
+                                "priority": 0,
+                                "query_identifier": "query_identifier",
+                                "response_identifier": "response_identifier",
+                                "should_escalate": True,
+                                "threshold": 0,
+                                "threshold_direction": "above",
+                            }
+                        }
+                    },
+                    "default_evals": {
+                        "context_sufficiency": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "query_ease": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_groundedness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_helpfulness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "trustworthiness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                    },
+                },
                 "llm_matching_model": "llm_matching_model",
                 "llm_matching_quality_preset": "llm_matching_quality_preset",
                 "lower_llm_match_distance_threshold": 0,
@@ -141,6 +207,72 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={
                 "clustering_use_llm_matching": True,
+                "eval_config": {
+                    "custom_evals": {
+                        "evals": {
+                            "foo": {
+                                "criteria": "criteria",
+                                "eval_key": "eval_key",
+                                "name": "name",
+                                "context_identifier": "context_identifier",
+                                "enabled": True,
+                                "priority": 0,
+                                "query_identifier": "query_identifier",
+                                "response_identifier": "response_identifier",
+                                "should_escalate": True,
+                                "threshold": 0,
+                                "threshold_direction": "above",
+                            }
+                        }
+                    },
+                    "default_evals": {
+                        "context_sufficiency": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "query_ease": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_groundedness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_helpfulness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "trustworthiness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                    },
+                },
                 "llm_matching_model": "llm_matching_model",
                 "llm_matching_quality_preset": "llm_matching_quality_preset",
                 "lower_llm_match_distance_threshold": 0,
@@ -534,6 +666,72 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         project = await async_client.projects.create(
             config={
                 "clustering_use_llm_matching": True,
+                "eval_config": {
+                    "custom_evals": {
+                        "evals": {
+                            "foo": {
+                                "criteria": "criteria",
+                                "eval_key": "eval_key",
+                                "name": "name",
+                                "context_identifier": "context_identifier",
+                                "enabled": True,
+                                "priority": 0,
+                                "query_identifier": "query_identifier",
+                                "response_identifier": "response_identifier",
+                                "should_escalate": True,
+                                "threshold": 0,
+                                "threshold_direction": "above",
+                            }
+                        }
+                    },
+                    "default_evals": {
+                        "context_sufficiency": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "query_ease": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_groundedness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_helpfulness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "trustworthiness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                    },
+                },
                 "llm_matching_model": "llm_matching_model",
                 "llm_matching_quality_preset": "llm_matching_quality_preset",
                 "lower_llm_match_distance_threshold": 0,
@@ -636,6 +834,72 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             config={
                 "clustering_use_llm_matching": True,
+                "eval_config": {
+                    "custom_evals": {
+                        "evals": {
+                            "foo": {
+                                "criteria": "criteria",
+                                "eval_key": "eval_key",
+                                "name": "name",
+                                "context_identifier": "context_identifier",
+                                "enabled": True,
+                                "priority": 0,
+                                "query_identifier": "query_identifier",
+                                "response_identifier": "response_identifier",
+                                "should_escalate": True,
+                                "threshold": 0,
+                                "threshold_direction": "above",
+                            }
+                        }
+                    },
+                    "default_evals": {
+                        "context_sufficiency": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "query_ease": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_groundedness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "response_helpfulness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                        "trustworthiness": {
+                            "eval_key": "eval_key",
+                            "name": "name",
+                            "enabled": True,
+                            "priority": 0,
+                            "should_escalate": True,
+                            "threshold": 0,
+                            "threshold_direction": "above",
+                        },
+                    },
+                },
                 "llm_matching_model": "llm_matching_model",
                 "llm_matching_quality_preset": "llm_matching_quality_preset",
                 "lower_llm_match_distance_threshold": 0,

From 0048a179ad99bf1f3ef3367c06788c0dc0c8b0ae Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 28 May 2025 03:26:20 +0000
Subject: [PATCH 149/227] fix(docs/api): remove references to nonexistent types

---
 api.md | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/api.md b/api.md
index f3a2ea14..8aac76fd 100644
--- a/api.md
+++ b/api.md
@@ -139,8 +139,6 @@ from codex.types import (
     ProjectReturnSchema,
     ProjectRetrieveResponse,
     ProjectListResponse,
-    ProjectExportResponse,
-    ProjectIncrementQueriesResponse,
     ProjectRetrieveAnalyticsResponse,
     ProjectValidateResponse,
 )
@@ -153,8 +151,8 @@ Methods:
 - <code title="put /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">update</a>(project_id, \*\*<a href="src/codex/types/project_update_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
 - <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
-- <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> <a href="./src/codex/types/project_export_response.py">object</a></code>
-- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> <a href="./src/codex/types/project_increment_queries_response.py">object</a></code>
+- <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> object</code>
+- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> object</code>
 - <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
 - <code title="post /api/projects/{project_id}/validate">client.projects.<a href="./src/codex/resources/projects/projects.py">validate</a>(project_id, \*\*<a href="src/codex/types/project_validate_params.py">params</a>) -> <a href="./src/codex/types/project_validate_response.py">ProjectValidateResponse</a></code>
 

From f48542a4ebea2df63b7f1da0345bcebbe535fa8a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 29 May 2025 03:09:34 +0000
Subject: [PATCH 150/227] chore(api): mark some methods as deprecated

---
 src/codex/resources/projects/entries.py      |  27 ++-
 src/codex/resources/projects/projects.py     |  27 ++-
 tests/api_resources/projects/test_entries.py | 164 ++++++++++---------
 tests/api_resources/test_projects.py         | 100 ++++++-----
 4 files changed, 186 insertions(+), 132 deletions(-)

diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 346dd353..c6b43a4a 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import typing_extensions
 from typing import Iterable, Optional
 
 import httpx
@@ -312,6 +313,7 @@ def publish_draft_answer(
             cast_to=Entry,
         )
 
+    @typing_extensions.deprecated("deprecated")
     def query(
         self,
         project_id: str,
@@ -707,6 +709,7 @@ async def publish_draft_answer(
             cast_to=Entry,
         )
 
+    @typing_extensions.deprecated("deprecated")
     async def query(
         self,
         project_id: str,
@@ -839,8 +842,10 @@ def __init__(self, entries: EntriesResource) -> None:
         self.publish_draft_answer = to_raw_response_wrapper(
             entries.publish_draft_answer,
         )
-        self.query = to_raw_response_wrapper(
-            entries.query,
+        self.query = (  # pyright: ignore[reportDeprecated]
+            to_raw_response_wrapper(
+                entries.query  # pyright: ignore[reportDeprecated],
+            )
         )
         self.unpublish_answer = to_raw_response_wrapper(
             entries.unpublish_answer,
@@ -869,8 +874,10 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.publish_draft_answer = async_to_raw_response_wrapper(
             entries.publish_draft_answer,
         )
-        self.query = async_to_raw_response_wrapper(
-            entries.query,
+        self.query = (  # pyright: ignore[reportDeprecated]
+            async_to_raw_response_wrapper(
+                entries.query  # pyright: ignore[reportDeprecated],
+            )
         )
         self.unpublish_answer = async_to_raw_response_wrapper(
             entries.unpublish_answer,
@@ -899,8 +906,10 @@ def __init__(self, entries: EntriesResource) -> None:
         self.publish_draft_answer = to_streamed_response_wrapper(
             entries.publish_draft_answer,
         )
-        self.query = to_streamed_response_wrapper(
-            entries.query,
+        self.query = (  # pyright: ignore[reportDeprecated]
+            to_streamed_response_wrapper(
+                entries.query  # pyright: ignore[reportDeprecated],
+            )
         )
         self.unpublish_answer = to_streamed_response_wrapper(
             entries.unpublish_answer,
@@ -929,8 +938,10 @@ def __init__(self, entries: AsyncEntriesResource) -> None:
         self.publish_draft_answer = async_to_streamed_response_wrapper(
             entries.publish_draft_answer,
         )
-        self.query = async_to_streamed_response_wrapper(
-            entries.query,
+        self.query = (  # pyright: ignore[reportDeprecated]
+            async_to_streamed_response_wrapper(
+                entries.query  # pyright: ignore[reportDeprecated],
+            )
         )
         self.unpublish_answer = async_to_streamed_response_wrapper(
             entries.unpublish_answer,
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 6195d1a4..cf8c0f82 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import typing_extensions
 from typing import Dict, List, Optional
 from typing_extensions import Literal
 
@@ -330,6 +331,7 @@ def export(
             cast_to=object,
         )
 
+    @typing_extensions.deprecated("deprecated")
     def increment_queries(
         self,
         project_id: str,
@@ -872,6 +874,7 @@ async def export(
             cast_to=object,
         )
 
+    @typing_extensions.deprecated("deprecated")
     async def increment_queries(
         self,
         project_id: str,
@@ -1167,8 +1170,10 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_raw_response_wrapper(
             projects.export,
         )
-        self.increment_queries = to_raw_response_wrapper(
-            projects.increment_queries,
+        self.increment_queries = (  # pyright: ignore[reportDeprecated]
+            to_raw_response_wrapper(
+                projects.increment_queries  # pyright: ignore[reportDeprecated],
+            )
         )
         self.retrieve_analytics = to_raw_response_wrapper(
             projects.retrieve_analytics,
@@ -1212,8 +1217,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_raw_response_wrapper(
             projects.export,
         )
-        self.increment_queries = async_to_raw_response_wrapper(
-            projects.increment_queries,
+        self.increment_queries = (  # pyright: ignore[reportDeprecated]
+            async_to_raw_response_wrapper(
+                projects.increment_queries  # pyright: ignore[reportDeprecated],
+            )
         )
         self.retrieve_analytics = async_to_raw_response_wrapper(
             projects.retrieve_analytics,
@@ -1257,8 +1264,10 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_streamed_response_wrapper(
             projects.export,
         )
-        self.increment_queries = to_streamed_response_wrapper(
-            projects.increment_queries,
+        self.increment_queries = (  # pyright: ignore[reportDeprecated]
+            to_streamed_response_wrapper(
+                projects.increment_queries  # pyright: ignore[reportDeprecated],
+            )
         )
         self.retrieve_analytics = to_streamed_response_wrapper(
             projects.retrieve_analytics,
@@ -1302,8 +1311,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_streamed_response_wrapper(
             projects.export,
         )
-        self.increment_queries = async_to_streamed_response_wrapper(
-            projects.increment_queries,
+        self.increment_queries = (  # pyright: ignore[reportDeprecated]
+            async_to_streamed_response_wrapper(
+                projects.increment_queries  # pyright: ignore[reportDeprecated],
+            )
         )
         self.retrieve_analytics = async_to_streamed_response_wrapper(
             projects.retrieve_analytics,
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 73a45ad4..32b0452e 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -15,6 +15,8 @@
     EntryNotifySmeResponse,
 )
 
+# pyright: reportDeprecated=false
+
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
 
@@ -382,40 +384,45 @@ def test_path_params_publish_draft_answer(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_method_query(self, client: Codex) -> None:
-        entry = client.projects.entries.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
+        with pytest.warns(DeprecationWarning):
+            entry = client.projects.entries.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            )
+
         assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_method_query_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            use_llm_matching=True,
-            client_metadata={},
-            query_metadata={
-                "context": "string",
-                "custom_metadata": {},
-                "eval_scores": {"foo": 0},
-                "evaluated_response": "evaluated_response",
-            },
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
+        with pytest.warns(DeprecationWarning):
+            entry = client.projects.entries.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+                use_llm_matching=True,
+                client_metadata={},
+                query_metadata={
+                    "context": "string",
+                    "custom_metadata": {},
+                    "eval_scores": {"foo": 0},
+                    "evaluated_response": "evaluated_response",
+                },
+                x_client_library_version="x-client-library-version",
+                x_integration_type="x-integration-type",
+                x_source="x-source",
+                x_stainless_package_version="x-stainless-package-version",
+            )
+
         assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_query(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
+        with pytest.warns(DeprecationWarning):
+            response = client.projects.entries.with_raw_response.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -425,26 +432,28 @@ def test_raw_response_query(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_streaming_response_query(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        with pytest.warns(DeprecationWarning):
+            with client.projects.entries.with_streaming_response.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            ) as response:
+                assert not response.is_closed
+                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
-            entry = response.parse()
-            assert_matches_type(EntryQueryResponse, entry, path=["response"])
+                entry = response.parse()
+                assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
     @pytest.mark.skip()
     @parametrize
     def test_path_params_query(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.query(
-                project_id="",
-                question="question",
-            )
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+                client.projects.entries.with_raw_response.query(
+                    project_id="",
+                    question="question",
+                )
 
     @pytest.mark.skip()
     @parametrize
@@ -863,40 +872,45 @@ async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex)
     @pytest.mark.skip()
     @parametrize
     async def test_method_query(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
+        with pytest.warns(DeprecationWarning):
+            entry = await async_client.projects.entries.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            )
+
         assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            use_llm_matching=True,
-            client_metadata={},
-            query_metadata={
-                "context": "string",
-                "custom_metadata": {},
-                "eval_scores": {"foo": 0},
-                "evaluated_response": "evaluated_response",
-            },
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
+        with pytest.warns(DeprecationWarning):
+            entry = await async_client.projects.entries.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+                use_llm_matching=True,
+                client_metadata={},
+                query_metadata={
+                    "context": "string",
+                    "custom_metadata": {},
+                    "eval_scores": {"foo": 0},
+                    "evaluated_response": "evaluated_response",
+                },
+                x_client_library_version="x-client-library-version",
+                x_integration_type="x-integration-type",
+                x_source="x-source",
+                x_stainless_package_version="x-stainless-package-version",
+            )
+
         assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
+        with pytest.warns(DeprecationWarning):
+            response = await async_client.projects.entries.with_raw_response.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -906,26 +920,28 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.query(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        with pytest.warns(DeprecationWarning):
+            async with async_client.projects.entries.with_streaming_response.query(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                question="question",
+            ) as response:
+                assert not response.is_closed
+                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
-            entry = await response.parse()
-            assert_matches_type(EntryQueryResponse, entry, path=["response"])
+                entry = await response.parse()
+                assert_matches_type(EntryQueryResponse, entry, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
     @pytest.mark.skip()
     @parametrize
     async def test_path_params_query(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.query(
-                project_id="",
-                question="question",
-            )
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+                await async_client.projects.entries.with_raw_response.query(
+                    project_id="",
+                    question="question",
+                )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index d5e0e1cc..f7c3f017 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -17,6 +17,8 @@
 )
 from tests.utils import assert_matches_type
 
+# pyright: reportDeprecated=false
+
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
 
@@ -454,26 +456,31 @@ def test_path_params_export(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_method_increment_queries(self, client: Codex) -> None:
-        project = client.projects.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        with pytest.warns(DeprecationWarning):
+            project = client.projects.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
         assert_matches_type(object, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_method_increment_queries_with_all_params(self, client: Codex) -> None:
-        project = client.projects.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            count=0,
-        )
+        with pytest.warns(DeprecationWarning):
+            project = client.projects.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                count=0,
+            )
+
         assert_matches_type(object, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     def test_raw_response_increment_queries(self, client: Codex) -> None:
-        response = client.projects.with_raw_response.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        with pytest.warns(DeprecationWarning):
+            response = client.projects.with_raw_response.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -483,24 +490,26 @@ def test_raw_response_increment_queries(self, client: Codex) -> None:
     @pytest.mark.skip()
     @parametrize
     def test_streaming_response_increment_queries(self, client: Codex) -> None:
-        with client.projects.with_streaming_response.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        with pytest.warns(DeprecationWarning):
+            with client.projects.with_streaming_response.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            ) as response:
+                assert not response.is_closed
+                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
-            project = response.parse()
-            assert_matches_type(object, project, path=["response"])
+                project = response.parse()
+                assert_matches_type(object, project, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
     @pytest.mark.skip()
     @parametrize
     def test_path_params_increment_queries(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.with_raw_response.increment_queries(
-                project_id="",
-            )
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+                client.projects.with_raw_response.increment_queries(
+                    project_id="",
+                )
 
     @pytest.mark.skip()
     @parametrize
@@ -1081,26 +1090,31 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None:
     @pytest.mark.skip()
     @parametrize
     async def test_method_increment_queries(self, async_client: AsyncCodex) -> None:
-        project = await async_client.projects.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        with pytest.warns(DeprecationWarning):
+            project = await async_client.projects.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
         assert_matches_type(object, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None:
-        project = await async_client.projects.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            count=0,
-        )
+        with pytest.warns(DeprecationWarning):
+            project = await async_client.projects.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                count=0,
+            )
+
         assert_matches_type(object, project, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
     async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.with_raw_response.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
+        with pytest.warns(DeprecationWarning):
+            response = await async_client.projects.with_raw_response.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
 
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1110,24 +1124,26 @@ async def test_raw_response_increment_queries(self, async_client: AsyncCodex) ->
     @pytest.mark.skip()
     @parametrize
     async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.with_streaming_response.increment_queries(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        with pytest.warns(DeprecationWarning):
+            async with async_client.projects.with_streaming_response.increment_queries(
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            ) as response:
+                assert not response.is_closed
+                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
-            project = await response.parse()
-            assert_matches_type(object, project, path=["response"])
+                project = await response.parse()
+                assert_matches_type(object, project, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
     @pytest.mark.skip()
     @parametrize
     async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.with_raw_response.increment_queries(
-                project_id="",
-            )
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+                await async_client.projects.with_raw_response.increment_queries(
+                    project_id="",
+                )
 
     @pytest.mark.skip()
     @parametrize

From 5b5c3b0c992677e53c96dc8ffe44afba76888c1c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 29 May 2025 17:16:51 +0000
Subject: [PATCH 151/227] feat(api): api update

---
 .stats.yml                                   | 2 +-
 README.md                                    | 1 +
 src/codex/types/project_create_params.py     | 3 +++
 src/codex/types/project_list_response.py     | 3 +++
 src/codex/types/project_retrieve_response.py | 3 +++
 src/codex/types/project_return_schema.py     | 3 +++
 src/codex/types/project_update_params.py     | 3 +++
 tests/api_resources/test_projects.py         | 4 ++++
 8 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index e80f0e13..ddf72408 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 0f1841fad65926e7ddfb22dd7a642b46
+openapi_spec_hash: dfccb5c181396678a22b9c079847889f
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/README.md b/README.md
index 4663d2fd..43251e6e 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,7 @@ project_return_schema = client.projects.create(
                         "name": "name",
                         "context_identifier": "context_identifier",
                         "enabled": True,
+                        "is_default": True,
                         "priority": 0,
                         "query_identifier": "query_identifier",
                         "response_identifier": "response_identifier",
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 75892e0b..31427557 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -55,6 +55,9 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
     enabled: bool
     """Allows the evaluation to be disabled without removing it"""
 
+    is_default: bool
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
     priority: Optional[int]
     """
     Priority order for evals (lower number = higher priority) to determine primary
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 59d3bf81..d4805732 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -47,6 +47,9 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel):
     enabled: Optional[bool] = None
     """Allows the evaluation to be disabled without removing it"""
 
+    is_default: Optional[bool] = None
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
     priority: Optional[int] = None
     """
     Priority order for evals (lower number = higher priority) to determine primary
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index a631f0c2..fb62cff3 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -46,6 +46,9 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel):
     enabled: Optional[bool] = None
     """Allows the evaluation to be disabled without removing it"""
 
+    is_default: Optional[bool] = None
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
     priority: Optional[int] = None
     """
     Priority order for evals (lower number = higher priority) to determine primary
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 7da2e615..420ec6e7 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -46,6 +46,9 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel):
     enabled: Optional[bool] = None
     """Allows the evaluation to be disabled without removing it"""
 
+    is_default: Optional[bool] = None
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
     priority: Optional[int] = None
     """
     Priority order for evals (lower number = higher priority) to determine primary
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index d58dd591..d1999550 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -53,6 +53,9 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
     enabled: bool
     """Allows the evaluation to be disabled without removing it"""
 
+    is_default: bool
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
     priority: Optional[int]
     """
     Priority order for evals (lower number = higher priority) to determine primary
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index f7c3f017..8ba69a14 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -50,6 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                                 "name": "name",
                                 "context_identifier": "context_identifier",
                                 "enabled": True,
+                                "is_default": True,
                                 "priority": 0,
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
@@ -218,6 +219,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                                 "name": "name",
                                 "context_identifier": "context_identifier",
                                 "enabled": True,
+                                "is_default": True,
                                 "priority": 0,
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
@@ -684,6 +686,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                                 "name": "name",
                                 "context_identifier": "context_identifier",
                                 "enabled": True,
+                                "is_default": True,
                                 "priority": 0,
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
@@ -852,6 +855,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                                 "name": "name",
                                 "context_identifier": "context_identifier",
                                 "enabled": True,
+                                "is_default": True,
                                 "priority": 0,
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",

From 01efedbbd10ae470fb13bb84fffb7e21a5f45885 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 3 Jun 2025 02:20:36 +0000
Subject: [PATCH 152/227] chore(docs): remove reference to rye shell

---
 CONTRIBUTING.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b9fa9a13..548ff4c7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,8 +17,7 @@ $ rye sync --all-features
 You can then run scripts using `rye run python script.py` or by activating the virtual environment:
 
 ```sh
-$ rye shell
-# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
+# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
 $ source .venv/bin/activate
 
 # now you can omit the `rye run` prefix

From 56f90646a781d4651c7d2cb3b1e65350d9e18371 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 3 Jun 2025 02:32:47 +0000
Subject: [PATCH 153/227] chore(docs): remove unnecessary param examples

---
 README.md | 77 +------------------------------------------------------
 1 file changed, 1 insertion(+), 76 deletions(-)

diff --git a/README.md b/README.md
index 43251e6e..05a9dc97 100644
--- a/README.md
+++ b/README.md
@@ -155,82 +155,7 @@ from codex import Codex
 client = Codex()
 
 project_return_schema = client.projects.create(
-    config={
-        "clustering_use_llm_matching": True,
-        "eval_config": {
-            "custom_evals": {
-                "evals": {
-                    "foo": {
-                        "criteria": "criteria",
-                        "eval_key": "eval_key",
-                        "name": "name",
-                        "context_identifier": "context_identifier",
-                        "enabled": True,
-                        "is_default": True,
-                        "priority": 0,
-                        "query_identifier": "query_identifier",
-                        "response_identifier": "response_identifier",
-                        "should_escalate": True,
-                        "threshold": 0,
-                        "threshold_direction": "above",
-                    }
-                }
-            },
-            "default_evals": {
-                "context_sufficiency": {
-                    "eval_key": "eval_key",
-                    "name": "name",
-                    "enabled": True,
-                    "priority": 0,
-                    "should_escalate": True,
-                    "threshold": 0,
-                    "threshold_direction": "above",
-                },
-                "query_ease": {
-                    "eval_key": "eval_key",
-                    "name": "name",
-                    "enabled": True,
-                    "priority": 0,
-                    "should_escalate": True,
-                    "threshold": 0,
-                    "threshold_direction": "above",
-                },
-                "response_groundedness": {
-                    "eval_key": "eval_key",
-                    "name": "name",
-                    "enabled": True,
-                    "priority": 0,
-                    "should_escalate": True,
-                    "threshold": 0,
-                    "threshold_direction": "above",
-                },
-                "response_helpfulness": {
-                    "eval_key": "eval_key",
-                    "name": "name",
-                    "enabled": True,
-                    "priority": 0,
-                    "should_escalate": True,
-                    "threshold": 0,
-                    "threshold_direction": "above",
-                },
-                "trustworthiness": {
-                    "eval_key": "eval_key",
-                    "name": "name",
-                    "enabled": True,
-                    "priority": 0,
-                    "should_escalate": True,
-                    "threshold": 0,
-                    "threshold_direction": "above",
-                },
-            },
-        },
-        "llm_matching_model": "llm_matching_model",
-        "llm_matching_quality_preset": "llm_matching_quality_preset",
-        "lower_llm_match_distance_threshold": 0,
-        "max_distance": 0,
-        "query_use_llm_matching": True,
-        "upper_llm_match_distance_threshold": 0,
-    },
+    config={},
     name="name",
     organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
 )

From d680d32f5667de458784b91d3f1d2ddde1fd28c2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 3 Jun 2025 03:34:08 +0000
Subject: [PATCH 154/227] feat(client): add follow_redirects request option

---
 src/codex/_base_client.py |  6 +++++
 src/codex/_models.py      |  2 ++
 src/codex/_types.py       |  2 ++
 tests/test_client.py      | 54 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 64 insertions(+)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 0bf6ca7f..dff802e4 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -960,6 +960,9 @@ def request(
             if self.custom_auth is not None:
                 kwargs["auth"] = self.custom_auth
 
+            if options.follow_redirects is not None:
+                kwargs["follow_redirects"] = options.follow_redirects
+
             log.debug("Sending HTTP Request: %s %s", request.method, request.url)
 
             response = None
@@ -1460,6 +1463,9 @@ async def request(
             if self.custom_auth is not None:
                 kwargs["auth"] = self.custom_auth
 
+            if options.follow_redirects is not None:
+                kwargs["follow_redirects"] = options.follow_redirects
+
             log.debug("Sending HTTP Request: %s %s", request.method, request.url)
 
             response = None
diff --git a/src/codex/_models.py b/src/codex/_models.py
index 798956f1..4f214980 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
     idempotency_key: str
     json_data: Body
     extra_json: AnyMapping
+    follow_redirects: bool
 
 
 @final
@@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel):
     files: Union[HttpxRequestFiles, None] = None
     idempotency_key: Union[str, None] = None
     post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
+    follow_redirects: Union[bool, None] = None
 
     # It should be noted that we cannot use `json` here as that would override
     # a BaseModel method in an incompatible fashion.
diff --git a/src/codex/_types.py b/src/codex/_types.py
index dfa51c2f..f2d17a1f 100644
--- a/src/codex/_types.py
+++ b/src/codex/_types.py
@@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False):
     params: Query
     extra_json: AnyMapping
     idempotency_key: str
+    follow_redirects: bool
 
 
 # Sentinel class used until PEP 0661 is accepted
@@ -215,3 +216,4 @@ class _GenericAlias(Protocol):
 
 class HttpxSendArgs(TypedDict, total=False):
     auth: httpx.Auth
+    follow_redirects: bool
diff --git a/tests/test_client.py b/tests/test_client.py
index 2d356fac..bc381273 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -803,6 +803,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
 
         assert response.http_request.headers.get("x-stainless-retry-count") == "42"
 
+    @pytest.mark.respx(base_url=base_url)
+    def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+        # Test that the default follow_redirects=True allows following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+        respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+        response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert response.json() == {"status": "ok"}
+
+    @pytest.mark.respx(base_url=base_url)
+    def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+        # Test that follow_redirects=False prevents following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+
+        with pytest.raises(APIStatusError) as exc_info:
+            self.client.post(
+                "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
+            )
+
+        assert exc_info.value.response.status_code == 302
+        assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
+
 
 class TestAsyncCodex:
     client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
@@ -1609,3 +1636,30 @@ async def test_main() -> None:
                     raise AssertionError("calling get_platform using asyncify resulted in a hung process")
 
                 time.sleep(0.1)
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+        # Test that the default follow_redirects=True allows following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+        respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+        response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+        assert response.status_code == 200
+        assert response.json() == {"status": "ok"}
+
+    @pytest.mark.respx(base_url=base_url)
+    async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+        # Test that follow_redirects=False prevents following redirects
+        respx_mock.post("/redirect").mock(
+            return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+        )
+
+        with pytest.raises(APIStatusError) as exc_info:
+            await self.client.post(
+                "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
+            )
+
+        assert exc_info.value.response.status_code == 302
+        assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"

From e54ca45ab07da8e826c4c084385aeb3fbb4816d7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 3 Jun 2025 22:17:18 +0000
Subject: [PATCH 155/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index ddf72408..145bcaa1 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: dfccb5c181396678a22b9c079847889f
+openapi_spec_hash: 12f575bae07a188ff130fdee07d46312
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 4ba26a2a2514252b745266eea17bc6d901205fd4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 5 Jun 2025 15:17:31 +0000
Subject: [PATCH 156/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 145bcaa1..82cbfb62 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 12f575bae07a188ff130fdee07d46312
+openapi_spec_hash: a8387ccffe9a593cea310f37eb64ea0e
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 25d27632e4cdfb4ceaed75e91ba7049843ded17a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 9 Jun 2025 17:17:08 +0000
Subject: [PATCH 157/227] feat(api): api update

---
 .stats.yml                                   | 2 +-
 src/codex/types/project_create_params.py     | 2 +-
 src/codex/types/project_list_response.py     | 2 +-
 src/codex/types/project_retrieve_response.py | 2 +-
 src/codex/types/project_return_schema.py     | 2 +-
 src/codex/types/project_update_params.py     | 2 +-
 tests/api_resources/test_projects.py         | 8 ++++----
 7 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 82cbfb62..0a2b18bb 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: a8387ccffe9a593cea310f37eb64ea0e
+openapi_spec_hash: ded11525d5fce121fac9be1f434c42af
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 31427557..13338463 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -305,7 +305,7 @@ class Config(TypedDict, total=False):
 
     llm_matching_model: str
 
-    llm_matching_quality_preset: str
+    llm_matching_quality_preset: Literal["best", "high", "medium", "low", "base"]
 
     lower_llm_match_distance_threshold: float
 
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index d4805732..9051542a 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -297,7 +297,7 @@ class ProjectConfig(BaseModel):
 
     llm_matching_model: Optional[str] = None
 
-    llm_matching_quality_preset: Optional[str] = None
+    llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None
 
     lower_llm_match_distance_threshold: Optional[float] = None
 
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index fb62cff3..5cf4f32b 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -296,7 +296,7 @@ class Config(BaseModel):
 
     llm_matching_model: Optional[str] = None
 
-    llm_matching_quality_preset: Optional[str] = None
+    llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None
 
     lower_llm_match_distance_threshold: Optional[float] = None
 
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 420ec6e7..979be221 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -296,7 +296,7 @@ class Config(BaseModel):
 
     llm_matching_model: Optional[str] = None
 
-    llm_matching_quality_preset: Optional[str] = None
+    llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None
 
     lower_llm_match_distance_threshold: Optional[float] = None
 
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index d1999550..6df16f43 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -303,7 +303,7 @@ class Config(TypedDict, total=False):
 
     llm_matching_model: str
 
-    llm_matching_quality_preset: str
+    llm_matching_quality_preset: Literal["best", "high", "medium", "low", "base"]
 
     lower_llm_match_distance_threshold: float
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 8ba69a14..7ca0ff9f 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -109,7 +109,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                     },
                 },
                 "llm_matching_model": "llm_matching_model",
-                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "llm_matching_quality_preset": "best",
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
@@ -278,7 +278,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                     },
                 },
                 "llm_matching_model": "llm_matching_model",
-                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "llm_matching_quality_preset": "best",
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
@@ -745,7 +745,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                     },
                 },
                 "llm_matching_model": "llm_matching_model",
-                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "llm_matching_quality_preset": "best",
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
@@ -914,7 +914,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                     },
                 },
                 "llm_matching_model": "llm_matching_model",
-                "llm_matching_quality_preset": "llm_matching_quality_preset",
+                "llm_matching_quality_preset": "best",
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,

From 75e6c2516d58b78ff2701af66cec0a5c02e9e49d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 10 Jun 2025 23:17:07 +0000
Subject: [PATCH 158/227] feat(api): api update

---
 .stats.yml                                   | 2 +-
 src/codex/resources/projects/projects.py     | 8 ++++++++
 src/codex/types/project_create_params.py     | 2 ++
 src/codex/types/project_list_response.py     | 2 ++
 src/codex/types/project_retrieve_response.py | 2 ++
 src/codex/types/project_return_schema.py     | 2 ++
 src/codex/types/project_update_params.py     | 2 ++
 tests/api_resources/test_projects.py         | 4 ++++
 8 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 0a2b18bb..735d7e30 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: ded11525d5fce121fac9be1f434c42af
+openapi_spec_hash: b342ef2514a2e44178169d241aff9273
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index cf8c0f82..5fc3428c 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -98,6 +98,7 @@ def create(
         config: project_create_params.Config,
         name: str,
         organization_id: str,
+        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -125,6 +126,7 @@ def create(
                     "config": config,
                     "name": name,
                     "organization_id": organization_id,
+                    "auto_clustering_enabled": auto_clustering_enabled,
                     "description": description,
                 },
                 project_create_params.ProjectCreateParams,
@@ -174,6 +176,7 @@ def update(
         *,
         config: project_update_params.Config,
         name: str,
+        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -202,6 +205,7 @@ def update(
                 {
                     "config": config,
                     "name": name,
+                    "auto_clustering_enabled": auto_clustering_enabled,
                     "description": description,
                 },
                 project_update_params.ProjectUpdateParams,
@@ -641,6 +645,7 @@ async def create(
         config: project_create_params.Config,
         name: str,
         organization_id: str,
+        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -668,6 +673,7 @@ async def create(
                     "config": config,
                     "name": name,
                     "organization_id": organization_id,
+                    "auto_clustering_enabled": auto_clustering_enabled,
                     "description": description,
                 },
                 project_create_params.ProjectCreateParams,
@@ -717,6 +723,7 @@ async def update(
         *,
         config: project_update_params.Config,
         name: str,
+        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -745,6 +752,7 @@ async def update(
                 {
                     "config": config,
                     "name": name,
+                    "auto_clustering_enabled": auto_clustering_enabled,
                     "description": description,
                 },
                 project_update_params.ProjectUpdateParams,
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 13338463..4d9d31f5 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -27,6 +27,8 @@ class ProjectCreateParams(TypedDict, total=False):
 
     organization_id: Required[str]
 
+    auto_clustering_enabled: bool
+
     description: Optional[str]
 
 
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 9051542a..465ed157 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -323,6 +323,8 @@ class Project(BaseModel):
 
     updated_at: datetime
 
+    auto_clustering_enabled: Optional[bool] = None
+
     description: Optional[str] = None
 
     unanswered_entries_count: Optional[int] = None
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index 5cf4f32b..53624096 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -322,6 +322,8 @@ class ProjectRetrieveResponse(BaseModel):
 
     updated_at: datetime
 
+    auto_clustering_enabled: Optional[bool] = None
+
     custom_rank_enabled: Optional[bool] = None
 
     description: Optional[str] = None
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 979be221..2ad7433a 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -322,4 +322,6 @@ class ProjectReturnSchema(BaseModel):
 
     updated_at: datetime
 
+    auto_clustering_enabled: Optional[bool] = None
+
     description: Optional[str] = None
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 6df16f43..fc6c52d4 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -25,6 +25,8 @@ class ProjectUpdateParams(TypedDict, total=False):
 
     name: Required[str]
 
+    auto_clustering_enabled: bool
+
     description: Optional[str]
 
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 7ca0ff9f..4c95e587 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -117,6 +117,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             },
             name="name",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            auto_clustering_enabled=True,
             description="description",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
@@ -285,6 +286,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                 "upper_llm_match_distance_threshold": 0,
             },
             name="name",
+            auto_clustering_enabled=True,
             description="description",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
@@ -753,6 +755,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             },
             name="name",
             organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            auto_clustering_enabled=True,
             description="description",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
@@ -921,6 +924,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                 "upper_llm_match_distance_threshold": 0,
             },
             name="name",
+            auto_clustering_enabled=True,
             description="description",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])

From d9358ed4031ddb58d1f37955855d7841a0a9bcc1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 13 Jun 2025 02:06:17 +0000
Subject: [PATCH 159/227] chore(tests): run tests in parallel

---
 pyproject.toml        | 3 ++-
 requirements-dev.lock | 4 ++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 8a5a62c7..68924033 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -54,6 +54,7 @@ dev-dependencies = [
     "importlib-metadata>=6.7.0",
     "rich>=13.7.1",
     "nest_asyncio==1.6.0",
+    "pytest-xdist>=3.6.1",
 ]
 
 [tool.rye.scripts]
@@ -125,7 +126,7 @@ replacement = '[\1](https://github.com/cleanlab/codex-python/tree/main/\g<2>)'
 
 [tool.pytest.ini_options]
 testpaths = ["tests"]
-addopts = "--tb=short"
+addopts = "--tb=short -n auto"
 xfail_strict = true
 asyncio_mode = "auto"
 asyncio_default_fixture_loop_scope = "session"
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 7fcfe657..9e127b74 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -30,6 +30,8 @@ distro==1.8.0
 exceptiongroup==1.2.2
     # via anyio
     # via pytest
+execnet==2.1.1
+    # via pytest-xdist
 filelock==3.12.4
     # via virtualenv
 h11==0.14.0
@@ -72,7 +74,9 @@ pygments==2.18.0
 pyright==1.1.399
 pytest==8.3.3
     # via pytest-asyncio
+    # via pytest-xdist
 pytest-asyncio==0.24.0
+pytest-xdist==3.7.0
 python-dateutil==2.8.2
     # via time-machine
 pytz==2023.3.post1

From 35ff0fdfcdbed3ab925b9aa8abb44530d127f2ac Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 13 Jun 2025 02:32:12 +0000
Subject: [PATCH 160/227] fix(client): correctly parse binary response | stream

---
 src/codex/_base_client.py | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index dff802e4..6ef7dd5f 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -1071,7 +1071,14 @@ def _process_response(
     ) -> ResponseT:
         origin = get_origin(cast_to) or cast_to
 
-        if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
+        if (
+            inspect.isclass(origin)
+            and issubclass(origin, BaseAPIResponse)
+            # we only want to actually return the custom BaseAPIResponse class if we're
+            # returning the raw response, or if we're not streaming SSE, as if we're streaming
+            # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+            and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+        ):
             if not issubclass(origin, APIResponse):
                 raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
 
@@ -1574,7 +1581,14 @@ async def _process_response(
     ) -> ResponseT:
         origin = get_origin(cast_to) or cast_to
 
-        if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
+        if (
+            inspect.isclass(origin)
+            and issubclass(origin, BaseAPIResponse)
+            # we only want to actually return the custom BaseAPIResponse class if we're
+            # returning the raw response, or if we're not streaming SSE, as if we're streaming
+            # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+            and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+        ):
             if not issubclass(origin, AsyncAPIResponse):
                 raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
 

From f254fb4d0c15230a8b6748a000f3db5d3fc0d558 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 16 Jun 2025 18:17:09 +0000
Subject: [PATCH 161/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 735d7e30..c98d193e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: b342ef2514a2e44178169d241aff9273
+openapi_spec_hash: 392a7547e79deafa47b8c452f0f5b79e
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5

From 0097703d30c4030e4a03bdf7ed005ddcb7e1353b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 16 Jun 2025 22:17:08 +0000
Subject: [PATCH 162/227] feat(api): api update

---
 .stats.yml                               | 2 +-
 src/codex/resources/projects/projects.py | 8 ++++----
 src/codex/types/project_list_params.py   | 2 +-
 src/codex/types/project_list_response.py | 2 +-
 tests/api_resources/test_projects.py     | 4 ++--
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index c98d193e..09b48e62 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 392a7547e79deafa47b8c452f0f5b79e
+openapi_spec_hash: 66a1eb040f577f552228baf4ddbd17d0
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 5fc3428c..b048b284 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -219,7 +219,7 @@ def update(
     def list(
         self,
         *,
-        include_entry_counts: bool | NotGiven = NOT_GIVEN,
+        include_unaddressed_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
@@ -254,7 +254,7 @@ def list(
                 timeout=timeout,
                 query=maybe_transform(
                     {
-                        "include_entry_counts": include_entry_counts,
+                        "include_unaddressed_counts": include_unaddressed_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
@@ -766,7 +766,7 @@ async def update(
     async def list(
         self,
         *,
-        include_entry_counts: bool | NotGiven = NOT_GIVEN,
+        include_unaddressed_counts: bool | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
@@ -801,7 +801,7 @@ async def list(
                 timeout=timeout,
                 query=await async_maybe_transform(
                     {
-                        "include_entry_counts": include_entry_counts,
+                        "include_unaddressed_counts": include_unaddressed_counts,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py
index 0ab3b84b..0c4ec1bd 100644
--- a/src/codex/types/project_list_params.py
+++ b/src/codex/types/project_list_params.py
@@ -9,7 +9,7 @@
 
 
 class ProjectListParams(TypedDict, total=False):
-    include_entry_counts: bool
+    include_unaddressed_counts: bool
 
     limit: int
 
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 465ed157..a7df3f38 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -327,7 +327,7 @@ class Project(BaseModel):
 
     description: Optional[str] = None
 
-    unanswered_entries_count: Optional[int] = None
+    unaddressed_count: Optional[int] = None
 
 
 class ProjectListResponse(BaseModel):
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 4c95e587..cf6b3171 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -341,7 +341,7 @@ def test_method_list(self, client: Codex) -> None:
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
-            include_entry_counts=True,
+            include_unaddressed_counts=True,
             limit=1,
             offset=0,
             order="asc",
@@ -979,7 +979,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
-            include_entry_counts=True,
+            include_unaddressed_counts=True,
             limit=1,
             offset=0,
             order="asc",

From 20c15ed6c46988c5ac3c35dfb99e036e146b68c3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 17 Jun 2025 02:34:19 +0000
Subject: [PATCH 163/227] chore(tests): add tests for httpx client
 instantiation & proxies

---
 tests/test_client.py | 53 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 52 insertions(+), 1 deletion(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index bc381273..d5815d3b 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -27,7 +27,14 @@
 from codex._models import BaseModel, FinalRequestOptions
 from codex._constants import RAW_RESPONSE_HEADER
 from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
-from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options
+from codex._base_client import (
+    DEFAULT_TIMEOUT,
+    HTTPX_DEFAULT_TIMEOUT,
+    BaseClient,
+    DefaultHttpxClient,
+    DefaultAsyncHttpxClient,
+    make_request_options,
+)
 from codex.types.project_create_params import ProjectCreateParams
 
 from .utils import update_env
@@ -803,6 +810,28 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
 
         assert response.http_request.headers.get("x-stainless-retry-count") == "42"
 
+    def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+        # Test that the proxy environment variables are set correctly
+        monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+
+        client = DefaultHttpxClient()
+
+        mounts = tuple(client._mounts.items())
+        assert len(mounts) == 1
+        assert mounts[0][0].pattern == "https://"
+
+    @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+    def test_default_client_creation(self) -> None:
+        # Ensure that the client can be initialized without any exceptions
+        DefaultHttpxClient(
+            verify=True,
+            cert=None,
+            trust_env=True,
+            http1=True,
+            http2=False,
+            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+        )
+
     @pytest.mark.respx(base_url=base_url)
     def test_follow_redirects(self, respx_mock: MockRouter) -> None:
         # Test that the default follow_redirects=True allows following redirects
@@ -1637,6 +1666,28 @@ async def test_main() -> None:
 
                 time.sleep(0.1)
 
+    async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+        # Test that the proxy environment variables are set correctly
+        monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+
+        client = DefaultAsyncHttpxClient()
+
+        mounts = tuple(client._mounts.items())
+        assert len(mounts) == 1
+        assert mounts[0][0].pattern == "https://"
+
+    @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+    async def test_default_client_creation(self) -> None:
+        # Ensure that the client can be initialized without any exceptions
+        DefaultAsyncHttpxClient(
+            verify=True,
+            cert=None,
+            trust_env=True,
+            http1=True,
+            http2=False,
+            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+        )
+
     @pytest.mark.respx(base_url=base_url)
     async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
         # Test that the default follow_redirects=True allows following redirects

From 52a5afd3e98d6f61f89e0b0c4caf67ba88c4292e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 17 Jun 2025 04:04:00 +0000
Subject: [PATCH 164/227] chore(internal): update conftest.py

---
 tests/conftest.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/conftest.py b/tests/conftest.py
index c93053b1..05c77729 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,5 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
 from __future__ import annotations
 
 import os

From 225d0b63ef2fef2836323d2e511f5013ca0f7ce6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 17 Jun 2025 06:33:35 +0000
Subject: [PATCH 165/227] chore(ci): enable for pull requests

---
 .github/workflows/ci.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c02247a3..2f7778ae 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,10 @@ on:
       - 'integrated/**'
       - 'stl-preview-head/**'
       - 'stl-preview-base/**'
+  pull_request:
+    branches-ignore:
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
 
 jobs:
   lint:

From 488e5d1d114b35cab73d6ad56dd783ddc3bea0b8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 18 Jun 2025 02:08:16 +0000
Subject: [PATCH 166/227] chore(readme): update badges

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 05a9dc97..a3b3288d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Codex SDK API library
 
-[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/)
+[![PyPI version](<https://img.shields.io/pypi/v/codex-sdk.svg?label=pypi%20(stable)>)](https://pypi.org/project/codex-sdk/)
 
 The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,

From 94d3c8533085aa0f2f9551d4202cb815cc07cd25 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 18 Jun 2025 05:44:40 +0000
Subject: [PATCH 167/227] fix(tests): fix: tests which call HTTP endpoints
 directly with the example parameters

---
 tests/test_client.py | 73 ++++++++++----------------------------------
 1 file changed, 16 insertions(+), 57 deletions(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index d5815d3b..9cf7e943 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -23,9 +23,7 @@
 
 from codex import Codex, AsyncCodex, APIResponseValidationError
 from codex._types import Omit
-from codex._utils import maybe_transform
 from codex._models import BaseModel, FinalRequestOptions
-from codex._constants import RAW_RESPONSE_HEADER
 from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
 from codex._base_client import (
     DEFAULT_TIMEOUT,
@@ -35,7 +33,6 @@
     DefaultAsyncHttpxClient,
     make_request_options,
 )
-from codex.types.project_create_params import ProjectCreateParams
 
 from .utils import update_env
 
@@ -683,44 +680,25 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str
 
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+    def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Codex) -> None:
         respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
 
         with pytest.raises(APITimeoutError):
-            self.client.post(
-                "/api/projects/",
-                body=cast(
-                    object,
-                    maybe_transform(
-                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
-                        ProjectCreateParams,
-                    ),
-                ),
-                cast_to=httpx.Response,
-                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
-            )
+            client.projects.with_streaming_response.create(
+                config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+            ).__enter__()
 
         assert _get_open_connections(self.client) == 0
 
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+    def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Codex) -> None:
         respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500))
 
         with pytest.raises(APIStatusError):
-            self.client.post(
-                "/api/projects/",
-                body=cast(
-                    object,
-                    maybe_transform(
-                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
-                        ProjectCreateParams,
-                    ),
-                ),
-                cast_to=httpx.Response,
-                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
-            )
-
+            client.projects.with_streaming_response.create(
+                config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+            ).__enter__()
         assert _get_open_connections(self.client) == 0
 
     @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -1489,44 +1467,25 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte
 
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+    async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncCodex) -> None:
         respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error"))
 
         with pytest.raises(APITimeoutError):
-            await self.client.post(
-                "/api/projects/",
-                body=cast(
-                    object,
-                    maybe_transform(
-                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
-                        ProjectCreateParams,
-                    ),
-                ),
-                cast_to=httpx.Response,
-                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
-            )
+            await async_client.projects.with_streaming_response.create(
+                config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+            ).__aenter__()
 
         assert _get_open_connections(self.client) == 0
 
     @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
     @pytest.mark.respx(base_url=base_url)
-    async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
+    async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncCodex) -> None:
         respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500))
 
         with pytest.raises(APIStatusError):
-            await self.client.post(
-                "/api/projects/",
-                body=cast(
-                    object,
-                    maybe_transform(
-                        dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"),
-                        ProjectCreateParams,
-                    ),
-                ),
-                cast_to=httpx.Response,
-                options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
-            )
-
+            await async_client.projects.with_streaming_response.create(
+                config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"
+            ).__aenter__()
         assert _get_open_connections(self.client) == 0
 
     @pytest.mark.parametrize("failures_before_success", [0, 2, 4])

From 62ccb8f1269b2f38a250c71597b570cdad40fe7f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 18 Jun 2025 18:17:12 +0000
Subject: [PATCH 168/227] feat(api): api update

---
 .stats.yml                                            | 2 +-
 src/codex/types/organization_list_members_response.py | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 09b48e62..40a2bb70 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 66a1eb040f577f552228baf4ddbd17d0
+openapi_spec_hash: 947c1396f52c4f78645b067ac0b708e5
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py
index 37897d56..1fa593ea 100644
--- a/src/codex/types/organization_list_members_response.py
+++ b/src/codex/types/organization_list_members_response.py
@@ -13,5 +13,7 @@ class OrganizationListMembersResponseItem(BaseModel):
 
     name: str
 
+    user_id: str
+
 
 OrganizationListMembersResponse: TypeAlias = List[OrganizationListMembersResponseItem]

From 13c1d7619e23cbb7d0d34d5a085ffe2977339349 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 19 Jun 2025 02:47:46 +0000
Subject: [PATCH 169/227] docs(client): fix httpx.Timeout documentation
 reference

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a3b3288d..ae489b6e 100644
--- a/README.md
+++ b/README.md
@@ -235,7 +235,7 @@ client.with_options(max_retries=5).projects.create(
 ### Timeouts
 
 By default requests time out after 1 minute. You can configure this with a `timeout` option,
-which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
+which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
 
 ```python
 from codex import Codex

From 8d6abb80de088c126aec035d2b0b4425d4423e51 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 20 Jun 2025 20:17:22 +0000
Subject: [PATCH 170/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 src/codex/resources/projects/projects.py      |  96 +++---
 src/codex/resources/tlm.py                    | 164 +++++------
 src/codex/types/project_validate_params.py    | 278 ++++++++++++++++--
 .../types/projects/entry_query_params.py      | 243 ++++++++++++++-
 src/codex/types/tlm_prompt_params.py          |  41 ++-
 src/codex/types/tlm_score_params.py           |  41 ++-
 tests/api_resources/projects/test_entries.py  |  16 +
 tests/api_resources/test_projects.py          |  14 +
 9 files changed, 690 insertions(+), 205 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 40a2bb70..9317ce1b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 44
-openapi_spec_hash: 947c1396f52c4f78645b067ac0b708e5
+openapi_spec_hash: eeb8ebc5600523bdfad046381a929572
 config_hash: 659f65b6ccf5612986f920f7f9abbcb5
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index b048b284..11b95d4d 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -3,7 +3,7 @@
 from __future__ import annotations
 
 import typing_extensions
-from typing import Dict, List, Optional
+from typing import Dict, List, Iterable, Optional
 from typing_extensions import Literal
 
 import httpx
@@ -436,6 +436,7 @@ def validate(
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
+        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
@@ -465,6 +466,10 @@ def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
+          messages: Optional message history to provide conversation context for the query. Used to
+              rewrite query into a self-contained version of itself. If not provided, the
+              query will be treated as self-contained.
+
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
               (learn about quality presets in the TLM [initialization method](./#class-tlm)).
@@ -490,27 +495,24 @@ def validate(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -536,7 +538,7 @@ def validate(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -588,6 +590,7 @@ def validate(
                     "custom_eval_thresholds": custom_eval_thresholds,
                     "custom_metadata": custom_metadata,
                     "eval_scores": eval_scores,
+                    "messages": messages,
                     "options": options,
                     "quality_preset": quality_preset,
                     "task": task,
@@ -985,6 +988,7 @@ async def validate(
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
+        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
@@ -1014,6 +1018,10 @@ async def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
+          messages: Optional message history to provide conversation context for the query. Used to
+              rewrite query into a self-contained version of itself. If not provided, the
+              query will be treated as self-contained.
+
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
               (learn about quality presets in the TLM [initialization method](./#class-tlm)).
@@ -1039,27 +1047,24 @@ async def validate(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -1085,7 +1090,7 @@ async def validate(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -1137,6 +1142,7 @@ async def validate(
                     "custom_eval_thresholds": custom_eval_thresholds,
                     "custom_metadata": custom_metadata,
                     "eval_scores": eval_scores,
+                    "messages": messages,
                     "options": options,
                     "quality_preset": quality_preset,
                     "task": task,
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index 5e4cd7e1..12ff6c0d 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -89,27 +89,24 @@ def prompt(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -135,7 +132,7 @@ def prompt(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -230,27 +227,24 @@ def score(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -276,7 +270,7 @@ def score(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -387,27 +381,24 @@ async def prompt(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -433,7 +424,7 @@ async def prompt(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -528,27 +519,24 @@ async def score(
                 `use_self_reflection` = True.
               - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
                 `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a cheaper self-reflection will be used to compute the
-                trustworthiness score.
-
-              By default, the TLM uses the "medium" quality preset. The default base LLM
-              `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-              You can set custom values for these arguments regardless of the quality preset
-              specified.
-
-              Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-              "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-              "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-              "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-              faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-              "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-              "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-              "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-              for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-              "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-              "gpt-4.1-nano", "nova-micro".
+                "base" preset, a faster self-reflection is employed.
+
+              By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+              `model`, and `max_tokens` is set to 512. You can set custom values for these
+              arguments regardless of the quality preset specified.
+
+              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+              better results, faster models yield faster results). - Models still in beta:
+              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+              latency/costs: "gpt-4.1-nano", "nova-micro".
 
                   max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
                   Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -574,7 +562,7 @@ async def score(
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-                  Supported similarity measures include: "semantic" (based on natural language inference),
+                  Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
                   and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index a855aa6f..8b38ebfa 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -2,12 +2,40 @@
 
 from __future__ import annotations
 
-from typing import Dict, List, Iterable, Optional
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing import Dict, List, Union, Iterable, Optional
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
 
 from .._utils import PropertyInfo
 
-__all__ = ["ProjectValidateParams", "Options"]
+__all__ = [
+    "ProjectValidateParams",
+    "Message",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParam",
+    "MessageChatCompletionUserMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamContentUnionMember1FileFile",
+    "MessageChatCompletionAssistantMessageParam",
+    "MessageChatCompletionAssistantMessageParamAudio",
+    "MessageChatCompletionAssistantMessageParamContentUnionMember1",
+    "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "MessageChatCompletionAssistantMessageParamFunctionCall",
+    "MessageChatCompletionAssistantMessageParamToolCall",
+    "MessageChatCompletionAssistantMessageParamToolCallFunction",
+    "MessageChatCompletionToolMessageParam",
+    "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionFunctionMessageParam",
+    "Options",
+]
 
 
 class ProjectValidateParams(TypedDict, total=False):
@@ -38,6 +66,13 @@ class ProjectValidateParams(TypedDict, total=False):
     If not provided, TLM will be used to generate scores.
     """
 
+    messages: Optional[Iterable[Message]]
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
     options: Optional[Options]
     """
     Typed dict of advanced configuration options for the Trustworthy Language Model.
@@ -65,27 +100,24 @@ class ProjectValidateParams(TypedDict, total=False):
       `use_self_reflection` = True.
     - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
       `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a cheaper self-reflection will be used to compute the
-      trustworthiness score.
-
-    By default, the TLM uses the "medium" quality preset. The default base LLM
-    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-    You can set custom values for these arguments regardless of the quality preset
-    specified.
-
-    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-    "gpt-4.1-nano", "nova-micro".
+      "base" preset, a faster self-reflection is employed.
+
+    By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+    `model`, and `max_tokens` is set to 512. You can set custom values for these
+    arguments regardless of the quality preset specified.
+
+    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+    better results, faster models yield faster results). - Models still in beta:
+    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+    latency/costs: "gpt-4.1-nano", "nova-micro".
 
         max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
         Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -111,7 +143,7 @@ class ProjectValidateParams(TypedDict, total=False):
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-        Supported similarity measures include: "semantic" (based on natural language inference),
+        Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
         and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
@@ -143,6 +175,202 @@ class ProjectValidateParams(TypedDict, total=False):
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
 
 
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["developer"]]
+
+    name: str
+
+
+class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class MessageChatCompletionSystemMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["system"]]
+
+    name: str
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+    TypedDict, total=False
+):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    TypedDict, total=False
+):
+    url: Required[str]
+
+    detail: Literal["auto", "low", "high"]
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam(
+    TypedDict, total=False
+):
+    image_url: Required[
+        MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL
+    ]
+
+    type: Required[Literal["image_url"]]
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    TypedDict, total=False
+):
+    data: Required[str]
+
+    format: Required[Literal["wav", "mp3"]]
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam(
+    TypedDict, total=False
+):
+    input_audio: Required[
+        MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+    ]
+
+    type: Required[Literal["input_audio"]]
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False):
+    file_data: str
+
+    file_id: str
+
+    filename: str
+
+
+class MessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False):
+    file: Required[MessageChatCompletionUserMessageParamContentUnionMember1FileFile]
+
+    type: Required[Literal["file"]]
+
+
+MessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam,
+    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    MessageChatCompletionUserMessageParamContentUnionMember1File,
+]
+
+
+class MessageChatCompletionUserMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["user"]]
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False):
+    id: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+    TypedDict, total=False
+):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam(
+    TypedDict, total=False
+):
+    refusal: Required[str]
+
+    type: Required[Literal["refusal"]]
+
+
+MessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False):
+    id: Required[str]
+
+    function: Required[MessageChatCompletionAssistantMessageParamToolCallFunction]
+
+    type: Required[Literal["function"]]
+
+
+class MessageChatCompletionAssistantMessageParam(TypedDict, total=False):
+    role: Required[Literal["assistant"]]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamAudio]
+
+    content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamContentUnionMember1], None]
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamFunctionCall]
+
+    name: str
+
+    refusal: Optional[str]
+
+    tool_calls: Iterable[MessageChatCompletionAssistantMessageParamToolCall]
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class MessageChatCompletionToolMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["tool"]]
+
+    tool_call_id: Required[str]
+
+
+class MessageChatCompletionFunctionMessageParam(TypedDict, total=False):
+    content: Required[Optional[str]]
+
+    name: Required[str]
+
+    role: Required[Literal["function"]]
+
+
+Message: TypeAlias = Union[
+    MessageChatCompletionDeveloperMessageParam,
+    MessageChatCompletionSystemMessageParam,
+    MessageChatCompletionUserMessageParam,
+    MessageChatCompletionAssistantMessageParam,
+    MessageChatCompletionToolMessageParam,
+    MessageChatCompletionFunctionMessageParam,
+]
+
+
 class Options(TypedDict, total=False):
     custom_eval_criteria: Iterable[object]
 
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index 1edabbed..2ba33b82 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -3,11 +3,40 @@
 from __future__ import annotations
 
 from typing import Dict, List, Union, Iterable, Optional
-from typing_extensions import Required, Annotated, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
 
 from ..._utils import PropertyInfo
 
-__all__ = ["EntryQueryParams", "QueryMetadata", "QueryMetadataContextUnionMember3"]
+__all__ = [
+    "EntryQueryParams",
+    "QueryMetadata",
+    "QueryMetadataContextUnionMember3",
+    "QueryMetadataMessage",
+    "QueryMetadataMessageChatCompletionDeveloperMessageParam",
+    "QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryMetadataMessageChatCompletionSystemMessageParam",
+    "QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1",
+    "QueryMetadataMessageChatCompletionUserMessageParam",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File",
+    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile",
+    "QueryMetadataMessageChatCompletionAssistantMessageParam",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamAudio",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamToolCall",
+    "QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction",
+    "QueryMetadataMessageChatCompletionToolMessageParam",
+    "QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1",
+    "QueryMetadataMessageChatCompletionFunctionMessageParam",
+]
 
 
 class EntryQueryParams(TypedDict, total=False):
@@ -47,6 +76,202 @@ class QueryMetadataContextUnionMember3(TypedDict, total=False):
     """Title or heading of the document. Useful for display and context."""
 
 
+class QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class QueryMetadataMessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["developer"]]
+
+    name: str
+
+
+class QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class QueryMetadataMessageChatCompletionSystemMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["system"]]
+
+    name: str
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+    TypedDict, total=False
+):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    TypedDict, total=False
+):
+    url: Required[str]
+
+    detail: Literal["auto", "low", "high"]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam(
+    TypedDict, total=False
+):
+    image_url: Required[
+        QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL
+    ]
+
+    type: Required[Literal["image_url"]]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    TypedDict, total=False
+):
+    data: Required[str]
+
+    format: Required[Literal["wav", "mp3"]]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam(
+    TypedDict, total=False
+):
+    input_audio: Required[
+        QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+    ]
+
+    type: Required[Literal["input_audio"]]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False):
+    file_data: str
+
+    file_id: str
+
+    filename: str
+
+
+class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False):
+    file: Required[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile]
+
+    type: Required[Literal["file"]]
+
+
+QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[
+    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam,
+    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File,
+]
+
+
+class QueryMetadataMessageChatCompletionUserMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["user"]]
+
+    name: str
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False):
+    id: Required[str]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+    TypedDict, total=False
+):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam(
+    TypedDict, total=False
+):
+    refusal: Required[str]
+
+    type: Required[Literal["refusal"]]
+
+
+QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[
+    QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False):
+    id: Required[str]
+
+    function: Required[QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction]
+
+    type: Required[Literal["function"]]
+
+
+class QueryMetadataMessageChatCompletionAssistantMessageParam(TypedDict, total=False):
+    role: Required[Literal["assistant"]]
+
+    audio: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamAudio]
+
+    content: Union[str, Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1], None]
+
+    function_call: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall]
+
+    name: str
+
+    refusal: Optional[str]
+
+    tool_calls: Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamToolCall]
+
+
+class QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
+    text: Required[str]
+
+    type: Required[Literal["text"]]
+
+
+class QueryMetadataMessageChatCompletionToolMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1]]]
+
+    role: Required[Literal["tool"]]
+
+    tool_call_id: Required[str]
+
+
+class QueryMetadataMessageChatCompletionFunctionMessageParam(TypedDict, total=False):
+    content: Required[Optional[str]]
+
+    name: Required[str]
+
+    role: Required[Literal["function"]]
+
+
+QueryMetadataMessage: TypeAlias = Union[
+    QueryMetadataMessageChatCompletionDeveloperMessageParam,
+    QueryMetadataMessageChatCompletionSystemMessageParam,
+    QueryMetadataMessageChatCompletionUserMessageParam,
+    QueryMetadataMessageChatCompletionAssistantMessageParam,
+    QueryMetadataMessageChatCompletionToolMessageParam,
+    QueryMetadataMessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryMetadata(TypedDict, total=False):
     context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None]
     """RAG context used for the query"""
@@ -59,3 +284,17 @@ class QueryMetadata(TypedDict, total=False):
 
     evaluated_response: Optional[str]
     """The response being evaluated from the RAG system(before any remediation)"""
+
+    messages: Optional[Iterable[QueryMetadataMessage]]
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str]
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 94536055..3c04bfc4 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -40,27 +40,24 @@ class TlmPromptParams(TypedDict, total=False):
       `use_self_reflection` = True.
     - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
       `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a cheaper self-reflection will be used to compute the
-      trustworthiness score.
-
-    By default, the TLM uses the "medium" quality preset. The default base LLM
-    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-    You can set custom values for these arguments regardless of the quality preset
-    specified.
-
-    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-    "gpt-4.1-nano", "nova-micro".
+      "base" preset, a faster self-reflection is employed.
+
+    By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+    `model`, and `max_tokens` is set to 512. You can set custom values for these
+    arguments regardless of the quality preset specified.
+
+    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+    better results, faster models yield faster results). - Models still in beta:
+    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+    latency/costs: "gpt-4.1-nano", "nova-micro".
 
         max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
         Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -86,7 +83,7 @@ class TlmPromptParams(TypedDict, total=False):
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-        Supported similarity measures include: "semantic" (based on natural language inference),
+        Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
         and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index a0d90175..95bcc4c4 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -42,27 +42,24 @@ class TlmScoreParams(TypedDict, total=False):
       `use_self_reflection` = True.
     - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
       `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a cheaper self-reflection will be used to compute the
-      trustworthiness score.
-
-    By default, the TLM uses the "medium" quality preset. The default base LLM
-    `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets.
-    You can set custom values for these arguments regardless of the quality preset
-    specified.
-
-    Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini",
-    "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku",
-    "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default =
-    "gpt-4o-mini"): Underlying base LLM to use (better models yield better results,
-    faster models yield faster/cheaper results). - Models still in beta: "o3", "o1",
-    "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano",
-    "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
-    "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models
-    for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet",
-    "claude-3.5-sonnet-v2". - Recommended models for low latency/costs:
-    "gpt-4.1-nano", "nova-micro".
+      "base" preset, a faster self-reflection is employed.
+
+    By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
+    `model`, and `max_tokens` is set to 512. You can set custom values for these
+    arguments regardless of the quality preset specified.
+
+    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
+    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
+    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
+    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
+    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
+    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
+    better results, faster models yield faster results). - Models still in beta:
+    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
+    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
+    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
+    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
+    latency/costs: "gpt-4.1-nano", "nova-micro".
 
         max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
         Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
@@ -88,7 +85,7 @@ class TlmScoreParams(TypedDict, total=False):
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
-        Supported similarity measures include: "semantic" (based on natural language inference),
+        Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
         and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 32b0452e..eb6fd372 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -406,6 +406,14 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
                     "custom_metadata": {},
                     "eval_scores": {"foo": 0},
                     "evaluated_response": "evaluated_response",
+                    "messages": [
+                        {
+                            "content": "string",
+                            "role": "developer",
+                            "name": "name",
+                        }
+                    ],
+                    "original_question": "original_question",
                 },
                 x_client_library_version="x-client-library-version",
                 x_integration_type="x-integration-type",
@@ -894,6 +902,14 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
                     "custom_metadata": {},
                     "eval_scores": {"foo": 0},
                     "evaluated_response": "evaluated_response",
+                    "messages": [
+                        {
+                            "content": "string",
+                            "role": "developer",
+                            "name": "name",
+                        }
+                    ],
+                    "original_question": "original_question",
                 },
                 x_client_library_version="x-client-library-version",
                 x_integration_type="x-integration-type",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index cf6b3171..c2b5b7db 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -593,6 +593,13 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             custom_eval_thresholds={"foo": 0},
             custom_metadata={},
             eval_scores={"foo": 0},
+            messages=[
+                {
+                    "content": "string",
+                    "role": "developer",
+                    "name": "name",
+                }
+            ],
             options={
                 "custom_eval_criteria": [{}],
                 "log": ["string"],
@@ -1231,6 +1238,13 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             custom_eval_thresholds={"foo": 0},
             custom_metadata={},
             eval_scores={"foo": 0},
+            messages=[
+                {
+                    "content": "string",
+                    "role": "developer",
+                    "name": "name",
+                }
+            ],
             options={
                 "custom_eval_criteria": [{}],
                 "log": ["string"],

From 63b8d21601addfa9b8c81e5d53a1841fb1e6214f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 20 Jun 2025 23:30:22 +0000
Subject: [PATCH 171/227] feat(api): add new endpoints

---
 .stats.yml                                    |    4 +-
 api.md                                        |   72 +
 src/codex/resources/projects/__init__.py      |   42 +
 src/codex/resources/projects/evals.py         |  803 +++++++++++
 src/codex/resources/projects/projects.py      |  202 +++
 src/codex/resources/projects/query_logs.py    |  741 ++++++++++
 src/codex/resources/projects/remediations.py  | 1135 +++++++++++++++
 src/codex/types/__init__.py                   |    2 +
 src/codex/types/project_invite_sme_params.py  |   15 +
 .../types/project_invite_sme_response.py      |   11 +
 src/codex/types/projects/__init__.py          |   31 +
 .../types/projects/eval_create_params.py      |   66 +
 .../types/projects/eval_list_response.py      |   69 +
 .../types/projects/eval_update_params.py      |  104 ++
 .../query_log_list_by_group_params.py         |   41 +
 .../query_log_list_by_group_response.py       |   96 ++
 .../projects/query_log_list_groups_params.py  |   38 +
 .../query_log_list_groups_response.py         |   91 ++
 .../types/projects/query_log_list_params.py   |   38 +
 .../types/projects/query_log_list_response.py |   87 ++
 .../projects/query_log_retrieve_response.py   |   79 ++
 .../query_log_start_remediation_response.py   |   33 +
 .../projects/remediation_create_params.py     |   16 +
 .../projects/remediation_create_response.py   |   33 +
 .../remediation_edit_answer_params.py         |   13 +
 .../remediation_edit_answer_response.py       |   33 +
 .../remediation_edit_draft_answer_params.py   |   13 +
 .../remediation_edit_draft_answer_response.py |   33 +
 ...iation_get_resolved_logs_count_response.py |   35 +
 .../types/projects/remediation_list_params.py |   39 +
 ...remediation_list_resolved_logs_response.py |   85 ++
 .../projects/remediation_list_response.py     |   41 +
 .../projects/remediation_pause_response.py    |   33 +
 .../projects/remediation_publish_response.py  |   33 +
 .../projects/remediation_retrieve_response.py |   33 +
 .../projects/remediation_unpause_response.py  |   33 +
 tests/api_resources/projects/test_evals.py    |  679 +++++++++
 .../api_resources/projects/test_query_logs.py |  593 ++++++++
 .../projects/test_remediations.py             | 1224 +++++++++++++++++
 tests/api_resources/test_projects.py          |  109 ++
 40 files changed, 6876 insertions(+), 2 deletions(-)
 create mode 100644 src/codex/resources/projects/evals.py
 create mode 100644 src/codex/resources/projects/query_logs.py
 create mode 100644 src/codex/resources/projects/remediations.py
 create mode 100644 src/codex/types/project_invite_sme_params.py
 create mode 100644 src/codex/types/project_invite_sme_response.py
 create mode 100644 src/codex/types/projects/eval_create_params.py
 create mode 100644 src/codex/types/projects/eval_list_response.py
 create mode 100644 src/codex/types/projects/eval_update_params.py
 create mode 100644 src/codex/types/projects/query_log_list_by_group_params.py
 create mode 100644 src/codex/types/projects/query_log_list_by_group_response.py
 create mode 100644 src/codex/types/projects/query_log_list_groups_params.py
 create mode 100644 src/codex/types/projects/query_log_list_groups_response.py
 create mode 100644 src/codex/types/projects/query_log_list_params.py
 create mode 100644 src/codex/types/projects/query_log_list_response.py
 create mode 100644 src/codex/types/projects/query_log_retrieve_response.py
 create mode 100644 src/codex/types/projects/query_log_start_remediation_response.py
 create mode 100644 src/codex/types/projects/remediation_create_params.py
 create mode 100644 src/codex/types/projects/remediation_create_response.py
 create mode 100644 src/codex/types/projects/remediation_edit_answer_params.py
 create mode 100644 src/codex/types/projects/remediation_edit_answer_response.py
 create mode 100644 src/codex/types/projects/remediation_edit_draft_answer_params.py
 create mode 100644 src/codex/types/projects/remediation_edit_draft_answer_response.py
 create mode 100644 src/codex/types/projects/remediation_get_resolved_logs_count_response.py
 create mode 100644 src/codex/types/projects/remediation_list_params.py
 create mode 100644 src/codex/types/projects/remediation_list_resolved_logs_response.py
 create mode 100644 src/codex/types/projects/remediation_list_response.py
 create mode 100644 src/codex/types/projects/remediation_pause_response.py
 create mode 100644 src/codex/types/projects/remediation_publish_response.py
 create mode 100644 src/codex/types/projects/remediation_retrieve_response.py
 create mode 100644 src/codex/types/projects/remediation_unpause_response.py
 create mode 100644 tests/api_resources/projects/test_evals.py
 create mode 100644 tests/api_resources/projects/test_query_logs.py
 create mode 100644 tests/api_resources/projects/test_remediations.py

diff --git a/.stats.yml b/.stats.yml
index 9317ce1b..bd77f787 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 44
+configured_endpoints: 65
 openapi_spec_hash: eeb8ebc5600523bdfad046381a929572
-config_hash: 659f65b6ccf5612986f920f7f9abbcb5
+config_hash: 63e520502003839482d0dbeb82132064
diff --git a/api.md b/api.md
index 8aac76fd..13e82147 100644
--- a/api.md
+++ b/api.md
@@ -139,6 +139,7 @@ from codex.types import (
     ProjectReturnSchema,
     ProjectRetrieveResponse,
     ProjectListResponse,
+    ProjectInviteSmeResponse,
     ProjectRetrieveAnalyticsResponse,
     ProjectValidateResponse,
 )
@@ -153,6 +154,7 @@ Methods:
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> object</code>
 - <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> object</code>
+- <code title="post /api/projects/{project_id}/notifications">client.projects.<a href="./src/codex/resources/projects/projects.py">invite_sme</a>(project_id, \*\*<a href="src/codex/types/project_invite_sme_params.py">params</a>) -> <a href="./src/codex/types/project_invite_sme_response.py">ProjectInviteSmeResponse</a></code>
 - <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
 - <code title="post /api/projects/{project_id}/validate">client.projects.<a href="./src/codex/resources/projects/projects.py">validate</a>(project_id, \*\*<a href="src/codex/types/project_validate_params.py">params</a>) -> <a href="./src/codex/types/project_validate_response.py">ProjectValidateResponse</a></code>
 
@@ -210,6 +212,76 @@ Methods:
 - <code title="get /api/projects/{project_id}/entries/clusters">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/cluster_list_params.py">params</a>) -> <a href="./src/codex/types/projects/cluster_list_response.py">SyncOffsetPageClusters[ClusterListResponse]</a></code>
 - <code title="get /api/projects/{project_id}/entries/clusters/{representative_entry_id}">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list_variants</a>(representative_entry_id, \*, project_id) -> <a href="./src/codex/types/projects/cluster_list_variants_response.py">ClusterListVariantsResponse</a></code>
 
+## Evals
+
+Types:
+
+```python
+from codex.types.projects import EvalListResponse
+```
+
+Methods:
+
+- <code title="post /api/projects/{project_id}/evals">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/eval_create_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="put /api/projects/{project_id}/evals/{eval_key}">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">update</a>(path_eval_key, \*, project_id, \*\*<a href="src/codex/types/projects/eval_update_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+- <code title="get /api/projects/{project_id}/evals">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">list</a>(project_id) -> <a href="./src/codex/types/projects/eval_list_response.py">EvalListResponse</a></code>
+- <code title="delete /api/projects/{project_id}/evals/{eval_key}">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">delete</a>(eval_key, \*, project_id) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
+
+## QueryLogs
+
+Types:
+
+```python
+from codex.types.projects import (
+    QueryLogRetrieveResponse,
+    QueryLogListResponse,
+    QueryLogListByGroupResponse,
+    QueryLogListGroupsResponse,
+    QueryLogStartRemediationResponse,
+)
+```
+
+Methods:
+
+- <code title="get /api/projects/{project_id}/query_logs/{query_log_id}">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">retrieve</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_retrieve_response.py">QueryLogRetrieveResponse</a></code>
+- <code title="get /api/projects/{project_id}/query_logs/">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_response.py">QueryLogListResponse</a></code>
+- <code title="get /api/projects/{project_id}/query_logs/logs_by_group">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_by_group</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_by_group_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_by_group_response.py">QueryLogListByGroupResponse</a></code>
+- <code title="get /api/projects/{project_id}/query_logs/groups">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_groups</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_groups_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_groups_response.py">QueryLogListGroupsResponse</a></code>
+- <code title="post /api/projects/{project_id}/query_logs/{query_log_id}/start_remediation">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">start_remediation</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_start_remediation_response.py">QueryLogStartRemediationResponse</a></code>
+
+## Remediations
+
+Types:
+
+```python
+from codex.types.projects import (
+    RemediationCreateResponse,
+    RemediationRetrieveResponse,
+    RemediationListResponse,
+    RemediationEditAnswerResponse,
+    RemediationEditDraftAnswerResponse,
+    RemediationGetResolvedLogsCountResponse,
+    RemediationListResolvedLogsResponse,
+    RemediationPauseResponse,
+    RemediationPublishResponse,
+    RemediationUnpauseResponse,
+)
+```
+
+Methods:
+
+- <code title="post /api/projects/{project_id}/remediations/">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/remediation_create_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_create_response.py">RemediationCreateResponse</a></code>
+- <code title="get /api/projects/{project_id}/remediations/{remediation_id}">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">retrieve</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_retrieve_response.py">RemediationRetrieveResponse</a></code>
+- <code title="get /api/projects/{project_id}/remediations/">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/remediation_list_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_list_response.py">RemediationListResponse</a></code>
+- <code title="delete /api/projects/{project_id}/remediations/{remediation_id}">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">delete</a>(remediation_id, \*, project_id) -> None</code>
+- <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/edit_answer">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">edit_answer</a>(remediation_id, \*, project_id, \*\*<a href="src/codex/types/projects/remediation_edit_answer_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_edit_answer_response.py">RemediationEditAnswerResponse</a></code>
+- <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">edit_draft_answer</a>(remediation_id, \*, project_id, \*\*<a href="src/codex/types/projects/remediation_edit_draft_answer_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_edit_draft_answer_response.py">RemediationEditDraftAnswerResponse</a></code>
+- <code title="get /api/projects/{project_id}/remediations/{remediation_id}/resolved_logs_count">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">get_resolved_logs_count</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_get_resolved_logs_count_response.py">RemediationGetResolvedLogsCountResponse</a></code>
+- <code title="get /api/projects/{project_id}/remediations/{remediation_id}/resolved_logs">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">list_resolved_logs</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_list_resolved_logs_response.py">RemediationListResolvedLogsResponse</a></code>
+- <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/pause">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">pause</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_pause_response.py">RemediationPauseResponse</a></code>
+- <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/publish">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">publish</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_publish_response.py">RemediationPublishResponse</a></code>
+- <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/unpause">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">unpause</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_unpause_response.py">RemediationUnpauseResponse</a></code>
+
 # Tlm
 
 Types:
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
index 2c0595d2..178855ac 100644
--- a/src/codex/resources/projects/__init__.py
+++ b/src/codex/resources/projects/__init__.py
@@ -1,5 +1,13 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
+from .evals import (
+    EvalsResource,
+    AsyncEvalsResource,
+    EvalsResourceWithRawResponse,
+    AsyncEvalsResourceWithRawResponse,
+    EvalsResourceWithStreamingResponse,
+    AsyncEvalsResourceWithStreamingResponse,
+)
 from .entries import (
     EntriesResource,
     AsyncEntriesResource,
@@ -24,6 +32,14 @@
     ProjectsResourceWithStreamingResponse,
     AsyncProjectsResourceWithStreamingResponse,
 )
+from .query_logs import (
+    QueryLogsResource,
+    AsyncQueryLogsResource,
+    QueryLogsResourceWithRawResponse,
+    AsyncQueryLogsResourceWithRawResponse,
+    QueryLogsResourceWithStreamingResponse,
+    AsyncQueryLogsResourceWithStreamingResponse,
+)
 from .access_keys import (
     AccessKeysResource,
     AsyncAccessKeysResource,
@@ -32,6 +48,14 @@
     AccessKeysResourceWithStreamingResponse,
     AsyncAccessKeysResourceWithStreamingResponse,
 )
+from .remediations import (
+    RemediationsResource,
+    AsyncRemediationsResource,
+    RemediationsResourceWithRawResponse,
+    AsyncRemediationsResourceWithRawResponse,
+    RemediationsResourceWithStreamingResponse,
+    AsyncRemediationsResourceWithStreamingResponse,
+)
 
 __all__ = [
     "AccessKeysResource",
@@ -52,6 +76,24 @@
     "AsyncClustersResourceWithRawResponse",
     "ClustersResourceWithStreamingResponse",
     "AsyncClustersResourceWithStreamingResponse",
+    "EvalsResource",
+    "AsyncEvalsResource",
+    "EvalsResourceWithRawResponse",
+    "AsyncEvalsResourceWithRawResponse",
+    "EvalsResourceWithStreamingResponse",
+    "AsyncEvalsResourceWithStreamingResponse",
+    "QueryLogsResource",
+    "AsyncQueryLogsResource",
+    "QueryLogsResourceWithRawResponse",
+    "AsyncQueryLogsResourceWithRawResponse",
+    "QueryLogsResourceWithStreamingResponse",
+    "AsyncQueryLogsResourceWithStreamingResponse",
+    "RemediationsResource",
+    "AsyncRemediationsResource",
+    "RemediationsResourceWithRawResponse",
+    "AsyncRemediationsResourceWithRawResponse",
+    "RemediationsResourceWithStreamingResponse",
+    "AsyncRemediationsResourceWithStreamingResponse",
     "ProjectsResource",
     "AsyncProjectsResource",
     "ProjectsResourceWithRawResponse",
diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py
new file mode 100644
index 00000000..1a6a6876
--- /dev/null
+++ b/src/codex/resources/projects/evals.py
@@ -0,0 +1,803 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, overload
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import required_args, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import eval_create_params, eval_update_params
+from ...types.project_return_schema import ProjectReturnSchema
+from ...types.projects.eval_list_response import EvalListResponse
+
+__all__ = ["EvalsResource", "AsyncEvalsResource"]
+
+
+class EvalsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> EvalsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return EvalsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> EvalsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return EvalsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        project_id: str,
+        *,
+        criteria: str,
+        eval_key: str,
+        name: str,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Create a new custom eval for a project.
+
+        Args:
+          criteria: The evaluation criteria text that describes what aspect is being evaluated and
+              how
+
+          eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          name: Display name/label for the evaluation metric
+
+          context_identifier: The exact string used in your evaluation criteria to reference the retrieved
+              context.
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          is_default: Whether the eval is a default, built-in eval or a custom eval
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          query_identifier: The exact string used in your evaluation criteria to reference the user's query.
+
+          response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
+              response.
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/evals",
+            body=maybe_transform(
+                {
+                    "criteria": criteria,
+                    "eval_key": eval_key,
+                    "name": name,
+                    "context_identifier": context_identifier,
+                    "enabled": enabled,
+                    "is_default": is_default,
+                    "priority": priority,
+                    "query_identifier": query_identifier,
+                    "response_identifier": response_identifier,
+                    "should_escalate": should_escalate,
+                    "threshold": threshold,
+                    "threshold_direction": threshold_direction,
+                },
+                eval_create_params.EvalCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    @overload
+    def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        criteria: str,
+        body_eval_key: str,
+        name: str,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update an existing eval for a project.
+
+        Args:
+          criteria: The evaluation criteria text that describes what aspect is being evaluated and
+              how
+
+          body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          name: Display name/label for the evaluation metric
+
+          context_identifier: The exact string used in your evaluation criteria to reference the retrieved
+              context.
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          is_default: Whether the eval is a default, built-in eval or a custom eval
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          query_identifier: The exact string used in your evaluation criteria to reference the user's query.
+
+          response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
+              response.
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        ...
+
+    @overload
+    def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        body_eval_key: str,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update an existing eval for a project.
+
+        Args:
+          body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        ...
+
+    @required_args(["project_id", "criteria", "body_eval_key", "name"], ["project_id", "body_eval_key"])
+    def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        criteria: str | NotGiven = NOT_GIVEN,
+        body_eval_key: str,
+        name: str | NotGiven = NOT_GIVEN,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not path_eval_key:
+            raise ValueError(f"Expected a non-empty value for `path_eval_key` but received {path_eval_key!r}")
+        return self._put(
+            f"/api/projects/{project_id}/evals/{path_eval_key}",
+            body=maybe_transform(
+                {
+                    "criteria": criteria,
+                    "body_eval_key": body_eval_key,
+                    "name": name,
+                    "context_identifier": context_identifier,
+                    "enabled": enabled,
+                    "is_default": is_default,
+                    "priority": priority,
+                    "query_identifier": query_identifier,
+                    "response_identifier": response_identifier,
+                    "should_escalate": should_escalate,
+                    "threshold": threshold,
+                    "threshold_direction": threshold_direction,
+                },
+                eval_update_params.EvalUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    def list(
+        self,
+        project_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EvalListResponse:
+        """
+        Get the evaluations config for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/evals",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EvalListResponse,
+        )
+
+    def delete(
+        self,
+        eval_key: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Remove a custom eval for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not eval_key:
+            raise ValueError(f"Expected a non-empty value for `eval_key` but received {eval_key!r}")
+        return self._delete(
+            f"/api/projects/{project_id}/evals/{eval_key}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+
+class AsyncEvalsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncEvalsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncEvalsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncEvalsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncEvalsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        project_id: str,
+        *,
+        criteria: str,
+        eval_key: str,
+        name: str,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Create a new custom eval for a project.
+
+        Args:
+          criteria: The evaluation criteria text that describes what aspect is being evaluated and
+              how
+
+          eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          name: Display name/label for the evaluation metric
+
+          context_identifier: The exact string used in your evaluation criteria to reference the retrieved
+              context.
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          is_default: Whether the eval is a default, built-in eval or a custom eval
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          query_identifier: The exact string used in your evaluation criteria to reference the user's query.
+
+          response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
+              response.
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/evals",
+            body=await async_maybe_transform(
+                {
+                    "criteria": criteria,
+                    "eval_key": eval_key,
+                    "name": name,
+                    "context_identifier": context_identifier,
+                    "enabled": enabled,
+                    "is_default": is_default,
+                    "priority": priority,
+                    "query_identifier": query_identifier,
+                    "response_identifier": response_identifier,
+                    "should_escalate": should_escalate,
+                    "threshold": threshold,
+                    "threshold_direction": threshold_direction,
+                },
+                eval_create_params.EvalCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    @overload
+    async def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        criteria: str,
+        body_eval_key: str,
+        name: str,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update an existing eval for a project.
+
+        Args:
+          criteria: The evaluation criteria text that describes what aspect is being evaluated and
+              how
+
+          body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          name: Display name/label for the evaluation metric
+
+          context_identifier: The exact string used in your evaluation criteria to reference the retrieved
+              context.
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          is_default: Whether the eval is a default, built-in eval or a custom eval
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          query_identifier: The exact string used in your evaluation criteria to reference the user's query.
+
+          response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
+              response.
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        ...
+
+    @overload
+    async def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        body_eval_key: str,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Update an existing eval for a project.
+
+        Args:
+          body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property
+              and eval_scores dictionary key to check against threshold
+
+          enabled: Allows the evaluation to be disabled without removing it
+
+          priority: Priority order for evals (lower number = higher priority) to determine primary
+              eval issue to surface
+
+          should_escalate: If true, failing this eval means the response is considered bad and can trigger
+              escalation to Codex/SME
+
+          threshold: Threshold value that determines if the evaluation fails
+
+          threshold_direction: Whether the evaluation fails when score is above or below the threshold
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        ...
+
+    @required_args(["project_id", "criteria", "body_eval_key", "name"], ["project_id", "body_eval_key"])
+    async def update(
+        self,
+        path_eval_key: str,
+        *,
+        project_id: str,
+        criteria: str | NotGiven = NOT_GIVEN,
+        body_eval_key: str,
+        name: str | NotGiven = NOT_GIVEN,
+        context_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        enabled: bool | NotGiven = NOT_GIVEN,
+        is_default: bool | NotGiven = NOT_GIVEN,
+        priority: Optional[int] | NotGiven = NOT_GIVEN,
+        query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
+        should_escalate: bool | NotGiven = NOT_GIVEN,
+        threshold: float | NotGiven = NOT_GIVEN,
+        threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not path_eval_key:
+            raise ValueError(f"Expected a non-empty value for `path_eval_key` but received {path_eval_key!r}")
+        return await self._put(
+            f"/api/projects/{project_id}/evals/{path_eval_key}",
+            body=await async_maybe_transform(
+                {
+                    "criteria": criteria,
+                    "body_eval_key": body_eval_key,
+                    "name": name,
+                    "context_identifier": context_identifier,
+                    "enabled": enabled,
+                    "is_default": is_default,
+                    "priority": priority,
+                    "query_identifier": query_identifier,
+                    "response_identifier": response_identifier,
+                    "should_escalate": should_escalate,
+                    "threshold": threshold,
+                    "threshold_direction": threshold_direction,
+                },
+                eval_update_params.EvalUpdateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+    async def list(
+        self,
+        project_id: str,
+        *,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> EvalListResponse:
+        """
+        Get the evaluations config for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/evals",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=EvalListResponse,
+        )
+
+    async def delete(
+        self,
+        eval_key: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectReturnSchema:
+        """
+        Remove a custom eval for a project.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not eval_key:
+            raise ValueError(f"Expected a non-empty value for `eval_key` but received {eval_key!r}")
+        return await self._delete(
+            f"/api/projects/{project_id}/evals/{eval_key}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectReturnSchema,
+        )
+
+
+class EvalsResourceWithRawResponse:
+    def __init__(self, evals: EvalsResource) -> None:
+        self._evals = evals
+
+        self.create = to_raw_response_wrapper(
+            evals.create,
+        )
+        self.update = to_raw_response_wrapper(
+            evals.update,
+        )
+        self.list = to_raw_response_wrapper(
+            evals.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            evals.delete,
+        )
+
+
+class AsyncEvalsResourceWithRawResponse:
+    def __init__(self, evals: AsyncEvalsResource) -> None:
+        self._evals = evals
+
+        self.create = async_to_raw_response_wrapper(
+            evals.create,
+        )
+        self.update = async_to_raw_response_wrapper(
+            evals.update,
+        )
+        self.list = async_to_raw_response_wrapper(
+            evals.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            evals.delete,
+        )
+
+
+class EvalsResourceWithStreamingResponse:
+    def __init__(self, evals: EvalsResource) -> None:
+        self._evals = evals
+
+        self.create = to_streamed_response_wrapper(
+            evals.create,
+        )
+        self.update = to_streamed_response_wrapper(
+            evals.update,
+        )
+        self.list = to_streamed_response_wrapper(
+            evals.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            evals.delete,
+        )
+
+
+class AsyncEvalsResourceWithStreamingResponse:
+    def __init__(self, evals: AsyncEvalsResource) -> None:
+        self._evals = evals
+
+        self.create = async_to_streamed_response_wrapper(
+            evals.create,
+        )
+        self.update = async_to_streamed_response_wrapper(
+            evals.update,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            evals.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            evals.delete,
+        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 11b95d4d..22b5caff 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -8,11 +8,20 @@
 
 import httpx
 
+from .evals import (
+    EvalsResource,
+    AsyncEvalsResource,
+    EvalsResourceWithRawResponse,
+    AsyncEvalsResourceWithRawResponse,
+    EvalsResourceWithStreamingResponse,
+    AsyncEvalsResourceWithStreamingResponse,
+)
 from ...types import (
     project_list_params,
     project_create_params,
     project_update_params,
     project_validate_params,
+    project_invite_sme_params,
     project_increment_queries_params,
     project_retrieve_analytics_params,
 )
@@ -35,6 +44,14 @@
     AsyncClustersResourceWithStreamingResponse,
 )
 from ..._compat import cached_property
+from .query_logs import (
+    QueryLogsResource,
+    AsyncQueryLogsResource,
+    QueryLogsResourceWithRawResponse,
+    AsyncQueryLogsResourceWithRawResponse,
+    QueryLogsResourceWithStreamingResponse,
+    AsyncQueryLogsResourceWithStreamingResponse,
+)
 from ..._resource import SyncAPIResource, AsyncAPIResource
 from ..._response import (
     to_raw_response_wrapper,
@@ -50,11 +67,20 @@
     AccessKeysResourceWithStreamingResponse,
     AsyncAccessKeysResourceWithStreamingResponse,
 )
+from .remediations import (
+    RemediationsResource,
+    AsyncRemediationsResource,
+    RemediationsResourceWithRawResponse,
+    AsyncRemediationsResourceWithRawResponse,
+    RemediationsResourceWithStreamingResponse,
+    AsyncRemediationsResourceWithStreamingResponse,
+)
 from ..._base_client import make_request_options
 from ...types.project_list_response import ProjectListResponse
 from ...types.project_return_schema import ProjectReturnSchema
 from ...types.project_retrieve_response import ProjectRetrieveResponse
 from ...types.project_validate_response import ProjectValidateResponse
+from ...types.project_invite_sme_response import ProjectInviteSmeResponse
 from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse
 
 __all__ = ["ProjectsResource", "AsyncProjectsResource"]
@@ -73,6 +99,18 @@ def entries(self) -> EntriesResource:
     def clusters(self) -> ClustersResource:
         return ClustersResource(self._client)
 
+    @cached_property
+    def evals(self) -> EvalsResource:
+        return EvalsResource(self._client)
+
+    @cached_property
+    def query_logs(self) -> QueryLogsResource:
+        return QueryLogsResource(self._client)
+
+    @cached_property
+    def remediations(self) -> RemediationsResource:
+        return RemediationsResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> ProjectsResourceWithRawResponse:
         """
@@ -374,6 +412,52 @@ def increment_queries(
             cast_to=object,
         )
 
+    def invite_sme(
+        self,
+        project_id: str,
+        *,
+        email: str,
+        page_type: Literal["query_log", "remediation"],
+        url_query_string: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectInviteSmeResponse:
+        """
+        Invite a subject matter expert to view a specific query log or remediation.
+
+        Returns: SMERemediationNotificationResponse with status and notification details
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/notifications",
+            body=maybe_transform(
+                {
+                    "email": email,
+                    "page_type": page_type,
+                    "url_query_string": url_query_string,
+                },
+                project_invite_sme_params.ProjectInviteSmeParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectInviteSmeResponse,
+        )
+
     def retrieve_analytics(
         self,
         project_id: str,
@@ -623,6 +707,18 @@ def entries(self) -> AsyncEntriesResource:
     def clusters(self) -> AsyncClustersResource:
         return AsyncClustersResource(self._client)
 
+    @cached_property
+    def evals(self) -> AsyncEvalsResource:
+        return AsyncEvalsResource(self._client)
+
+    @cached_property
+    def query_logs(self) -> AsyncQueryLogsResource:
+        return AsyncQueryLogsResource(self._client)
+
+    @cached_property
+    def remediations(self) -> AsyncRemediationsResource:
+        return AsyncRemediationsResource(self._client)
+
     @cached_property
     def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
         """
@@ -926,6 +1022,52 @@ async def increment_queries(
             cast_to=object,
         )
 
+    async def invite_sme(
+        self,
+        project_id: str,
+        *,
+        email: str,
+        page_type: Literal["query_log", "remediation"],
+        url_query_string: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> ProjectInviteSmeResponse:
+        """
+        Invite a subject matter expert to view a specific query log or remediation.
+
+        Returns: SMERemediationNotificationResponse with status and notification details
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/notifications",
+            body=await async_maybe_transform(
+                {
+                    "email": email,
+                    "page_type": page_type,
+                    "url_query_string": url_query_string,
+                },
+                project_invite_sme_params.ProjectInviteSmeParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=ProjectInviteSmeResponse,
+        )
+
     async def retrieve_analytics(
         self,
         project_id: str,
@@ -1189,6 +1331,9 @@ def __init__(self, projects: ProjectsResource) -> None:
                 projects.increment_queries  # pyright: ignore[reportDeprecated],
             )
         )
+        self.invite_sme = to_raw_response_wrapper(
+            projects.invite_sme,
+        )
         self.retrieve_analytics = to_raw_response_wrapper(
             projects.retrieve_analytics,
         )
@@ -1208,6 +1353,18 @@ def entries(self) -> EntriesResourceWithRawResponse:
     def clusters(self) -> ClustersResourceWithRawResponse:
         return ClustersResourceWithRawResponse(self._projects.clusters)
 
+    @cached_property
+    def evals(self) -> EvalsResourceWithRawResponse:
+        return EvalsResourceWithRawResponse(self._projects.evals)
+
+    @cached_property
+    def query_logs(self) -> QueryLogsResourceWithRawResponse:
+        return QueryLogsResourceWithRawResponse(self._projects.query_logs)
+
+    @cached_property
+    def remediations(self) -> RemediationsResourceWithRawResponse:
+        return RemediationsResourceWithRawResponse(self._projects.remediations)
+
 
 class AsyncProjectsResourceWithRawResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -1236,6 +1393,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
                 projects.increment_queries  # pyright: ignore[reportDeprecated],
             )
         )
+        self.invite_sme = async_to_raw_response_wrapper(
+            projects.invite_sme,
+        )
         self.retrieve_analytics = async_to_raw_response_wrapper(
             projects.retrieve_analytics,
         )
@@ -1255,6 +1415,18 @@ def entries(self) -> AsyncEntriesResourceWithRawResponse:
     def clusters(self) -> AsyncClustersResourceWithRawResponse:
         return AsyncClustersResourceWithRawResponse(self._projects.clusters)
 
+    @cached_property
+    def evals(self) -> AsyncEvalsResourceWithRawResponse:
+        return AsyncEvalsResourceWithRawResponse(self._projects.evals)
+
+    @cached_property
+    def query_logs(self) -> AsyncQueryLogsResourceWithRawResponse:
+        return AsyncQueryLogsResourceWithRawResponse(self._projects.query_logs)
+
+    @cached_property
+    def remediations(self) -> AsyncRemediationsResourceWithRawResponse:
+        return AsyncRemediationsResourceWithRawResponse(self._projects.remediations)
+
 
 class ProjectsResourceWithStreamingResponse:
     def __init__(self, projects: ProjectsResource) -> None:
@@ -1283,6 +1455,9 @@ def __init__(self, projects: ProjectsResource) -> None:
                 projects.increment_queries  # pyright: ignore[reportDeprecated],
             )
         )
+        self.invite_sme = to_streamed_response_wrapper(
+            projects.invite_sme,
+        )
         self.retrieve_analytics = to_streamed_response_wrapper(
             projects.retrieve_analytics,
         )
@@ -1302,6 +1477,18 @@ def entries(self) -> EntriesResourceWithStreamingResponse:
     def clusters(self) -> ClustersResourceWithStreamingResponse:
         return ClustersResourceWithStreamingResponse(self._projects.clusters)
 
+    @cached_property
+    def evals(self) -> EvalsResourceWithStreamingResponse:
+        return EvalsResourceWithStreamingResponse(self._projects.evals)
+
+    @cached_property
+    def query_logs(self) -> QueryLogsResourceWithStreamingResponse:
+        return QueryLogsResourceWithStreamingResponse(self._projects.query_logs)
+
+    @cached_property
+    def remediations(self) -> RemediationsResourceWithStreamingResponse:
+        return RemediationsResourceWithStreamingResponse(self._projects.remediations)
+
 
 class AsyncProjectsResourceWithStreamingResponse:
     def __init__(self, projects: AsyncProjectsResource) -> None:
@@ -1330,6 +1517,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
                 projects.increment_queries  # pyright: ignore[reportDeprecated],
             )
         )
+        self.invite_sme = async_to_streamed_response_wrapper(
+            projects.invite_sme,
+        )
         self.retrieve_analytics = async_to_streamed_response_wrapper(
             projects.retrieve_analytics,
         )
@@ -1348,3 +1538,15 @@ def entries(self) -> AsyncEntriesResourceWithStreamingResponse:
     @cached_property
     def clusters(self) -> AsyncClustersResourceWithStreamingResponse:
         return AsyncClustersResourceWithStreamingResponse(self._projects.clusters)
+
+    @cached_property
+    def evals(self) -> AsyncEvalsResourceWithStreamingResponse:
+        return AsyncEvalsResourceWithStreamingResponse(self._projects.evals)
+
+    @cached_property
+    def query_logs(self) -> AsyncQueryLogsResourceWithStreamingResponse:
+        return AsyncQueryLogsResourceWithStreamingResponse(self._projects.query_logs)
+
+    @cached_property
+    def remediations(self) -> AsyncRemediationsResourceWithStreamingResponse:
+        return AsyncRemediationsResourceWithStreamingResponse(self._projects.remediations)
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
new file mode 100644
index 00000000..32ec7390
--- /dev/null
+++ b/src/codex/resources/projects/query_logs.py
@@ -0,0 +1,741 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params
+from ...types.projects.query_log_list_response import QueryLogListResponse
+from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse
+from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse
+from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse
+from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse
+
+__all__ = ["QueryLogsResource", "AsyncQueryLogsResource"]
+
+
+class QueryLogsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> QueryLogsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return QueryLogsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> QueryLogsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return QueryLogsResourceWithStreamingResponse(self)
+
+    def retrieve(
+        self,
+        query_log_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogRetrieveResponse:
+        """
+        Get Query Log Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not query_log_id:
+            raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/query_logs/{query_log_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=QueryLogRetrieveResponse,
+        )
+
+    def list(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListResponse:
+        """
+        List query logs by project ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/query_logs/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_params.QueryLogListParams,
+                ),
+            ),
+            cast_to=QueryLogListResponse,
+        )
+
+    def list_by_group(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        remediation_ids: List[str] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListByGroupResponse:
+        """
+        List query log group by remediation ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          remediation_ids: List of groups to list child logs for
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/query_logs/logs_by_group",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "remediation_ids": remediation_ids,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_by_group_params.QueryLogListByGroupParams,
+                ),
+            ),
+            cast_to=QueryLogListByGroupResponse,
+        )
+
+    def list_groups(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+        | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListGroupsResponse:
+        """
+        List query log groups by project ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/query_logs/groups",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_groups_params.QueryLogListGroupsParams,
+                ),
+            ),
+            cast_to=QueryLogListGroupsResponse,
+        )
+
+    def start_remediation(
+        self,
+        query_log_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogStartRemediationResponse:
+        """
+        Start Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not query_log_id:
+            raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/query_logs/{query_log_id}/start_remediation",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=QueryLogStartRemediationResponse,
+        )
+
+
+class AsyncQueryLogsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncQueryLogsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncQueryLogsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncQueryLogsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncQueryLogsResourceWithStreamingResponse(self)
+
+    async def retrieve(
+        self,
+        query_log_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogRetrieveResponse:
+        """
+        Get Query Log Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not query_log_id:
+            raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/query_logs/{query_log_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=QueryLogRetrieveResponse,
+        )
+
+    async def list(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListResponse:
+        """
+        List query logs by project ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/query_logs/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_params.QueryLogListParams,
+                ),
+            ),
+            cast_to=QueryLogListResponse,
+        )
+
+    async def list_by_group(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        remediation_ids: List[str] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListByGroupResponse:
+        """
+        List query log group by remediation ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          remediation_ids: List of groups to list child logs for
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/query_logs/logs_by_group",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "remediation_ids": remediation_ids,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_by_group_params.QueryLogListByGroupParams,
+                ),
+            ),
+            cast_to=QueryLogListByGroupResponse,
+        )
+
+    async def list_groups(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        primary_eval_issue: Optional[
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        ]
+        | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+        | NotGiven = NOT_GIVEN,
+        was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogListGroupsResponse:
+        """
+        List query log groups by project ID.
+
+        Args:
+          created_at_end: Filter logs created at or before this timestamp
+
+          created_at_start: Filter logs created at or after this timestamp
+
+          custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
+
+          primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
+
+          was_cache_hit: Filter by cache hit status
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/query_logs/groups",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "custom_metadata": custom_metadata,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "primary_eval_issue": primary_eval_issue,
+                        "sort": sort,
+                        "was_cache_hit": was_cache_hit,
+                    },
+                    query_log_list_groups_params.QueryLogListGroupsParams,
+                ),
+            ),
+            cast_to=QueryLogListGroupsResponse,
+        )
+
+    async def start_remediation(
+        self,
+        query_log_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> QueryLogStartRemediationResponse:
+        """
+        Start Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not query_log_id:
+            raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/query_logs/{query_log_id}/start_remediation",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=QueryLogStartRemediationResponse,
+        )
+
+
+class QueryLogsResourceWithRawResponse:
+    def __init__(self, query_logs: QueryLogsResource) -> None:
+        self._query_logs = query_logs
+
+        self.retrieve = to_raw_response_wrapper(
+            query_logs.retrieve,
+        )
+        self.list = to_raw_response_wrapper(
+            query_logs.list,
+        )
+        self.list_by_group = to_raw_response_wrapper(
+            query_logs.list_by_group,
+        )
+        self.list_groups = to_raw_response_wrapper(
+            query_logs.list_groups,
+        )
+        self.start_remediation = to_raw_response_wrapper(
+            query_logs.start_remediation,
+        )
+
+
+class AsyncQueryLogsResourceWithRawResponse:
+    def __init__(self, query_logs: AsyncQueryLogsResource) -> None:
+        self._query_logs = query_logs
+
+        self.retrieve = async_to_raw_response_wrapper(
+            query_logs.retrieve,
+        )
+        self.list = async_to_raw_response_wrapper(
+            query_logs.list,
+        )
+        self.list_by_group = async_to_raw_response_wrapper(
+            query_logs.list_by_group,
+        )
+        self.list_groups = async_to_raw_response_wrapper(
+            query_logs.list_groups,
+        )
+        self.start_remediation = async_to_raw_response_wrapper(
+            query_logs.start_remediation,
+        )
+
+
+class QueryLogsResourceWithStreamingResponse:
+    def __init__(self, query_logs: QueryLogsResource) -> None:
+        self._query_logs = query_logs
+
+        self.retrieve = to_streamed_response_wrapper(
+            query_logs.retrieve,
+        )
+        self.list = to_streamed_response_wrapper(
+            query_logs.list,
+        )
+        self.list_by_group = to_streamed_response_wrapper(
+            query_logs.list_by_group,
+        )
+        self.list_groups = to_streamed_response_wrapper(
+            query_logs.list_groups,
+        )
+        self.start_remediation = to_streamed_response_wrapper(
+            query_logs.start_remediation,
+        )
+
+
+class AsyncQueryLogsResourceWithStreamingResponse:
+    def __init__(self, query_logs: AsyncQueryLogsResource) -> None:
+        self._query_logs = query_logs
+
+        self.retrieve = async_to_streamed_response_wrapper(
+            query_logs.retrieve,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            query_logs.list,
+        )
+        self.list_by_group = async_to_streamed_response_wrapper(
+            query_logs.list_by_group,
+        )
+        self.list_groups = async_to_streamed_response_wrapper(
+            query_logs.list_groups,
+        )
+        self.start_remediation = async_to_streamed_response_wrapper(
+            query_logs.start_remediation,
+        )
diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py
new file mode 100644
index 00000000..65015a14
--- /dev/null
+++ b/src/codex/resources/projects/remediations.py
@@ -0,0 +1,1135 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+    to_raw_response_wrapper,
+    to_streamed_response_wrapper,
+    async_to_raw_response_wrapper,
+    async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import (
+    remediation_list_params,
+    remediation_create_params,
+    remediation_edit_answer_params,
+    remediation_edit_draft_answer_params,
+)
+from ...types.projects.remediation_list_response import RemediationListResponse
+from ...types.projects.remediation_pause_response import RemediationPauseResponse
+from ...types.projects.remediation_create_response import RemediationCreateResponse
+from ...types.projects.remediation_publish_response import RemediationPublishResponse
+from ...types.projects.remediation_unpause_response import RemediationUnpauseResponse
+from ...types.projects.remediation_retrieve_response import RemediationRetrieveResponse
+from ...types.projects.remediation_edit_answer_response import RemediationEditAnswerResponse
+from ...types.projects.remediation_edit_draft_answer_response import RemediationEditDraftAnswerResponse
+from ...types.projects.remediation_list_resolved_logs_response import RemediationListResolvedLogsResponse
+from ...types.projects.remediation_get_resolved_logs_count_response import RemediationGetResolvedLogsCountResponse
+
+__all__ = ["RemediationsResource", "AsyncRemediationsResource"]
+
+
+class RemediationsResource(SyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> RemediationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return RemediationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> RemediationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return RemediationsResourceWithStreamingResponse(self)
+
+    def create(
+        self,
+        project_id: str,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationCreateResponse:
+        """
+        Create Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._post(
+            f"/api/projects/{project_id}/remediations/",
+            body=maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                    "draft_answer": draft_answer,
+                },
+                remediation_create_params.RemediationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationCreateResponse,
+        )
+
+    def retrieve(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationRetrieveResponse:
+        """
+        Get Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationRetrieveResponse,
+        )
+
+    def list(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_by: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN,
+        status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationListResponse:
+        """
+        List remediations by project ID.
+
+        Args:
+          created_at_end: Filter remediations created at or before this timestamp
+
+          created_at_start: Filter remediations created at or after this timestamp
+
+          last_edited_at_end: Filter remediations last edited at or before this timestamp
+
+          last_edited_at_start: Filter remediations last edited at or after this timestamp
+
+          last_edited_by: Filter by last edited by user ID
+
+          status: Filter remediations that have ANY of these statuses (OR operation)
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/remediations/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "last_edited_at_end": last_edited_at_end,
+                        "last_edited_at_start": last_edited_at_start,
+                        "last_edited_by": last_edited_by,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "status": status,
+                    },
+                    remediation_list_params.RemediationListParams,
+                ),
+            ),
+            cast_to=RemediationListResponse,
+        )
+
+    def delete(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return self._delete(
+            f"/api/projects/{project_id}/remediations/{remediation_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    def edit_answer(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        answer: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationEditAnswerResponse:
+        """
+        Edit Answer Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/edit_answer",
+            body=maybe_transform({"answer": answer}, remediation_edit_answer_params.RemediationEditAnswerParams),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationEditAnswerResponse,
+        )
+
+    def edit_draft_answer(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        draft_answer: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationEditDraftAnswerResponse:
+        """
+        Edit Draft Answer Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer",
+            body=maybe_transform(
+                {"draft_answer": draft_answer}, remediation_edit_draft_answer_params.RemediationEditDraftAnswerParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationEditDraftAnswerResponse,
+        )
+
+    def get_resolved_logs_count(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationGetResolvedLogsCountResponse:
+        """
+        Get Remediation With Resolved Logs Count Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs_count",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationGetResolvedLogsCountResponse,
+        )
+
+    def list_resolved_logs(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationListResolvedLogsResponse:
+        """
+        List resolved logs by remediation ID.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationListResolvedLogsResponse,
+        )
+
+    def pause(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationPauseResponse:
+        """
+        Pause Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/pause",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationPauseResponse,
+        )
+
+    def publish(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationPublishResponse:
+        """
+        Publish Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/publish",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationPublishResponse,
+        )
+
+    def unpause(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationUnpauseResponse:
+        """
+        Unpause Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/unpause",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationUnpauseResponse,
+        )
+
+
+class AsyncRemediationsResource(AsyncAPIResource):
+    @cached_property
+    def with_raw_response(self) -> AsyncRemediationsResourceWithRawResponse:
+        """
+        This property can be used as a prefix for any HTTP method call to return
+        the raw response object instead of the parsed content.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
+        """
+        return AsyncRemediationsResourceWithRawResponse(self)
+
+    @cached_property
+    def with_streaming_response(self) -> AsyncRemediationsResourceWithStreamingResponse:
+        """
+        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
+        """
+        return AsyncRemediationsResourceWithStreamingResponse(self)
+
+    async def create(
+        self,
+        project_id: str,
+        *,
+        question: str,
+        answer: Optional[str] | NotGiven = NOT_GIVEN,
+        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationCreateResponse:
+        """
+        Create Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._post(
+            f"/api/projects/{project_id}/remediations/",
+            body=await async_maybe_transform(
+                {
+                    "question": question,
+                    "answer": answer,
+                    "draft_answer": draft_answer,
+                },
+                remediation_create_params.RemediationCreateParams,
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationCreateResponse,
+        )
+
+    async def retrieve(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationRetrieveResponse:
+        """
+        Get Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationRetrieveResponse,
+        )
+
+    async def list(
+        self,
+        project_id: str,
+        *,
+        created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
+        last_edited_by: Optional[str] | NotGiven = NOT_GIVEN,
+        limit: int | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
+        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN,
+        status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationListResponse:
+        """
+        List remediations by project ID.
+
+        Args:
+          created_at_end: Filter remediations created at or before this timestamp
+
+          created_at_start: Filter remediations created at or after this timestamp
+
+          last_edited_at_end: Filter remediations last edited at or before this timestamp
+
+          last_edited_at_start: Filter remediations last edited at or after this timestamp
+
+          last_edited_by: Filter by last edited by user ID
+
+          status: Filter remediations that have ANY of these statuses (OR operation)
+
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/remediations/",
+            options=make_request_options(
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "created_at_end": created_at_end,
+                        "created_at_start": created_at_start,
+                        "last_edited_at_end": last_edited_at_end,
+                        "last_edited_at_start": last_edited_at_start,
+                        "last_edited_by": last_edited_by,
+                        "limit": limit,
+                        "offset": offset,
+                        "order": order,
+                        "sort": sort,
+                        "status": status,
+                    },
+                    remediation_list_params.RemediationListParams,
+                ),
+            ),
+            cast_to=RemediationListResponse,
+        )
+
+    async def delete(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> None:
+        """
+        Delete Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+        return await self._delete(
+            f"/api/projects/{project_id}/remediations/{remediation_id}",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=NoneType,
+        )
+
+    async def edit_answer(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        answer: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationEditAnswerResponse:
+        """
+        Edit Answer Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/edit_answer",
+            body=await async_maybe_transform(
+                {"answer": answer}, remediation_edit_answer_params.RemediationEditAnswerParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationEditAnswerResponse,
+        )
+
+    async def edit_draft_answer(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        draft_answer: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationEditDraftAnswerResponse:
+        """
+        Edit Draft Answer Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer",
+            body=await async_maybe_transform(
+                {"draft_answer": draft_answer}, remediation_edit_draft_answer_params.RemediationEditDraftAnswerParams
+            ),
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationEditDraftAnswerResponse,
+        )
+
+    async def get_resolved_logs_count(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationGetResolvedLogsCountResponse:
+        """
+        Get Remediation With Resolved Logs Count Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs_count",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationGetResolvedLogsCountResponse,
+        )
+
+    async def list_resolved_logs(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationListResolvedLogsResponse:
+        """
+        List resolved logs by remediation ID.
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._get(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationListResolvedLogsResponse,
+        )
+
+    async def pause(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationPauseResponse:
+        """
+        Pause Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/pause",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationPauseResponse,
+        )
+
+    async def publish(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationPublishResponse:
+        """
+        Publish Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/publish",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationPublishResponse,
+        )
+
+    async def unpause(
+        self,
+        remediation_id: str,
+        *,
+        project_id: str,
+        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+        # The extra values given here take precedence over values defined on the client or passed to this method.
+        extra_headers: Headers | None = None,
+        extra_query: Query | None = None,
+        extra_body: Body | None = None,
+        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+    ) -> RemediationUnpauseResponse:
+        """
+        Unpause Remediation Route
+
+        Args:
+          extra_headers: Send extra headers
+
+          extra_query: Add additional query parameters to the request
+
+          extra_body: Add additional JSON properties to the request
+
+          timeout: Override the client-level default timeout for this request, in seconds
+        """
+        if not project_id:
+            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+        if not remediation_id:
+            raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}")
+        return await self._patch(
+            f"/api/projects/{project_id}/remediations/{remediation_id}/unpause",
+            options=make_request_options(
+                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+            ),
+            cast_to=RemediationUnpauseResponse,
+        )
+
+
+class RemediationsResourceWithRawResponse:
+    def __init__(self, remediations: RemediationsResource) -> None:
+        self._remediations = remediations
+
+        self.create = to_raw_response_wrapper(
+            remediations.create,
+        )
+        self.retrieve = to_raw_response_wrapper(
+            remediations.retrieve,
+        )
+        self.list = to_raw_response_wrapper(
+            remediations.list,
+        )
+        self.delete = to_raw_response_wrapper(
+            remediations.delete,
+        )
+        self.edit_answer = to_raw_response_wrapper(
+            remediations.edit_answer,
+        )
+        self.edit_draft_answer = to_raw_response_wrapper(
+            remediations.edit_draft_answer,
+        )
+        self.get_resolved_logs_count = to_raw_response_wrapper(
+            remediations.get_resolved_logs_count,
+        )
+        self.list_resolved_logs = to_raw_response_wrapper(
+            remediations.list_resolved_logs,
+        )
+        self.pause = to_raw_response_wrapper(
+            remediations.pause,
+        )
+        self.publish = to_raw_response_wrapper(
+            remediations.publish,
+        )
+        self.unpause = to_raw_response_wrapper(
+            remediations.unpause,
+        )
+
+
+class AsyncRemediationsResourceWithRawResponse:
+    def __init__(self, remediations: AsyncRemediationsResource) -> None:
+        self._remediations = remediations
+
+        self.create = async_to_raw_response_wrapper(
+            remediations.create,
+        )
+        self.retrieve = async_to_raw_response_wrapper(
+            remediations.retrieve,
+        )
+        self.list = async_to_raw_response_wrapper(
+            remediations.list,
+        )
+        self.delete = async_to_raw_response_wrapper(
+            remediations.delete,
+        )
+        self.edit_answer = async_to_raw_response_wrapper(
+            remediations.edit_answer,
+        )
+        self.edit_draft_answer = async_to_raw_response_wrapper(
+            remediations.edit_draft_answer,
+        )
+        self.get_resolved_logs_count = async_to_raw_response_wrapper(
+            remediations.get_resolved_logs_count,
+        )
+        self.list_resolved_logs = async_to_raw_response_wrapper(
+            remediations.list_resolved_logs,
+        )
+        self.pause = async_to_raw_response_wrapper(
+            remediations.pause,
+        )
+        self.publish = async_to_raw_response_wrapper(
+            remediations.publish,
+        )
+        self.unpause = async_to_raw_response_wrapper(
+            remediations.unpause,
+        )
+
+
+class RemediationsResourceWithStreamingResponse:
+    def __init__(self, remediations: RemediationsResource) -> None:
+        self._remediations = remediations
+
+        self.create = to_streamed_response_wrapper(
+            remediations.create,
+        )
+        self.retrieve = to_streamed_response_wrapper(
+            remediations.retrieve,
+        )
+        self.list = to_streamed_response_wrapper(
+            remediations.list,
+        )
+        self.delete = to_streamed_response_wrapper(
+            remediations.delete,
+        )
+        self.edit_answer = to_streamed_response_wrapper(
+            remediations.edit_answer,
+        )
+        self.edit_draft_answer = to_streamed_response_wrapper(
+            remediations.edit_draft_answer,
+        )
+        self.get_resolved_logs_count = to_streamed_response_wrapper(
+            remediations.get_resolved_logs_count,
+        )
+        self.list_resolved_logs = to_streamed_response_wrapper(
+            remediations.list_resolved_logs,
+        )
+        self.pause = to_streamed_response_wrapper(
+            remediations.pause,
+        )
+        self.publish = to_streamed_response_wrapper(
+            remediations.publish,
+        )
+        self.unpause = to_streamed_response_wrapper(
+            remediations.unpause,
+        )
+
+
+class AsyncRemediationsResourceWithStreamingResponse:
+    def __init__(self, remediations: AsyncRemediationsResource) -> None:
+        self._remediations = remediations
+
+        self.create = async_to_streamed_response_wrapper(
+            remediations.create,
+        )
+        self.retrieve = async_to_streamed_response_wrapper(
+            remediations.retrieve,
+        )
+        self.list = async_to_streamed_response_wrapper(
+            remediations.list,
+        )
+        self.delete = async_to_streamed_response_wrapper(
+            remediations.delete,
+        )
+        self.edit_answer = async_to_streamed_response_wrapper(
+            remediations.edit_answer,
+        )
+        self.edit_draft_answer = async_to_streamed_response_wrapper(
+            remediations.edit_draft_answer,
+        )
+        self.get_resolved_logs_count = async_to_streamed_response_wrapper(
+            remediations.get_resolved_logs_count,
+        )
+        self.list_resolved_logs = async_to_streamed_response_wrapper(
+            remediations.list_resolved_logs,
+        )
+        self.pause = async_to_streamed_response_wrapper(
+            remediations.pause,
+        )
+        self.publish = async_to_streamed_response_wrapper(
+            remediations.publish,
+        )
+        self.unpause = async_to_streamed_response_wrapper(
+            remediations.unpause,
+        )
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 8e0cc4a4..70713a3b 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -13,9 +13,11 @@
 from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
 from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
 from .project_validate_params import ProjectValidateParams as ProjectValidateParams
+from .project_invite_sme_params import ProjectInviteSmeParams as ProjectInviteSmeParams
 from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
 from .project_validate_response import ProjectValidateResponse as ProjectValidateResponse
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
+from .project_invite_sme_response import ProjectInviteSmeResponse as ProjectInviteSmeResponse
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
 from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams
 from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams
diff --git a/src/codex/types/project_invite_sme_params.py b/src/codex/types/project_invite_sme_params.py
new file mode 100644
index 00000000..f2694632
--- /dev/null
+++ b/src/codex/types/project_invite_sme_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ProjectInviteSmeParams"]
+
+
+class ProjectInviteSmeParams(TypedDict, total=False):
+    email: Required[str]
+
+    page_type: Required[Literal["query_log", "remediation"]]
+
+    url_query_string: Required[str]
diff --git a/src/codex/types/project_invite_sme_response.py b/src/codex/types/project_invite_sme_response.py
new file mode 100644
index 00000000..8c871a57
--- /dev/null
+++ b/src/codex/types/project_invite_sme_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["ProjectInviteSmeResponse"]
+
+
+class ProjectInviteSmeResponse(BaseModel):
+    recipient_email: str
+
+    status: str
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 2b69e570..8a26aa00 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -5,17 +5,48 @@
 from .entry import Entry as Entry
 from .access_key_schema import AccessKeySchema as AccessKeySchema
 from .entry_query_params import EntryQueryParams as EntryQueryParams
+from .eval_create_params import EvalCreateParams as EvalCreateParams
+from .eval_list_response import EvalListResponse as EvalListResponse
+from .eval_update_params import EvalUpdateParams as EvalUpdateParams
 from .cluster_list_params import ClusterListParams as ClusterListParams
 from .entry_create_params import EntryCreateParams as EntryCreateParams
 from .entry_update_params import EntryUpdateParams as EntryUpdateParams
 from .entry_query_response import EntryQueryResponse as EntryQueryResponse
 from .cluster_list_response import ClusterListResponse as ClusterListResponse
+from .query_log_list_params import QueryLogListParams as QueryLogListParams
 from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams
+from .query_log_list_response import QueryLogListResponse as QueryLogListResponse
+from .remediation_list_params import RemediationListParams as RemediationListParams
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
 from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse
+from .remediation_create_params import RemediationCreateParams as RemediationCreateParams
+from .remediation_list_response import RemediationListResponse as RemediationListResponse
+from .remediation_pause_response import RemediationPauseResponse as RemediationPauseResponse
+from .query_log_retrieve_response import QueryLogRetrieveResponse as QueryLogRetrieveResponse
+from .remediation_create_response import RemediationCreateResponse as RemediationCreateResponse
+from .query_log_list_groups_params import QueryLogListGroupsParams as QueryLogListGroupsParams
+from .remediation_publish_response import RemediationPublishResponse as RemediationPublishResponse
+from .remediation_unpause_response import RemediationUnpauseResponse as RemediationUnpauseResponse
+from .remediation_retrieve_response import RemediationRetrieveResponse as RemediationRetrieveResponse
 from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse
+from .query_log_list_by_group_params import QueryLogListByGroupParams as QueryLogListByGroupParams
+from .query_log_list_groups_response import QueryLogListGroupsResponse as QueryLogListGroupsResponse
+from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams
+from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse
+from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse
+from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse
+from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams
+from .remediation_edit_draft_answer_response import (
+    RemediationEditDraftAnswerResponse as RemediationEditDraftAnswerResponse,
+)
 from .access_key_retrieve_project_id_response import (
     AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse,
 )
+from .remediation_list_resolved_logs_response import (
+    RemediationListResolvedLogsResponse as RemediationListResolvedLogsResponse,
+)
+from .remediation_get_resolved_logs_count_response import (
+    RemediationGetResolvedLogsCountResponse as RemediationGetResolvedLogsCountResponse,
+)
diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py
new file mode 100644
index 00000000..20dcdd3d
--- /dev/null
+++ b/src/codex/types/projects/eval_create_params.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["EvalCreateParams"]
+
+
+class EvalCreateParams(TypedDict, total=False):
+    criteria: Required[str]
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: Required[str]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    is_default: bool
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py
new file mode 100644
index 00000000..e7f2b1b3
--- /dev/null
+++ b/src/codex/types/projects/eval_list_response.py
@@ -0,0 +1,69 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal, TypeAlias
+
+from ..._models import BaseModel
+
+__all__ = ["EvalListResponse", "EvalListResponseItem"]
+
+
+class EvalListResponseItem(BaseModel):
+    criteria: str
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    eval_key: str
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: str
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: Optional[bool] = None
+    """Allows the evaluation to be disabled without removing it"""
+
+    is_default: Optional[bool] = None
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
+    priority: Optional[int] = None
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str] = None
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: Optional[bool] = None
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: Optional[float] = None
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Optional[Literal["above", "below"]] = None
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+EvalListResponse: TypeAlias = List[EvalListResponseItem]
diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py
new file mode 100644
index 00000000..b690ec43
--- /dev/null
+++ b/src/codex/types/projects/eval_update_params.py
@@ -0,0 +1,104 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["EvalUpdateParams", "CustomEvalCreateOrUpdateSchema", "DefaultEvalUpdateSchema"]
+
+
+class CustomEvalCreateOrUpdateSchema(TypedDict, total=False):
+    project_id: Required[str]
+
+    criteria: Required[str]
+    """
+    The evaluation criteria text that describes what aspect is being evaluated and
+    how
+    """
+
+    body_eval_key: Required[Annotated[str, PropertyInfo(alias="eval_key")]]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    name: Required[str]
+    """Display name/label for the evaluation metric"""
+
+    context_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the retrieved
+    context.
+    """
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    is_default: bool
+    """Whether the eval is a default, built-in eval or a custom eval"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    query_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the user's query.
+    """
+
+    response_identifier: Optional[str]
+    """
+    The exact string used in your evaluation criteria to reference the RAG/LLM
+    response.
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+class DefaultEvalUpdateSchema(TypedDict, total=False):
+    project_id: Required[str]
+
+    body_eval_key: Required[Annotated[str, PropertyInfo(alias="eval_key")]]
+    """
+    Unique key for eval metric - currently maps to the TrustworthyRAG name property
+    and eval_scores dictionary key to check against threshold
+    """
+
+    enabled: bool
+    """Allows the evaluation to be disabled without removing it"""
+
+    priority: Optional[int]
+    """
+    Priority order for evals (lower number = higher priority) to determine primary
+    eval issue to surface
+    """
+
+    should_escalate: bool
+    """
+    If true, failing this eval means the response is considered bad and can trigger
+    escalation to Codex/SME
+    """
+
+    threshold: float
+    """Threshold value that determines if the evaluation fails"""
+
+    threshold_direction: Literal["above", "below"]
+    """Whether the evaluation fails when score is above or below the threshold"""
+
+
+EvalUpdateParams: TypeAlias = Union[CustomEvalCreateOrUpdateSchema, DefaultEvalUpdateSchema]
diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py
new file mode 100644
index 00000000..66166a19
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_by_group_params.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["QueryLogListByGroupParams"]
+
+
+class QueryLogListByGroupParams(TypedDict, total=False):
+    created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or before this timestamp"""
+
+    created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or after this timestamp"""
+
+    custom_metadata: Optional[str]
+    """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    primary_eval_issue: Optional[
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+    ]
+    """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
+
+    remediation_ids: List[str]
+    """List of groups to list child logs for"""
+
+    sort: Optional[Literal["created_at", "primary_eval_issue_score"]]
+
+    was_cache_hit: Optional[bool]
+    """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
new file mode 100644
index 00000000..ee79b6fa
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -0,0 +1,96 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = [
+    "QueryLogListByGroupResponse",
+    "QueryLogsByGroup",
+    "QueryLogsByGroupQueryLog",
+    "QueryLogsByGroupQueryLogContext",
+]
+
+
+class QueryLogsByGroupQueryLogContext(BaseModel):
+    content: str
+    """The actual content/text of the document."""
+
+    id: Optional[str] = None
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str] = None
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]] = None
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str] = None
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryLogsByGroupQueryLog(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    """Format evaluation scores for frontend display with pass/fail status.
+
+    Returns: Dictionary mapping eval keys to their formatted representation: {
+    "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if
+    eval_scores is None.
+    """
+
+    is_bad_response: bool
+    """If an eval with should_escalate=True failed"""
+
+    project_id: str
+
+    question: str
+
+    remediation_id: str
+
+    was_cache_hit: Optional[bool] = None
+    """If similar query already answered, or None if cache was not checked"""
+
+    context: Optional[List[QueryLogsByGroupQueryLogContext]] = None
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object] = None
+    """Arbitrary metadata supplied by the user/system"""
+
+    custom_metadata_keys: Optional[List[str]] = None
+    """Keys of the custom metadata"""
+
+    eval_issue_labels: Optional[List[str]] = None
+    """Labels derived from evaluation scores"""
+
+    eval_scores: Optional[Dict[str, float]] = None
+    """Evaluation scores for the original response"""
+
+    eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None
+    """Evaluation thresholds and directions at time of creation"""
+
+    evaluated_response: Optional[str] = None
+    """The response being evaluated from the RAG system (before any remediation)"""
+
+    primary_eval_issue: Optional[str] = None
+    """Primary issue identified in evaluation"""
+
+    primary_eval_issue_score: Optional[float] = None
+    """Score of the primary eval issue"""
+
+
+class QueryLogsByGroup(BaseModel):
+    query_logs: List[QueryLogsByGroupQueryLog]
+
+    total_count: int
+
+
+class QueryLogListByGroupResponse(BaseModel):
+    custom_metadata_columns: List[str]
+
+    query_logs_by_group: Dict[str, QueryLogsByGroup]
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
new file mode 100644
index 00000000..558ac0b2
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["QueryLogListGroupsParams"]
+
+
+class QueryLogListGroupsParams(TypedDict, total=False):
+    created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or before this timestamp"""
+
+    created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or after this timestamp"""
+
+    custom_metadata: Optional[str]
+    """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    primary_eval_issue: Optional[
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+    ]
+    """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
+
+    sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+
+    was_cache_hit: Optional[bool]
+    """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
new file mode 100644
index 00000000..979d34d6
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -0,0 +1,91 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["QueryLogListGroupsResponse", "QueryLogGroup", "QueryLogGroupContext"]
+
+
+class QueryLogGroupContext(BaseModel):
+    content: str
+    """The actual content/text of the document."""
+
+    id: Optional[str] = None
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str] = None
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]] = None
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str] = None
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryLogGroup(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    """Format evaluation scores for frontend display with pass/fail status.
+
+    Returns: Dictionary mapping eval keys to their formatted representation: {
+    "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if
+    eval_scores is None.
+    """
+
+    is_bad_response: bool
+    """If an eval with should_escalate=True failed"""
+
+    project_id: str
+
+    question: str
+
+    remediation_id: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    total_count: int
+
+    was_cache_hit: Optional[bool] = None
+    """If similar query already answered, or None if cache was not checked"""
+
+    context: Optional[List[QueryLogGroupContext]] = None
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object] = None
+    """Arbitrary metadata supplied by the user/system"""
+
+    custom_metadata_keys: Optional[List[str]] = None
+    """Keys of the custom metadata"""
+
+    eval_issue_labels: Optional[List[str]] = None
+    """Labels derived from evaluation scores"""
+
+    eval_scores: Optional[Dict[str, float]] = None
+    """Evaluation scores for the original response"""
+
+    eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None
+    """Evaluation thresholds and directions at time of creation"""
+
+    evaluated_response: Optional[str] = None
+    """The response being evaluated from the RAG system (before any remediation)"""
+
+    primary_eval_issue: Optional[str] = None
+    """Primary issue identified in evaluation"""
+
+    primary_eval_issue_score: Optional[float] = None
+    """Score of the primary eval issue"""
+
+
+class QueryLogListGroupsResponse(BaseModel):
+    custom_metadata_columns: List[str]
+
+    query_log_groups: List[QueryLogGroup]
+
+    total_count: int
diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py
new file mode 100644
index 00000000..9cf3211f
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_params.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["QueryLogListParams"]
+
+
+class QueryLogListParams(TypedDict, total=False):
+    created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or before this timestamp"""
+
+    created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter logs created at or after this timestamp"""
+
+    custom_metadata: Optional[str]
+    """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    primary_eval_issue: Optional[
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+    ]
+    """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
+
+    sort: Optional[Literal["created_at", "primary_eval_issue_score"]]
+
+    was_cache_hit: Optional[bool]
+    """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
new file mode 100644
index 00000000..d570ea50
--- /dev/null
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -0,0 +1,87 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["QueryLogListResponse", "QueryLog", "QueryLogContext"]
+
+
+class QueryLogContext(BaseModel):
+    content: str
+    """The actual content/text of the document."""
+
+    id: Optional[str] = None
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str] = None
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]] = None
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str] = None
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryLog(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    """Format evaluation scores for frontend display with pass/fail status.
+
+    Returns: Dictionary mapping eval keys to their formatted representation: {
+    "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if
+    eval_scores is None.
+    """
+
+    is_bad_response: bool
+    """If an eval with should_escalate=True failed"""
+
+    project_id: str
+
+    question: str
+
+    remediation_id: str
+
+    was_cache_hit: Optional[bool] = None
+    """If similar query already answered, or None if cache was not checked"""
+
+    context: Optional[List[QueryLogContext]] = None
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object] = None
+    """Arbitrary metadata supplied by the user/system"""
+
+    custom_metadata_keys: Optional[List[str]] = None
+    """Keys of the custom metadata"""
+
+    eval_issue_labels: Optional[List[str]] = None
+    """Labels derived from evaluation scores"""
+
+    eval_scores: Optional[Dict[str, float]] = None
+    """Evaluation scores for the original response"""
+
+    eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None
+    """Evaluation thresholds and directions at time of creation"""
+
+    evaluated_response: Optional[str] = None
+    """The response being evaluated from the RAG system (before any remediation)"""
+
+    primary_eval_issue: Optional[str] = None
+    """Primary issue identified in evaluation"""
+
+    primary_eval_issue_score: Optional[float] = None
+    """Score of the primary eval issue"""
+
+
+class QueryLogListResponse(BaseModel):
+    custom_metadata_columns: List[str]
+
+    query_logs: List[QueryLog]
+
+    total_count: int
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
new file mode 100644
index 00000000..f918c214
--- /dev/null
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["QueryLogRetrieveResponse", "Context"]
+
+
+class Context(BaseModel):
+    content: str
+    """The actual content/text of the document."""
+
+    id: Optional[str] = None
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str] = None
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]] = None
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str] = None
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryLogRetrieveResponse(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    """Format evaluation scores for frontend display with pass/fail status.
+
+    Returns: Dictionary mapping eval keys to their formatted representation: {
+    "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if
+    eval_scores is None.
+    """
+
+    is_bad_response: bool
+    """If an eval with should_escalate=True failed"""
+
+    project_id: str
+
+    question: str
+
+    remediation_id: str
+
+    was_cache_hit: Optional[bool] = None
+    """If similar query already answered, or None if cache was not checked"""
+
+    context: Optional[List[Context]] = None
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object] = None
+    """Arbitrary metadata supplied by the user/system"""
+
+    custom_metadata_keys: Optional[List[str]] = None
+    """Keys of the custom metadata"""
+
+    eval_issue_labels: Optional[List[str]] = None
+    """Labels derived from evaluation scores"""
+
+    eval_scores: Optional[Dict[str, float]] = None
+    """Evaluation scores for the original response"""
+
+    eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None
+    """Evaluation thresholds and directions at time of creation"""
+
+    evaluated_response: Optional[str] = None
+    """The response being evaluated from the RAG system (before any remediation)"""
+
+    primary_eval_issue: Optional[str] = None
+    """Primary issue identified in evaluation"""
+
+    primary_eval_issue_score: Optional[float] = None
+    """Score of the primary eval issue"""
diff --git a/src/codex/types/projects/query_log_start_remediation_response.py b/src/codex/types/projects/query_log_start_remediation_response.py
new file mode 100644
index 00000000..0250fb15
--- /dev/null
+++ b/src/codex/types/projects/query_log_start_remediation_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["QueryLogStartRemediationResponse"]
+
+
+class QueryLogStartRemediationResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_create_params.py b/src/codex/types/projects/remediation_create_params.py
new file mode 100644
index 00000000..0d347490
--- /dev/null
+++ b/src/codex/types/projects/remediation_create_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["RemediationCreateParams"]
+
+
+class RemediationCreateParams(TypedDict, total=False):
+    question: Required[str]
+
+    answer: Optional[str]
+
+    draft_answer: Optional[str]
diff --git a/src/codex/types/projects/remediation_create_response.py b/src/codex/types/projects/remediation_create_response.py
new file mode 100644
index 00000000..ad4e6893
--- /dev/null
+++ b/src/codex/types/projects/remediation_create_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationCreateResponse"]
+
+
+class RemediationCreateResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_edit_answer_params.py b/src/codex/types/projects/remediation_edit_answer_params.py
new file mode 100644
index 00000000..0dbc7d8d
--- /dev/null
+++ b/src/codex/types/projects/remediation_edit_answer_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["RemediationEditAnswerParams"]
+
+
+class RemediationEditAnswerParams(TypedDict, total=False):
+    project_id: Required[str]
+
+    answer: Required[str]
diff --git a/src/codex/types/projects/remediation_edit_answer_response.py b/src/codex/types/projects/remediation_edit_answer_response.py
new file mode 100644
index 00000000..d8b34323
--- /dev/null
+++ b/src/codex/types/projects/remediation_edit_answer_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationEditAnswerResponse"]
+
+
+class RemediationEditAnswerResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_edit_draft_answer_params.py b/src/codex/types/projects/remediation_edit_draft_answer_params.py
new file mode 100644
index 00000000..9f51ec6f
--- /dev/null
+++ b/src/codex/types/projects/remediation_edit_draft_answer_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["RemediationEditDraftAnswerParams"]
+
+
+class RemediationEditDraftAnswerParams(TypedDict, total=False):
+    project_id: Required[str]
+
+    draft_answer: Required[str]
diff --git a/src/codex/types/projects/remediation_edit_draft_answer_response.py b/src/codex/types/projects/remediation_edit_draft_answer_response.py
new file mode 100644
index 00000000..828035e8
--- /dev/null
+++ b/src/codex/types/projects/remediation_edit_draft_answer_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationEditDraftAnswerResponse"]
+
+
+class RemediationEditDraftAnswerResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py
new file mode 100644
index 00000000..79997b09
--- /dev/null
+++ b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationGetResolvedLogsCountResponse"]
+
+
+class RemediationGetResolvedLogsCountResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    resolved_logs_count: int
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_list_params.py b/src/codex/types/projects/remediation_list_params.py
new file mode 100644
index 00000000..65cd08d0
--- /dev/null
+++ b/src/codex/types/projects/remediation_list_params.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["RemediationListParams"]
+
+
+class RemediationListParams(TypedDict, total=False):
+    created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter remediations created at or before this timestamp"""
+
+    created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter remediations created at or after this timestamp"""
+
+    last_edited_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter remediations last edited at or before this timestamp"""
+
+    last_edited_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+    """Filter remediations last edited at or after this timestamp"""
+
+    last_edited_by: Optional[str]
+    """Filter by last edited by user ID"""
+
+    limit: int
+
+    offset: int
+
+    order: Literal["asc", "desc"]
+
+    sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]]
+
+    status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]]
+    """Filter remediations that have ANY of these statuses (OR operation)"""
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
new file mode 100644
index 00000000..6181028d
--- /dev/null
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationListResolvedLogsResponse", "QueryLog", "QueryLogContext"]
+
+
+class QueryLogContext(BaseModel):
+    content: str
+    """The actual content/text of the document."""
+
+    id: Optional[str] = None
+    """Unique identifier for the document. Useful for tracking documents"""
+
+    source: Optional[str] = None
+    """Source or origin of the document. Useful for citations."""
+
+    tags: Optional[List[str]] = None
+    """Tags or categories for the document. Useful for filtering"""
+
+    title: Optional[str] = None
+    """Title or heading of the document. Useful for display and context."""
+
+
+class QueryLog(BaseModel):
+    id: str
+
+    created_at: datetime
+
+    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    """Format evaluation scores for frontend display with pass/fail status.
+
+    Returns: Dictionary mapping eval keys to their formatted representation: {
+    "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if
+    eval_scores is None.
+    """
+
+    is_bad_response: bool
+    """If an eval with should_escalate=True failed"""
+
+    project_id: str
+
+    question: str
+
+    remediation_id: str
+
+    was_cache_hit: Optional[bool] = None
+    """If similar query already answered, or None if cache was not checked"""
+
+    context: Optional[List[QueryLogContext]] = None
+    """RAG context used for the query"""
+
+    custom_metadata: Optional[object] = None
+    """Arbitrary metadata supplied by the user/system"""
+
+    custom_metadata_keys: Optional[List[str]] = None
+    """Keys of the custom metadata"""
+
+    eval_issue_labels: Optional[List[str]] = None
+    """Labels derived from evaluation scores"""
+
+    eval_scores: Optional[Dict[str, float]] = None
+    """Evaluation scores for the original response"""
+
+    eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None
+    """Evaluation thresholds and directions at time of creation"""
+
+    evaluated_response: Optional[str] = None
+    """The response being evaluated from the RAG system (before any remediation)"""
+
+    primary_eval_issue: Optional[str] = None
+    """Primary issue identified in evaluation"""
+
+    primary_eval_issue_score: Optional[float] = None
+    """Score of the primary eval issue"""
+
+
+class RemediationListResolvedLogsResponse(BaseModel):
+    query_logs: List[QueryLog]
+
+    total_count: int
diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py
new file mode 100644
index 00000000..3e737970
--- /dev/null
+++ b/src/codex/types/projects/remediation_list_response.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationListResponse", "Remediation"]
+
+
+class Remediation(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    resolved_logs_count: int
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
+
+
+class RemediationListResponse(BaseModel):
+    remediations: List[Remediation]
+
+    total_count: int
diff --git a/src/codex/types/projects/remediation_pause_response.py b/src/codex/types/projects/remediation_pause_response.py
new file mode 100644
index 00000000..ae453581
--- /dev/null
+++ b/src/codex/types/projects/remediation_pause_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationPauseResponse"]
+
+
+class RemediationPauseResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_publish_response.py b/src/codex/types/projects/remediation_publish_response.py
new file mode 100644
index 00000000..5eb2c622
--- /dev/null
+++ b/src/codex/types/projects/remediation_publish_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationPublishResponse"]
+
+
+class RemediationPublishResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_retrieve_response.py b/src/codex/types/projects/remediation_retrieve_response.py
new file mode 100644
index 00000000..6fbd60d2
--- /dev/null
+++ b/src/codex/types/projects/remediation_retrieve_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationRetrieveResponse"]
+
+
+class RemediationRetrieveResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_unpause_response.py b/src/codex/types/projects/remediation_unpause_response.py
new file mode 100644
index 00000000..789484cd
--- /dev/null
+++ b/src/codex/types/projects/remediation_unpause_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["RemediationUnpauseResponse"]
+
+
+class RemediationUnpauseResponse(BaseModel):
+    id: str
+
+    answered_at: Optional[datetime] = None
+
+    answered_by: Optional[str] = None
+
+    created_at: datetime
+
+    last_edited_at: Optional[datetime] = None
+
+    last_edited_by: Optional[str] = None
+
+    project_id: str
+
+    question: str
+
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+
+    answer: Optional[str] = None
+
+    draft_answer: Optional[str] = None
diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py
new file mode 100644
index 00000000..c16fbc37
--- /dev/null
+++ b/tests/api_resources/projects/test_evals.py
@@ -0,0 +1,679 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from codex.types import ProjectReturnSchema
+from tests.utils import assert_matches_type
+from codex.types.projects import EvalListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEvals:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        eval = client.projects.evals.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        eval = client.projects.evals.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+            context_identifier="context_identifier",
+            enabled=True,
+            is_default=True,
+            priority=0,
+            query_identifier="query_identifier",
+            response_identifier="response_identifier",
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.evals.with_raw_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.evals.with_streaming_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_create(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.evals.with_raw_response.create(
+                project_id="",
+                criteria="criteria",
+                eval_key="eval_key",
+                name="name",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_overload_1(self, client: Codex) -> None:
+        eval = client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params_overload_1(self, client: Codex) -> None:
+        eval = client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+            context_identifier="context_identifier",
+            enabled=True,
+            is_default=True,
+            priority=0,
+            query_identifier="query_identifier",
+            response_identifier="response_identifier",
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update_overload_1(self, client: Codex) -> None:
+        response = client.projects.evals.with_raw_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update_overload_1(self, client: Codex) -> None:
+        with client.projects.evals.with_streaming_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_update_overload_1(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.evals.with_raw_response.update(
+                path_eval_key="eval_key",
+                project_id="",
+                criteria="criteria",
+                body_eval_key="eval_key",
+                name="name",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"):
+            client.projects.evals.with_raw_response.update(
+                path_eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                criteria="criteria",
+                body_eval_key="eval_key",
+                name="name",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_overload_2(self, client: Codex) -> None:
+        eval = client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_update_with_all_params_overload_2(self, client: Codex) -> None:
+        eval = client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+            enabled=True,
+            priority=0,
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_update_overload_2(self, client: Codex) -> None:
+        response = client.projects.evals.with_raw_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_update_overload_2(self, client: Codex) -> None:
+        with client.projects.evals.with_streaming_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_update_overload_2(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.evals.with_raw_response.update(
+                path_eval_key="eval_key",
+                project_id="",
+                body_eval_key="eval_key",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"):
+            client.projects.evals.with_raw_response.update(
+                path_eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                body_eval_key="eval_key",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        eval = client.projects.evals.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.evals.with_raw_response.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = response.parse()
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.evals.with_streaming_response.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = response.parse()
+            assert_matches_type(EvalListResponse, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.evals.with_raw_response.list(
+                "",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        eval = client.projects.evals.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.evals.with_raw_response.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.evals.with_streaming_response.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.evals.with_raw_response.delete(
+                eval_key="eval_key",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_key` but received ''"):
+            client.projects.evals.with_raw_response.delete(
+                eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+
+class TestAsyncEvals:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+            context_identifier="context_identifier",
+            enabled=True,
+            is_default=True,
+            priority=0,
+            query_identifier="query_identifier",
+            response_identifier="response_identifier",
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.evals.with_raw_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = await response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.evals.with_streaming_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            eval_key="eval_key",
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = await response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.evals.with_raw_response.create(
+                project_id="",
+                criteria="criteria",
+                eval_key="eval_key",
+                name="name",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params_overload_1(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+            context_identifier="context_identifier",
+            enabled=True,
+            is_default=True,
+            priority=0,
+            query_identifier="query_identifier",
+            response_identifier="response_identifier",
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.evals.with_raw_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = await response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update_overload_1(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.evals.with_streaming_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            criteria="criteria",
+            body_eval_key="eval_key",
+            name="name",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = await response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_update_overload_1(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.evals.with_raw_response.update(
+                path_eval_key="eval_key",
+                project_id="",
+                criteria="criteria",
+                body_eval_key="eval_key",
+                name="name",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"):
+            await async_client.projects.evals.with_raw_response.update(
+                path_eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                criteria="criteria",
+                body_eval_key="eval_key",
+                name="name",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_update_with_all_params_overload_2(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+            enabled=True,
+            priority=0,
+            should_escalate=True,
+            threshold=0,
+            threshold_direction="above",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.evals.with_raw_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = await response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_update_overload_2(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.evals.with_streaming_response.update(
+            path_eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            body_eval_key="eval_key",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = await response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.evals.with_raw_response.update(
+                path_eval_key="eval_key",
+                project_id="",
+                body_eval_key="eval_key",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"):
+            await async_client.projects.evals.with_raw_response.update(
+                path_eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                body_eval_key="eval_key",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.evals.with_raw_response.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = await response.parse()
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.evals.with_streaming_response.list(
+            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = await response.parse()
+            assert_matches_type(EvalListResponse, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.evals.with_raw_response.list(
+                "",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.evals.with_raw_response.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        eval = await response.parse()
+        assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.evals.with_streaming_response.delete(
+            eval_key="eval_key",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            eval = await response.parse()
+            assert_matches_type(ProjectReturnSchema, eval, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.evals.with_raw_response.delete(
+                eval_key="eval_key",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_key` but received ''"):
+            await async_client.projects.evals.with_raw_response.delete(
+                eval_key="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
new file mode 100644
index 00000000..847af5df
--- /dev/null
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -0,0 +1,593 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex._utils import parse_datetime
+from codex.types.projects import (
+    QueryLogListResponse,
+    QueryLogRetrieveResponse,
+    QueryLogListGroupsResponse,
+    QueryLogListByGroupResponse,
+    QueryLogStartRemediationResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestQueryLogs:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.query_logs.with_raw_response.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = response.parse()
+        assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.query_logs.with_streaming_response.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = response.parse()
+            assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.query_logs.with_raw_response.retrieve(
+                query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"):
+            client.projects.query_logs.with_raw_response.retrieve(
+                query_log_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.query_logs.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = response.parse()
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.query_logs.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = response.parse()
+            assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.query_logs.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_by_group(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_by_group(self, client: Codex) -> None:
+        response = client.projects.query_logs.with_raw_response.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = response.parse()
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_by_group(self, client: Codex) -> None:
+        with client.projects.query_logs.with_streaming_response.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = response.parse()
+            assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list_by_group(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.query_logs.with_raw_response.list_by_group(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_groups(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_groups_with_all_params(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_groups(self, client: Codex) -> None:
+        response = client.projects.query_logs.with_raw_response.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = response.parse()
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_groups(self, client: Codex) -> None:
+        with client.projects.query_logs.with_streaming_response.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = response.parse()
+            assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list_groups(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.query_logs.with_raw_response.list_groups(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_start_remediation(self, client: Codex) -> None:
+        query_log = client.projects.query_logs.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_start_remediation(self, client: Codex) -> None:
+        response = client.projects.query_logs.with_raw_response.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = response.parse()
+        assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_start_remediation(self, client: Codex) -> None:
+        with client.projects.query_logs.with_streaming_response.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = response.parse()
+            assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_start_remediation(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.query_logs.with_raw_response.start_remediation(
+                query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"):
+            client.projects.query_logs.with_raw_response.start_remediation(
+                query_log_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+
+class TestAsyncQueryLogs:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.query_logs.with_raw_response.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = await response.parse()
+        assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.query_logs.with_streaming_response.retrieve(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = await response.parse()
+            assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.retrieve(
+                query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.retrieve(
+                query_log_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.query_logs.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = await response.parse()
+        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.query_logs.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = await response.parse()
+            assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_by_group(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_by_group_with_all_params(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.query_logs.with_raw_response.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = await response.parse()
+        assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.query_logs.with_streaming_response.list_by_group(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = await response.parse()
+            assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.list_by_group(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_groups(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            custom_metadata="custom_metadata",
+            limit=1,
+            offset=0,
+            order="asc",
+            primary_eval_issue=["hallucination"],
+            sort="created_at",
+            was_cache_hit=True,
+        )
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.query_logs.with_raw_response.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = await response.parse()
+        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.query_logs.with_streaming_response.list_groups(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = await response.parse()
+            assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.list_groups(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_start_remediation(self, async_client: AsyncCodex) -> None:
+        query_log = await async_client.projects.query_logs.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.query_logs.with_raw_response.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        query_log = await response.parse()
+        assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_start_remediation(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.query_logs.with_streaming_response.start_remediation(
+            query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            query_log = await response.parse()
+            assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.start_remediation(
+                query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"):
+            await async_client.projects.query_logs.with_raw_response.start_remediation(
+                query_log_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py
new file mode 100644
index 00000000..cfd5ae76
--- /dev/null
+++ b/tests/api_resources/projects/test_remediations.py
@@ -0,0 +1,1224 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from codex import Codex, AsyncCodex
+from tests.utils import assert_matches_type
+from codex._utils import parse_datetime
+from codex.types.projects import (
+    RemediationListResponse,
+    RemediationPauseResponse,
+    RemediationCreateResponse,
+    RemediationPublishResponse,
+    RemediationUnpauseResponse,
+    RemediationRetrieveResponse,
+    RemediationEditAnswerResponse,
+    RemediationEditDraftAnswerResponse,
+    RemediationListResolvedLogsResponse,
+    RemediationGetResolvedLogsCountResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRemediations:
+    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create(self, client: Codex) -> None:
+        remediation = client.projects.remediations.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        )
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_create_with_all_params(self, client: Codex) -> None:
+        remediation = client.projects.remediations.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            answer="answer",
+            draft_answer="draft_answer",
+        )
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_create(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_create(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_create(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.create(
+                project_id="",
+                question="question",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_retrieve(self, client: Codex) -> None:
+        remediation = client.projects.remediations.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_retrieve(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_retrieve(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_retrieve(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.retrieve(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.retrieve(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list(self, client: Codex) -> None:
+        remediation = client.projects.remediations.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        remediation = client.projects.remediations.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_by="last_edited_by",
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            status=["ACTIVE"],
+        )
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_delete(self, client: Codex) -> None:
+        remediation = client.projects.remediations.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert remediation is None
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_delete(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert remediation is None
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_delete(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert remediation is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_delete(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.delete(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.delete(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_edit_answer(self, client: Codex) -> None:
+        remediation = client.projects.remediations.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        )
+        assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_edit_answer(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_edit_answer(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_edit_answer(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.edit_answer(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                answer="answer",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.edit_answer(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                answer="answer",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_edit_draft_answer(self, client: Codex) -> None:
+        remediation = client.projects.remediations.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        )
+        assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_edit_draft_answer(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_edit_draft_answer(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_edit_draft_answer(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.edit_draft_answer(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                draft_answer="draft_answer",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.edit_draft_answer(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                draft_answer="draft_answer",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_get_resolved_logs_count(self, client: Codex) -> None:
+        remediation = client.projects.remediations.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_get_resolved_logs_count(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.get_resolved_logs_count(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.get_resolved_logs_count(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_resolved_logs(self, client: Codex) -> None:
+        remediation = client.projects.remediations.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_list_resolved_logs(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_list_resolved_logs(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_list_resolved_logs(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.list_resolved_logs(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.list_resolved_logs(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_pause(self, client: Codex) -> None:
+        remediation = client.projects.remediations.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_pause(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_pause(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_pause(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.pause(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.pause(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_publish(self, client: Codex) -> None:
+        remediation = client.projects.remediations.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_publish(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_publish(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_publish(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.publish(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.publish(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_unpause(self, client: Codex) -> None:
+        remediation = client.projects.remediations.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_unpause(self, client: Codex) -> None:
+        response = client.projects.remediations.with_raw_response.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = response.parse()
+        assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_unpause(self, client: Codex) -> None:
+        with client.projects.remediations.with_streaming_response.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = response.parse()
+            assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_unpause(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.remediations.with_raw_response.unpause(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            client.projects.remediations.with_raw_response.unpause(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+
+class TestAsyncRemediations:
+    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        )
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+            answer="answer",
+            draft_answer="draft_answer",
+        )
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.create(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            question="question",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.create(
+                project_id="",
+                question="question",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.retrieve(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.retrieve(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.retrieve(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+            last_edited_by="last_edited_by",
+            limit=1,
+            offset=0,
+            order="asc",
+            sort="created_at",
+            status=["ACTIVE"],
+        )
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationListResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.list(
+                project_id="",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_delete(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert remediation is None
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert remediation is None
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.delete(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert remediation is None
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.delete(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.delete(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_edit_answer(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        )
+        assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.edit_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            answer="answer",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.edit_answer(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                answer="answer",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.edit_answer(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                answer="answer",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        )
+        assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.edit_draft_answer(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            draft_answer="draft_answer",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.edit_draft_answer(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+                draft_answer="draft_answer",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.edit_draft_answer(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                draft_answer="draft_answer",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.get_resolved_logs_count(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.get_resolved_logs_count(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.list_resolved_logs(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.list_resolved_logs(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.list_resolved_logs(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_pause(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_pause(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.pause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_pause(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.pause(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.pause(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_publish(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_publish(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.publish(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_publish(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.publish(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.publish(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_unpause(self, async_client: AsyncCodex) -> None:
+        remediation = await async_client.projects.remediations.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.remediations.with_raw_response.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        remediation = await response.parse()
+        assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.remediations.with_streaming_response.unpause(
+            remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            remediation = await response.parse()
+            assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_unpause(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.unpause(
+                remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+                project_id="",
+            )
+
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"):
+            await async_client.projects.remediations.with_raw_response.unpause(
+                remediation_id="",
+                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index c2b5b7db..86bd8053 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -13,6 +13,7 @@
     ProjectReturnSchema,
     ProjectRetrieveResponse,
     ProjectValidateResponse,
+    ProjectInviteSmeResponse,
     ProjectRetrieveAnalyticsResponse,
 )
 from tests.utils import assert_matches_type
@@ -515,6 +516,60 @@ def test_path_params_increment_queries(self, client: Codex) -> None:
                     project_id="",
                 )
 
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_invite_sme(self, client: Codex) -> None:
+        project = client.projects.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        )
+        assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_raw_response_invite_sme(self, client: Codex) -> None:
+        response = client.projects.with_raw_response.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = response.parse()
+        assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_streaming_response_invite_sme(self, client: Codex) -> None:
+        with client.projects.with_streaming_response.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = response.parse()
+            assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_path_params_invite_sme(self, client: Codex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            client.projects.with_raw_response.invite_sme(
+                project_id="",
+                email="email",
+                page_type="query_log",
+                url_query_string="url_query_string",
+            )
+
     @pytest.mark.skip()
     @parametrize
     def test_method_retrieve_analytics(self, client: Codex) -> None:
@@ -1160,6 +1215,60 @@ async def test_path_params_increment_queries(self, async_client: AsyncCodex) ->
                     project_id="",
                 )
 
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_invite_sme(self, async_client: AsyncCodex) -> None:
+        project = await async_client.projects.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        )
+        assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None:
+        response = await async_client.projects.with_raw_response.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        )
+
+        assert response.is_closed is True
+        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+        project = await response.parse()
+        assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) -> None:
+        async with async_client.projects.with_streaming_response.invite_sme(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            email="email",
+            page_type="query_log",
+            url_query_string="url_query_string",
+        ) as response:
+            assert not response.is_closed
+            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+            project = await response.parse()
+            assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
+
+        assert cast(Any, response.is_closed) is True
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None:
+        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+            await async_client.projects.with_raw_response.invite_sme(
+                project_id="",
+                email="email",
+                page_type="query_log",
+                url_query_string="url_query_string",
+            )
+
     @pytest.mark.skip()
     @parametrize
     async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None:

From 407fce98af9d619ffdc2034f9a1e4f1d57deabd2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 20 Jun 2025 23:35:06 +0000
Subject: [PATCH 172/227] feat(api): add bearer token auth

---
 .stats.yml           |  2 +-
 README.md            |  2 ++
 src/codex/_client.py | 44 ++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index bd77f787..20dc969f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
 openapi_spec_hash: eeb8ebc5600523bdfad046381a929572
-config_hash: 63e520502003839482d0dbeb82132064
+config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/README.md b/README.md
index ae489b6e..2e04092c 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ The full API of this library can be found in [api.md](api.md).
 from codex import Codex
 
 client = Codex(
+    auth_token="My Auth Token",
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
@@ -48,6 +49,7 @@ import asyncio
 from codex import AsyncCodex
 
 client = AsyncCodex(
+    auth_token="My Auth Token",
     # or 'production' | 'local'; defaults to "production".
     environment="staging",
 )
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 7464f6dc..352d0dbc 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -63,6 +63,7 @@ class Codex(SyncAPIClient):
     with_streaming_response: CodexWithStreamedResponse
 
     # client options
+    auth_token: str | None
     api_key: str | None
     access_key: str | None
 
@@ -71,6 +72,7 @@ class Codex(SyncAPIClient):
     def __init__(
         self,
         *,
+        auth_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
@@ -94,6 +96,8 @@ def __init__(
         _strict_response_validation: bool = False,
     ) -> None:
         """Construct a new synchronous Codex client instance."""
+        self.auth_token = auth_token
+
         self.api_key = api_key
 
         self.access_key = access_key
@@ -151,7 +155,14 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        return {**self._authenticated_api_key, **self._public_access_key}
+        return {**self._http_bearer, **self._authenticated_api_key, **self._public_access_key}
+
+    @property
+    def _http_bearer(self) -> dict[str, str]:
+        auth_token = self.auth_token
+        if auth_token is None:
+            return {}
+        return {"Authorization": f"Bearer {auth_token}"}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
@@ -178,6 +189,11 @@ def default_headers(self) -> dict[str, str | Omit]:
 
     @override
     def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.auth_token and headers.get("Authorization"):
+            return
+        if isinstance(custom_headers.get("Authorization"), Omit):
+            return
+
         if self.api_key and headers.get("X-API-Key"):
             return
         if isinstance(custom_headers.get("X-API-Key"), Omit):
@@ -189,12 +205,13 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
             return
 
         raise TypeError(
-            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+            '"Could not resolve authentication method. Expected one of auth_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
         )
 
     def copy(
         self,
         *,
+        auth_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | None = None,
@@ -231,6 +248,7 @@ def copy(
 
         http_client = http_client or self._client
         return self.__class__(
+            auth_token=auth_token or self.auth_token,
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,
@@ -291,6 +309,7 @@ class AsyncCodex(AsyncAPIClient):
     with_streaming_response: AsyncCodexWithStreamedResponse
 
     # client options
+    auth_token: str | None
     api_key: str | None
     access_key: str | None
 
@@ -299,6 +318,7 @@ class AsyncCodex(AsyncAPIClient):
     def __init__(
         self,
         *,
+        auth_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN,
@@ -322,6 +342,8 @@ def __init__(
         _strict_response_validation: bool = False,
     ) -> None:
         """Construct a new async AsyncCodex client instance."""
+        self.auth_token = auth_token
+
         self.api_key = api_key
 
         self.access_key = access_key
@@ -379,7 +401,14 @@ def qs(self) -> Querystring:
     @property
     @override
     def auth_headers(self) -> dict[str, str]:
-        return {**self._authenticated_api_key, **self._public_access_key}
+        return {**self._http_bearer, **self._authenticated_api_key, **self._public_access_key}
+
+    @property
+    def _http_bearer(self) -> dict[str, str]:
+        auth_token = self.auth_token
+        if auth_token is None:
+            return {}
+        return {"Authorization": f"Bearer {auth_token}"}
 
     @property
     def _authenticated_api_key(self) -> dict[str, str]:
@@ -406,6 +435,11 @@ def default_headers(self) -> dict[str, str | Omit]:
 
     @override
     def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+        if self.auth_token and headers.get("Authorization"):
+            return
+        if isinstance(custom_headers.get("Authorization"), Omit):
+            return
+
         if self.api_key and headers.get("X-API-Key"):
             return
         if isinstance(custom_headers.get("X-API-Key"), Omit):
@@ -417,12 +451,13 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
             return
 
         raise TypeError(
-            '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
+            '"Could not resolve authentication method. Expected one of auth_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"'
         )
 
     def copy(
         self,
         *,
+        auth_token: str | None = None,
         api_key: str | None = None,
         access_key: str | None = None,
         environment: Literal["production", "staging", "local"] | None = None,
@@ -459,6 +494,7 @@ def copy(
 
         http_client = http_client or self._client
         return self.__class__(
+            auth_token=auth_token or self.auth_token,
             api_key=api_key or self.api_key,
             access_key=access_key or self.access_key,
             base_url=base_url or self.base_url,

From 731656ac936c7a166ebb369b2775074248ea5ced Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 21 Jun 2025 04:02:37 +0000
Subject: [PATCH 173/227] feat(client): add support for aiohttp

---
 README.md                                     | 35 +++++++++++++++++
 pyproject.toml                                |  2 +
 requirements-dev.lock                         | 27 +++++++++++++
 requirements.lock                             | 27 +++++++++++++
 src/codex/__init__.py                         |  3 +-
 src/codex/_base_client.py                     | 22 +++++++++++
 .../billing/test_card_details.py              |  4 +-
 .../billing/test_plan_details.py              |  4 +-
 .../billing/test_setup_intent.py              |  4 +-
 .../organizations/test_billing.py             |  4 +-
 .../projects/test_access_keys.py              |  4 +-
 tests/api_resources/projects/test_clusters.py |  4 +-
 tests/api_resources/projects/test_entries.py  |  4 +-
 tests/api_resources/projects/test_evals.py    |  4 +-
 .../api_resources/projects/test_query_logs.py |  4 +-
 .../projects/test_remediations.py             |  4 +-
 tests/api_resources/test_health.py            |  4 +-
 tests/api_resources/test_organizations.py     |  4 +-
 tests/api_resources/test_projects.py          |  4 +-
 tests/api_resources/test_tlm.py               |  4 +-
 tests/api_resources/test_users.py             |  4 +-
 .../users/myself/test_api_key.py              |  4 +-
 .../users/myself/test_organizations.py        |  4 +-
 tests/api_resources/users/test_myself.py      |  4 +-
 .../api_resources/users/test_verification.py  |  4 +-
 tests/conftest.py                             | 39 ++++++++++++++++---
 26 files changed, 206 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index 2e04092c..74f841bf 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,41 @@ asyncio.run(main())
 
 Functionality between the synchronous and asynchronous clients is otherwise identical.
 
+### With aiohttp
+
+By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
+
+You can enable this by installing `aiohttp`:
+
+```sh
+# install from PyPI
+pip install --pre codex-sdk[aiohttp]
+```
+
+Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
+
+```python
+import asyncio
+from codex import DefaultAioHttpClient
+from codex import AsyncCodex
+
+
+async def main() -> None:
+    async with AsyncCodex(
+        auth_token="My Auth Token",
+        http_client=DefaultAioHttpClient(),
+    ) as client:
+        project_return_schema = await client.projects.create(
+            config={},
+            name="name",
+            organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        print(project_return_schema.id)
+
+
+asyncio.run(main())
+```
+
 ## Using types
 
 Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
diff --git a/pyproject.toml b/pyproject.toml
index 68924033..bad25395 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,6 +37,8 @@ classifiers = [
 Homepage = "https://github.com/cleanlab/codex-python"
 Repository = "https://github.com/cleanlab/codex-python"
 
+[project.optional-dependencies]
+aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
 
 [tool.rye]
 managed = true
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 9e127b74..11a27038 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -10,6 +10,13 @@
 #   universal: false
 
 -e file:.
+aiohappyeyeballs==2.6.1
+    # via aiohttp
+aiohttp==3.12.8
+    # via codex-sdk
+    # via httpx-aiohttp
+aiosignal==1.3.2
+    # via aiohttp
 annotated-types==0.6.0
     # via pydantic
 anyio==4.4.0
@@ -17,6 +24,10 @@ anyio==4.4.0
     # via httpx
 argcomplete==3.1.2
     # via nox
+async-timeout==5.0.1
+    # via aiohttp
+attrs==25.3.0
+    # via aiohttp
 certifi==2023.7.22
     # via httpcore
     # via httpx
@@ -34,16 +45,23 @@ execnet==2.1.1
     # via pytest-xdist
 filelock==3.12.4
     # via virtualenv
+frozenlist==1.6.2
+    # via aiohttp
+    # via aiosignal
 h11==0.14.0
     # via httpcore
 httpcore==1.0.2
     # via httpx
 httpx==0.28.1
     # via codex-sdk
+    # via httpx-aiohttp
     # via respx
+httpx-aiohttp==0.1.6
+    # via codex-sdk
 idna==3.4
     # via anyio
     # via httpx
+    # via yarl
 importlib-metadata==7.0.0
 iniconfig==2.0.0
     # via pytest
@@ -51,6 +69,9 @@ markdown-it-py==3.0.0
     # via rich
 mdurl==0.1.2
     # via markdown-it-py
+multidict==6.4.4
+    # via aiohttp
+    # via yarl
 mypy==1.14.1
 mypy-extensions==1.0.0
     # via mypy
@@ -65,6 +86,9 @@ platformdirs==3.11.0
     # via virtualenv
 pluggy==1.5.0
     # via pytest
+propcache==0.3.1
+    # via aiohttp
+    # via yarl
 pydantic==2.10.3
     # via codex-sdk
 pydantic-core==2.27.1
@@ -98,11 +122,14 @@ tomli==2.0.2
 typing-extensions==4.12.2
     # via anyio
     # via codex-sdk
+    # via multidict
     # via mypy
     # via pydantic
     # via pydantic-core
     # via pyright
 virtualenv==20.24.5
     # via nox
+yarl==1.20.0
+    # via aiohttp
 zipp==3.17.0
     # via importlib-metadata
diff --git a/requirements.lock b/requirements.lock
index 1f6c5f7f..5eda9d9c 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -10,11 +10,22 @@
 #   universal: false
 
 -e file:.
+aiohappyeyeballs==2.6.1
+    # via aiohttp
+aiohttp==3.12.8
+    # via codex-sdk
+    # via httpx-aiohttp
+aiosignal==1.3.2
+    # via aiohttp
 annotated-types==0.6.0
     # via pydantic
 anyio==4.4.0
     # via codex-sdk
     # via httpx
+async-timeout==5.0.1
+    # via aiohttp
+attrs==25.3.0
+    # via aiohttp
 certifi==2023.7.22
     # via httpcore
     # via httpx
@@ -22,15 +33,28 @@ distro==1.8.0
     # via codex-sdk
 exceptiongroup==1.2.2
     # via anyio
+frozenlist==1.6.2
+    # via aiohttp
+    # via aiosignal
 h11==0.14.0
     # via httpcore
 httpcore==1.0.2
     # via httpx
 httpx==0.28.1
     # via codex-sdk
+    # via httpx-aiohttp
+httpx-aiohttp==0.1.6
+    # via codex-sdk
 idna==3.4
     # via anyio
     # via httpx
+    # via yarl
+multidict==6.4.4
+    # via aiohttp
+    # via yarl
+propcache==0.3.1
+    # via aiohttp
+    # via yarl
 pydantic==2.10.3
     # via codex-sdk
 pydantic-core==2.27.1
@@ -41,5 +65,8 @@ sniffio==1.3.0
 typing-extensions==4.12.2
     # via anyio
     # via codex-sdk
+    # via multidict
     # via pydantic
     # via pydantic-core
+yarl==1.20.0
+    # via aiohttp
diff --git a/src/codex/__init__.py b/src/codex/__init__.py
index 5c5f678c..2b07cd12 100644
--- a/src/codex/__init__.py
+++ b/src/codex/__init__.py
@@ -37,7 +37,7 @@
     UnprocessableEntityError,
     APIResponseValidationError,
 )
-from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
+from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
 from ._utils._logs import setup_logging as _setup_logging
 
 __all__ = [
@@ -80,6 +80,7 @@
     "DEFAULT_CONNECTION_LIMITS",
     "DefaultHttpxClient",
     "DefaultAsyncHttpxClient",
+    "DefaultAioHttpClient",
 ]
 
 if not _t.TYPE_CHECKING:
diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 6ef7dd5f..1eca89e0 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -1289,6 +1289,24 @@ def __init__(self, **kwargs: Any) -> None:
         super().__init__(**kwargs)
 
 
+try:
+    import httpx_aiohttp
+except ImportError:
+
+    class _DefaultAioHttpClient(httpx.AsyncClient):
+        def __init__(self, **_kwargs: Any) -> None:
+            raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
+else:
+
+    class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient):  # type: ignore
+        def __init__(self, **kwargs: Any) -> None:
+            kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+            kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+            kwargs.setdefault("follow_redirects", True)
+
+            super().__init__(**kwargs)
+
+
 if TYPE_CHECKING:
     DefaultAsyncHttpxClient = httpx.AsyncClient
     """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
@@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None:
     This is useful because overriding the `http_client` with your own instance of
     `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
     """
+
+    DefaultAioHttpClient = httpx.AsyncClient
+    """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
 else:
     DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
+    DefaultAioHttpClient = _DefaultAioHttpClient
 
 
 class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py
index 2fb71fc5..3a034833 100644
--- a/tests/api_resources/organizations/billing/test_card_details.py
+++ b/tests/api_resources/organizations/billing/test_card_details.py
@@ -61,7 +61,9 @@ def test_path_params_retrieve(self, client: Codex) -> None:
 
 
 class TestAsyncCardDetails:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py
index 2fc1b810..76d9732e 100644
--- a/tests/api_resources/organizations/billing/test_plan_details.py
+++ b/tests/api_resources/organizations/billing/test_plan_details.py
@@ -61,7 +61,9 @@ def test_path_params_retrieve(self, client: Codex) -> None:
 
 
 class TestAsyncPlanDetails:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py
index 3eb05fbc..49d80b0d 100644
--- a/tests/api_resources/organizations/billing/test_setup_intent.py
+++ b/tests/api_resources/organizations/billing/test_setup_intent.py
@@ -61,7 +61,9 @@ def test_path_params_create(self, client: Codex) -> None:
 
 
 class TestAsyncSetupIntent:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index e3bb1d1d..237562b5 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -103,7 +103,9 @@ def test_path_params_usage(self, client: Codex) -> None:
 
 
 class TestAsyncBilling:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index ad4ee5e4..c3bc1785 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -380,7 +380,9 @@ def test_path_params_revoke(self, client: Codex) -> None:
 
 
 class TestAsyncAccessKeys:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py
index 496236f8..87734277 100644
--- a/tests/api_resources/projects/test_clusters.py
+++ b/tests/api_resources/projects/test_clusters.py
@@ -131,7 +131,9 @@ def test_path_params_list_variants(self, client: Codex) -> None:
 
 
 class TestAsyncClusters:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index eb6fd372..1b077d10 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -517,7 +517,9 @@ def test_path_params_unpublish_answer(self, client: Codex) -> None:
 
 
 class TestAsyncEntries:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py
index c16fbc37..1fd4216f 100644
--- a/tests/api_resources/projects/test_evals.py
+++ b/tests/api_resources/projects/test_evals.py
@@ -348,7 +348,9 @@ def test_path_params_delete(self, client: Codex) -> None:
 
 
 class TestAsyncEvals:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index 847af5df..d75dcabe 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -308,7 +308,9 @@ def test_path_params_start_remediation(self, client: Codex) -> None:
 
 
 class TestAsyncQueryLogs:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py
index cfd5ae76..c75a255c 100644
--- a/tests/api_resources/projects/test_remediations.py
+++ b/tests/api_resources/projects/test_remediations.py
@@ -626,7 +626,9 @@ def test_path_params_unpause(self, client: Codex) -> None:
 
 
 class TestAsyncRemediations:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index ed942cd2..92db3a81 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -75,7 +75,9 @@ def test_streaming_response_db(self, client: Codex) -> None:
 
 
 class TestAsyncHealth:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index c665e85c..eecdf3f7 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -149,7 +149,9 @@ def test_path_params_retrieve_permissions(self, client: Codex) -> None:
 
 
 class TestAsyncOrganizations:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 86bd8053..6c95f273 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -723,7 +723,9 @@ def test_path_params_validate(self, client: Codex) -> None:
 
 
 class TestAsyncProjects:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index 32d5a67f..41376a46 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -135,7 +135,9 @@ def test_streaming_response_score(self, client: Codex) -> None:
 
 
 class TestAsyncTlm:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py
index 101d0f66..661ee559 100644
--- a/tests/api_resources/test_users.py
+++ b/tests/api_resources/test_users.py
@@ -71,7 +71,9 @@ def test_streaming_response_activate_account(self, client: Codex) -> None:
 
 
 class TestAsyncUsers:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index c6ed0209..f0a7ccf7 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -75,7 +75,9 @@ def test_streaming_response_refresh(self, client: Codex) -> None:
 
 
 class TestAsyncAPIKey:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index 9f35f7c6..fd377ea0 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -47,7 +47,9 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
 
 class TestAsyncOrganizations:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index 63123275..1c56b0be 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -47,7 +47,9 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
 
 class TestAsyncMyself:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py
index 8332327e..fbf6b667 100644
--- a/tests/api_resources/users/test_verification.py
+++ b/tests/api_resources/users/test_verification.py
@@ -47,7 +47,9 @@ def test_streaming_response_resend(self, client: Codex) -> None:
 
 
 class TestAsyncVerification:
-    parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+    parametrize = pytest.mark.parametrize(
+        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+    )
 
     @pytest.mark.skip()
     @parametrize
diff --git a/tests/conftest.py b/tests/conftest.py
index 05c77729..3472c36d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,10 +6,12 @@
 import logging
 from typing import TYPE_CHECKING, Iterator, AsyncIterator
 
+import httpx
 import pytest
 from pytest_asyncio import is_async_test
 
-from codex import Codex, AsyncCodex
+from codex import Codex, AsyncCodex, DefaultAioHttpClient
+from codex._utils import is_dict
 
 if TYPE_CHECKING:
     from _pytest.fixtures import FixtureRequest  # pyright: ignore[reportPrivateImportUsage]
@@ -27,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
     for async_test in pytest_asyncio_tests:
         async_test.add_marker(session_scope_marker, append=False)
 
+    # We skip tests that use both the aiohttp client and respx_mock as respx_mock
+    # doesn't support custom transports.
+    for item in items:
+        if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames:
+            continue
+
+        if not hasattr(item, "callspec"):
+            continue
+
+        async_client_param = item.callspec.params.get("async_client")
+        if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp":
+            item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock"))
+
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
@@ -43,9 +58,23 @@ def client(request: FixtureRequest) -> Iterator[Codex]:
 
 @pytest.fixture(scope="session")
 async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
-    strict = getattr(request, "param", True)
-    if not isinstance(strict, bool):
-        raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
+    param = getattr(request, "param", True)
+
+    # defaults
+    strict = True
+    http_client: None | httpx.AsyncClient = None
+
+    if isinstance(param, bool):
+        strict = param
+    elif is_dict(param):
+        strict = param.get("strict", True)
+        assert isinstance(strict, bool)
+
+        http_client_type = param.get("http_client", "httpx")
+        if http_client_type == "aiohttp":
+            http_client = DefaultAioHttpClient()
+    else:
+        raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
 
-    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client:
+    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict, http_client=http_client) as client:
         yield client

From 373006432d62627c76b44f15461d23c830aa5509 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sun, 22 Jun 2025 20:09:03 +0000
Subject: [PATCH 174/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index fac14074..7c31fce2 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.20"
+  ".": "0.1.0-alpha.21"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index bad25395..d311e3f1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.20"
+version = "0.1.0-alpha.21"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 44d6131d..3b23c98f 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.20"  # x-release-please-version
+__version__ = "0.1.0-alpha.21"  # x-release-please-version

From 8b5b5dc90bc437ed55ddafbd3b620be52335df98 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Jun 2025 04:11:04 +0000
Subject: [PATCH 175/227] chore(tests): skip some failing tests on the latest
 python versions

---
 tests/test_client.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/test_client.py b/tests/test_client.py
index 9cf7e943..c012f4d5 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -182,6 +182,7 @@ def test_copy_signature(self) -> None:
             copy_param = copy_signature.parameters.get(name)
             assert copy_param is not None, f"copy() signature is missing the {name} param"
 
+    @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
     def test_copy_build_request(self) -> None:
         options = FinalRequestOptions(method="get", url="/foo")
 
@@ -965,6 +966,7 @@ def test_copy_signature(self) -> None:
             copy_param = copy_signature.parameters.get(name)
             assert copy_param is not None, f"copy() signature is missing the {name} param"
 
+    @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
     def test_copy_build_request(self) -> None:
         options = FinalRequestOptions(method="get", url="/foo")
 

From 7277b794b56a208f1f1f7a54357d91dce4d428bb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Jun 2025 17:17:38 +0000
Subject: [PATCH 176/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/evals.py         | 48 ++++++++++++++-----
 src/codex/types/project_create_params.py      | 42 +++++++++++-----
 src/codex/types/project_list_response.py      | 42 +++++++++++-----
 src/codex/types/project_retrieve_response.py  | 42 +++++++++++-----
 src/codex/types/project_return_schema.py      | 42 +++++++++++-----
 src/codex/types/project_update_params.py      | 42 +++++++++++-----
 src/codex/types/project_validate_response.py  | 23 ++++++++-
 .../types/projects/eval_create_params.py      |  7 ++-
 .../types/projects/eval_list_response.py      |  7 ++-
 .../types/projects/eval_update_params.py      | 14 ++++--
 .../query_log_list_by_group_response.py       |  7 ++-
 .../query_log_list_groups_response.py         | 11 ++++-
 .../types/projects/query_log_list_response.py |  7 ++-
 .../projects/query_log_retrieve_response.py   |  7 ++-
 .../query_log_start_remediation_response.py   |  4 +-
 .../projects/remediation_create_response.py   |  4 +-
 .../remediation_edit_answer_response.py       |  4 +-
 .../remediation_edit_draft_answer_response.py |  4 +-
 ...iation_get_resolved_logs_count_response.py |  4 +-
 ...remediation_list_resolved_logs_response.py |  7 ++-
 .../projects/remediation_list_response.py     |  4 +-
 .../projects/remediation_pause_response.py    |  4 +-
 .../projects/remediation_publish_response.py  |  4 +-
 .../projects/remediation_retrieve_response.py |  4 +-
 .../projects/remediation_unpause_response.py  |  4 +-
 tests/api_resources/projects/test_evals.py    |  6 +++
 tests/api_resources/test_projects.py          | 24 ++++++++++
 28 files changed, 321 insertions(+), 99 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 20dc969f..04c13865 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
-openapi_spec_hash: eeb8ebc5600523bdfad046381a929572
+openapi_spec_hash: 80696dc202de8bacc0e43506d7c210b0
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py
index 1a6a6876..1fc95890 100644
--- a/src/codex/resources/projects/evals.py
+++ b/src/codex/resources/projects/evals.py
@@ -59,6 +59,7 @@ def create(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -95,8 +96,10 @@ def create(
           response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
               response.
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -126,6 +129,7 @@ def create(
                     "query_identifier": query_identifier,
                     "response_identifier": response_identifier,
                     "should_escalate": should_escalate,
+                    "should_guardrail": should_guardrail,
                     "threshold": threshold,
                     "threshold_direction": threshold_direction,
                 },
@@ -153,6 +157,7 @@ def update(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -189,8 +194,10 @@ def update(
           response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
               response.
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -216,6 +223,7 @@ def update(
         enabled: bool | NotGiven = NOT_GIVEN,
         priority: Optional[int] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -237,8 +245,10 @@ def update(
           priority: Priority order for evals (lower number = higher priority) to determine primary
               eval issue to surface
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -270,6 +280,7 @@ def update(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -297,6 +308,7 @@ def update(
                     "query_identifier": query_identifier,
                     "response_identifier": response_identifier,
                     "should_escalate": should_escalate,
+                    "should_guardrail": should_guardrail,
                     "threshold": threshold,
                     "threshold_direction": threshold_direction,
                 },
@@ -412,6 +424,7 @@ async def create(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -448,8 +461,10 @@ async def create(
           response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
               response.
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -479,6 +494,7 @@ async def create(
                     "query_identifier": query_identifier,
                     "response_identifier": response_identifier,
                     "should_escalate": should_escalate,
+                    "should_guardrail": should_guardrail,
                     "threshold": threshold,
                     "threshold_direction": threshold_direction,
                 },
@@ -506,6 +522,7 @@ async def update(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -542,8 +559,10 @@ async def update(
           response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM
               response.
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -569,6 +588,7 @@ async def update(
         enabled: bool | NotGiven = NOT_GIVEN,
         priority: Optional[int] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -590,8 +610,10 @@ async def update(
           priority: Priority order for evals (lower number = higher priority) to determine primary
               eval issue to surface
 
-          should_escalate: If true, failing this eval means the response is considered bad and can trigger
-              escalation to Codex/SME
+          should_escalate: If true, failing this eval means the question should be escalated to Codex for
+              an SME to review
+
+          should_guardrail: If true, failing this eval means the response should be guardrailed
 
           threshold: Threshold value that determines if the evaluation fails
 
@@ -623,6 +645,7 @@ async def update(
         query_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         response_identifier: Optional[str] | NotGiven = NOT_GIVEN,
         should_escalate: bool | NotGiven = NOT_GIVEN,
+        should_guardrail: bool | NotGiven = NOT_GIVEN,
         threshold: float | NotGiven = NOT_GIVEN,
         threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -650,6 +673,7 @@ async def update(
                     "query_identifier": query_identifier,
                     "response_identifier": response_identifier,
                     "should_escalate": should_escalate,
+                    "should_guardrail": should_guardrail,
                     "threshold": threshold,
                     "threshold_direction": threshold_direction,
                 },
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 4d9d31f5..a8369782 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -79,10 +79,13 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -115,10 +118,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -147,10 +153,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -179,10 +188,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -211,10 +223,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -243,10 +258,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index a7df3f38..c39bf086 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -69,10 +69,13 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -105,10 +108,13 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -137,10 +143,13 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -169,10 +178,13 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -201,10 +213,13 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -233,10 +248,13 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index 53624096..7d1f8edd 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -68,10 +68,13 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -104,10 +107,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -136,10 +142,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -168,10 +177,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -200,10 +212,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -232,10 +247,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 2ad7433a..170d7994 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -68,10 +68,13 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -104,10 +107,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -136,10 +142,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -168,10 +177,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -200,10 +212,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
@@ -232,10 +247,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index fc6c52d4..73dad672 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -77,10 +77,13 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -113,10 +116,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -145,10 +151,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -177,10 +186,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -209,10 +221,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -241,10 +256,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py
index a88874da..db65f676 100644
--- a/src/codex/types/project_validate_response.py
+++ b/src/codex/types/project_validate_response.py
@@ -8,14 +8,27 @@
 
 
 class EvalScores(BaseModel):
-    failed: bool
-
     score: Optional[float] = None
 
+    triggered: bool
+
+    triggered_escalation: bool
+
+    triggered_guardrail: bool
+
+    failed: Optional[bool] = None
+
     log: Optional[object] = None
 
 
 class ProjectValidateResponse(BaseModel):
+    escalated_to_sme: bool
+    """
+    True if the question should be escalated to Codex for an SME to review, False
+    otherwise. When True, a lookup is performed, which logs this query in the
+    project for SMEs to answer, if it does not already exist.
+    """
+
     eval_scores: Dict[str, EvalScores]
     """
     Evaluation scores for the original response along with a boolean flag, `failed`,
@@ -34,3 +47,9 @@ class ProjectValidateResponse(BaseModel):
     When True, a lookup is performed, which logs this query in the project for SMEs
     to answer, if it does not already exist.
     """
+
+    should_guardrail: bool
+    """
+    True if the response should be guardrailed by the AI system, False if the
+    response is okay to return to the user.
+    """
diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py
index 20dcdd3d..aead432e 100644
--- a/src/codex/types/projects/eval_create_params.py
+++ b/src/codex/types/projects/eval_create_params.py
@@ -55,10 +55,13 @@ class EvalCreateParams(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py
index e7f2b1b3..48859b8d 100644
--- a/src/codex/types/projects/eval_list_response.py
+++ b/src/codex/types/projects/eval_list_response.py
@@ -55,10 +55,13 @@ class EvalListResponseItem(BaseModel):
 
     should_escalate: Optional[bool] = None
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: Optional[bool] = None
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: Optional[float] = None
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py
index b690ec43..545c0b29 100644
--- a/src/codex/types/projects/eval_update_params.py
+++ b/src/codex/types/projects/eval_update_params.py
@@ -59,10 +59,13 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
@@ -90,10 +93,13 @@ class DefaultEvalUpdateSchema(TypedDict, total=False):
 
     should_escalate: bool
     """
-    If true, failing this eval means the response is considered bad and can trigger
-    escalation to Codex/SME
+    If true, failing this eval means the question should be escalated to Codex for
+    an SME to review
     """
 
+    should_guardrail: bool
+    """If true, failing this eval means the response should be guardrailed"""
+
     threshold: float
     """Threshold value that determines if the evaluation fails"""
 
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index ee79b6fa..d11d8276 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -45,7 +45,6 @@ class QueryLogsByGroupQueryLog(BaseModel):
     """
 
     is_bad_response: bool
-    """If an eval with should_escalate=True failed"""
 
     project_id: str
 
@@ -65,6 +64,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    escalated: Optional[bool] = None
+    """If true, the question was escalated to Codex for an SME to review"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -77,6 +79,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrailed: Optional[bool] = None
+    """If true, the response was guardrailed"""
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 979d34d6..fd87c309 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -40,7 +40,8 @@ class QueryLogGroup(BaseModel):
     """
 
     is_bad_response: bool
-    """If an eval with should_escalate=True failed"""
+
+    needs_review: bool
 
     project_id: str
 
@@ -48,7 +49,7 @@ class QueryLogGroup(BaseModel):
 
     remediation_id: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     total_count: int
 
@@ -64,6 +65,9 @@ class QueryLogGroup(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    escalated: Optional[bool] = None
+    """If true, the question was escalated to Codex for an SME to review"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -76,6 +80,9 @@ class QueryLogGroup(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrailed: Optional[bool] = None
+    """If true, the response was guardrailed"""
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index d570ea50..bfd37cdd 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -40,7 +40,6 @@ class QueryLog(BaseModel):
     """
 
     is_bad_response: bool
-    """If an eval with should_escalate=True failed"""
 
     project_id: str
 
@@ -60,6 +59,9 @@ class QueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    escalated: Optional[bool] = None
+    """If true, the question was escalated to Codex for an SME to review"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -72,6 +74,9 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrailed: Optional[bool] = None
+    """If true, the response was guardrailed"""
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index f918c214..3b813eea 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -40,7 +40,6 @@ class QueryLogRetrieveResponse(BaseModel):
     """
 
     is_bad_response: bool
-    """If an eval with should_escalate=True failed"""
 
     project_id: str
 
@@ -60,6 +59,9 @@ class QueryLogRetrieveResponse(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    escalated: Optional[bool] = None
+    """If true, the question was escalated to Codex for an SME to review"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -72,6 +74,9 @@ class QueryLogRetrieveResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrailed: Optional[bool] = None
+    """If true, the response was guardrailed"""
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_start_remediation_response.py b/src/codex/types/projects/query_log_start_remediation_response.py
index 0250fb15..ee7f0c72 100644
--- a/src/codex/types/projects/query_log_start_remediation_response.py
+++ b/src/codex/types/projects/query_log_start_remediation_response.py
@@ -22,11 +22,13 @@ class QueryLogStartRemediationResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_create_response.py b/src/codex/types/projects/remediation_create_response.py
index ad4e6893..9b8a8775 100644
--- a/src/codex/types/projects/remediation_create_response.py
+++ b/src/codex/types/projects/remediation_create_response.py
@@ -22,11 +22,13 @@ class RemediationCreateResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_edit_answer_response.py b/src/codex/types/projects/remediation_edit_answer_response.py
index d8b34323..1d43c082 100644
--- a/src/codex/types/projects/remediation_edit_answer_response.py
+++ b/src/codex/types/projects/remediation_edit_answer_response.py
@@ -22,11 +22,13 @@ class RemediationEditAnswerResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_edit_draft_answer_response.py b/src/codex/types/projects/remediation_edit_draft_answer_response.py
index 828035e8..80f80c07 100644
--- a/src/codex/types/projects/remediation_edit_draft_answer_response.py
+++ b/src/codex/types/projects/remediation_edit_draft_answer_response.py
@@ -22,11 +22,13 @@ class RemediationEditDraftAnswerResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py
index 79997b09..9222eb9a 100644
--- a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py
+++ b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py
@@ -22,13 +22,15 @@ class RemediationGetResolvedLogsCountResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
     resolved_logs_count: int
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 6181028d..4f9682b6 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -40,7 +40,6 @@ class QueryLog(BaseModel):
     """
 
     is_bad_response: bool
-    """If an eval with should_escalate=True failed"""
 
     project_id: str
 
@@ -60,6 +59,9 @@ class QueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    escalated: Optional[bool] = None
+    """If true, the question was escalated to Codex for an SME to review"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -72,6 +74,9 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrailed: Optional[bool] = None
+    """If true, the response was guardrailed"""
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py
index 3e737970..9d5cda86 100644
--- a/src/codex/types/projects/remediation_list_response.py
+++ b/src/codex/types/projects/remediation_list_response.py
@@ -22,13 +22,15 @@ class Remediation(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
     resolved_logs_count: int
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_pause_response.py b/src/codex/types/projects/remediation_pause_response.py
index ae453581..97e1ac58 100644
--- a/src/codex/types/projects/remediation_pause_response.py
+++ b/src/codex/types/projects/remediation_pause_response.py
@@ -22,11 +22,13 @@ class RemediationPauseResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_publish_response.py b/src/codex/types/projects/remediation_publish_response.py
index 5eb2c622..b43c8b98 100644
--- a/src/codex/types/projects/remediation_publish_response.py
+++ b/src/codex/types/projects/remediation_publish_response.py
@@ -22,11 +22,13 @@ class RemediationPublishResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_retrieve_response.py b/src/codex/types/projects/remediation_retrieve_response.py
index 6fbd60d2..69a327e0 100644
--- a/src/codex/types/projects/remediation_retrieve_response.py
+++ b/src/codex/types/projects/remediation_retrieve_response.py
@@ -22,11 +22,13 @@ class RemediationRetrieveResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/src/codex/types/projects/remediation_unpause_response.py b/src/codex/types/projects/remediation_unpause_response.py
index 789484cd..c3ce44fd 100644
--- a/src/codex/types/projects/remediation_unpause_response.py
+++ b/src/codex/types/projects/remediation_unpause_response.py
@@ -22,11 +22,13 @@ class RemediationUnpauseResponse(BaseModel):
 
     last_edited_by: Optional[str] = None
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"]
+    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     answer: Optional[str] = None
 
diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py
index 1fd4216f..22b83803 100644
--- a/tests/api_resources/projects/test_evals.py
+++ b/tests/api_resources/projects/test_evals.py
@@ -44,6 +44,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
             query_identifier="query_identifier",
             response_identifier="response_identifier",
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
@@ -120,6 +121,7 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None:
             query_identifier="query_identifier",
             response_identifier="response_identifier",
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
@@ -200,6 +202,7 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None:
             enabled=True,
             priority=0,
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
@@ -378,6 +381,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
             query_identifier="query_identifier",
             response_identifier="response_identifier",
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
@@ -454,6 +458,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn
             query_identifier="query_identifier",
             response_identifier="response_identifier",
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
@@ -534,6 +539,7 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn
             enabled=True,
             priority=0,
             should_escalate=True,
+            should_guardrail=True,
             threshold=0,
             threshold_direction="above",
         )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 6c95f273..e7d7eb1f 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -56,6 +56,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
                                 "should_escalate": True,
+                                "should_guardrail": True,
                                 "threshold": 0,
                                 "threshold_direction": "above",
                             }
@@ -68,6 +69,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -77,6 +79,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -86,6 +89,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -95,6 +99,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -104,6 +109,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -226,6 +232,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
                                 "should_escalate": True,
+                                "should_guardrail": True,
                                 "threshold": 0,
                                 "threshold_direction": "above",
                             }
@@ -238,6 +245,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -247,6 +255,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -256,6 +265,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -265,6 +275,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -274,6 +285,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -757,6 +769,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
                                 "should_escalate": True,
+                                "should_guardrail": True,
                                 "threshold": 0,
                                 "threshold_direction": "above",
                             }
@@ -769,6 +782,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -778,6 +792,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -787,6 +802,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -796,6 +812,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -805,6 +822,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -927,6 +945,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                                 "query_identifier": "query_identifier",
                                 "response_identifier": "response_identifier",
                                 "should_escalate": True,
+                                "should_guardrail": True,
                                 "threshold": 0,
                                 "threshold_direction": "above",
                             }
@@ -939,6 +958,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -948,6 +968,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -957,6 +978,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -966,6 +988,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },
@@ -975,6 +998,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                             "enabled": True,
                             "priority": 0,
                             "should_escalate": True,
+                            "should_guardrail": True,
                             "threshold": 0,
                             "threshold_direction": "above",
                         },

From fca40bf99465bfae4f61c65ec27f3e45f2dd96dd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Jun 2025 21:54:08 +0000
Subject: [PATCH 177/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 7c31fce2..aa848759 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.21"
+  ".": "0.1.0-alpha.22"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index d311e3f1..a2a3d7c2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.21"
+version = "0.1.0-alpha.22"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 3b23c98f..a88a1c39 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.21"  # x-release-please-version
+__version__ = "0.1.0-alpha.22"  # x-release-please-version

From f2fc581f478b5de321d4ae608705e432df7baba4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 24 Jun 2025 23:17:37 +0000
Subject: [PATCH 178/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 .../query_log_list_by_group_response.py       | 33 +++++++++++++++-
 .../query_log_list_groups_response.py         | 39 ++++++++++++++++++-
 .../types/projects/query_log_list_response.py | 39 ++++++++++++++++++-
 .../projects/query_log_retrieve_response.py   | 38 +++++++++++++++++-
 ...remediation_list_resolved_logs_response.py | 39 ++++++++++++++++++-
 6 files changed, 180 insertions(+), 10 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 04c13865..9e05e796 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
-openapi_spec_hash: 80696dc202de8bacc0e43506d7c210b0
+openapi_spec_hash: 8bc7a64933cd540d1d2499d055430832
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index d11d8276..9d1e0e69 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -10,10 +10,31 @@
     "QueryLogListByGroupResponse",
     "QueryLogsByGroup",
     "QueryLogsByGroupQueryLog",
+    "QueryLogsByGroupQueryLogFormattedEscalationEvalScores",
+    "QueryLogsByGroupQueryLogFormattedEvalScores",
+    "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
 ]
 
 
+class QueryLogsByGroupQueryLogFormattedEscalationEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogsByGroupQueryLogFormattedEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogsByGroupQueryLogFormattedGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class QueryLogsByGroupQueryLogContext(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -36,7 +57,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
 
     created_at: datetime
 
-    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedEscalationEvalScores]] = None
+
+    formatted_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -44,6 +67,8 @@ class QueryLogsByGroupQueryLog(BaseModel):
     eval_scores is None.
     """
 
+    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -67,6 +92,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
+    escalation_evals: Optional[List[str]] = None
+    """Evals that should trigger escalation to SME"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -79,6 +107,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrail_evals: Optional[List[str]] = None
+    """Evals that should trigger guardrail"""
+
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index fd87c309..7b77cc05 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -6,7 +6,32 @@
 
 from ..._models import BaseModel
 
-__all__ = ["QueryLogListGroupsResponse", "QueryLogGroup", "QueryLogGroupContext"]
+__all__ = [
+    "QueryLogListGroupsResponse",
+    "QueryLogGroup",
+    "QueryLogGroupFormattedEscalationEvalScores",
+    "QueryLogGroupFormattedEvalScores",
+    "QueryLogGroupFormattedGuardrailEvalScores",
+    "QueryLogGroupContext",
+]
+
+
+class QueryLogGroupFormattedEscalationEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogGroupFormattedEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogGroupFormattedGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
 
 
 class QueryLogGroupContext(BaseModel):
@@ -31,7 +56,9 @@ class QueryLogGroup(BaseModel):
 
     created_at: datetime
 
-    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEscalationEvalScores]] = None
+
+    formatted_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -39,6 +66,8 @@ class QueryLogGroup(BaseModel):
     eval_scores is None.
     """
 
+    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     needs_review: bool
@@ -68,6 +97,9 @@ class QueryLogGroup(BaseModel):
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
+    escalation_evals: Optional[List[str]] = None
+    """Evals that should trigger escalation to SME"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -80,6 +112,9 @@ class QueryLogGroup(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrail_evals: Optional[List[str]] = None
+    """Evals that should trigger guardrail"""
+
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index bfd37cdd..fa04904d 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -6,7 +6,32 @@
 
 from ..._models import BaseModel
 
-__all__ = ["QueryLogListResponse", "QueryLog", "QueryLogContext"]
+__all__ = [
+    "QueryLogListResponse",
+    "QueryLog",
+    "QueryLogFormattedEscalationEvalScores",
+    "QueryLogFormattedEvalScores",
+    "QueryLogFormattedGuardrailEvalScores",
+    "QueryLogContext",
+]
+
+
+class QueryLogFormattedEscalationEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogFormattedEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogFormattedGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
 
 
 class QueryLogContext(BaseModel):
@@ -31,7 +56,9 @@ class QueryLog(BaseModel):
 
     created_at: datetime
 
-    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None
+
+    formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -39,6 +66,8 @@ class QueryLog(BaseModel):
     eval_scores is None.
     """
 
+    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -62,6 +91,9 @@ class QueryLog(BaseModel):
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
+    escalation_evals: Optional[List[str]] = None
+    """Evals that should trigger escalation to SME"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -74,6 +106,9 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrail_evals: Optional[List[str]] = None
+    """Evals that should trigger guardrail"""
+
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 3b813eea..8bb61283 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -6,7 +6,31 @@
 
 from ..._models import BaseModel
 
-__all__ = ["QueryLogRetrieveResponse", "Context"]
+__all__ = [
+    "QueryLogRetrieveResponse",
+    "FormattedEscalationEvalScores",
+    "FormattedEvalScores",
+    "FormattedGuardrailEvalScores",
+    "Context",
+]
+
+
+class FormattedEscalationEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class FormattedEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class FormattedGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
 
 
 class Context(BaseModel):
@@ -31,7 +55,9 @@ class QueryLogRetrieveResponse(BaseModel):
 
     created_at: datetime
 
-    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None
+
+    formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -39,6 +65,8 @@ class QueryLogRetrieveResponse(BaseModel):
     eval_scores is None.
     """
 
+    formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -62,6 +90,9 @@ class QueryLogRetrieveResponse(BaseModel):
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
+    escalation_evals: Optional[List[str]] = None
+    """Evals that should trigger escalation to SME"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -74,6 +105,9 @@ class QueryLogRetrieveResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrail_evals: Optional[List[str]] = None
+    """Evals that should trigger guardrail"""
+
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 4f9682b6..b2315aa4 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -6,7 +6,32 @@
 
 from ..._models import BaseModel
 
-__all__ = ["RemediationListResolvedLogsResponse", "QueryLog", "QueryLogContext"]
+__all__ = [
+    "RemediationListResolvedLogsResponse",
+    "QueryLog",
+    "QueryLogFormattedEscalationEvalScores",
+    "QueryLogFormattedEvalScores",
+    "QueryLogFormattedGuardrailEvalScores",
+    "QueryLogContext",
+]
+
+
+class QueryLogFormattedEscalationEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogFormattedEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
+class QueryLogFormattedGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
 
 
 class QueryLogContext(BaseModel):
@@ -31,7 +56,9 @@ class QueryLog(BaseModel):
 
     created_at: datetime
 
-    formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None
+
+    formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -39,6 +66,8 @@ class QueryLog(BaseModel):
     eval_scores is None.
     """
 
+    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -62,6 +91,9 @@ class QueryLog(BaseModel):
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
+    escalation_evals: Optional[List[str]] = None
+    """Evals that should trigger escalation to SME"""
+
     eval_issue_labels: Optional[List[str]] = None
     """Labels derived from evaluation scores"""
 
@@ -74,6 +106,9 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    guardrail_evals: Optional[List[str]] = None
+    """Evals that should trigger guardrail"""
+
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 

From 3032709fc4eee6a1c92228ec502a52053a902b94 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 26 Jun 2025 00:17:35 +0000
Subject: [PATCH 179/227] feat(api): api update

---
 .stats.yml                               |  2 +-
 src/codex/resources/projects/projects.py | 20 ++++++++++----------
 src/codex/types/project_update_params.py |  8 ++++----
 tests/api_resources/test_projects.py     | 24 ++++--------------------
 4 files changed, 19 insertions(+), 35 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 9e05e796..31393e04 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
-openapi_spec_hash: 8bc7a64933cd540d1d2499d055430832
+openapi_spec_hash: 5d686da2afda75185dc9e4190a42b75c
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 22b5caff..fc5a43a4 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -212,10 +212,10 @@ def update(
         self,
         project_id: str,
         *,
-        config: project_update_params.Config,
-        name: str,
-        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
+        auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -241,10 +241,10 @@ def update(
             f"/api/projects/{project_id}",
             body=maybe_transform(
                 {
-                    "config": config,
-                    "name": name,
                     "auto_clustering_enabled": auto_clustering_enabled,
+                    "config": config,
                     "description": description,
+                    "name": name,
                 },
                 project_update_params.ProjectUpdateParams,
             ),
@@ -820,10 +820,10 @@ async def update(
         self,
         project_id: str,
         *,
-        config: project_update_params.Config,
-        name: str,
-        auto_clustering_enabled: bool | NotGiven = NOT_GIVEN,
+        auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN,
+        config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN,
         description: Optional[str] | NotGiven = NOT_GIVEN,
+        name: Optional[str] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -849,10 +849,10 @@ async def update(
             f"/api/projects/{project_id}",
             body=await async_maybe_transform(
                 {
-                    "config": config,
-                    "name": name,
                     "auto_clustering_enabled": auto_clustering_enabled,
+                    "config": config,
                     "description": description,
+                    "name": name,
                 },
                 project_update_params.ProjectUpdateParams,
             ),
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 73dad672..3e244411 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -21,14 +21,14 @@
 
 
 class ProjectUpdateParams(TypedDict, total=False):
-    config: Required[Config]
+    auto_clustering_enabled: Optional[bool]
 
-    name: Required[str]
-
-    auto_clustering_enabled: bool
+    config: Optional[Config]
 
     description: Optional[str]
 
+    name: Optional[str]
+
 
 class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False):
     criteria: Required[str]
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index e7d7eb1f..0764d9a1 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -206,8 +206,6 @@ def test_path_params_retrieve(self, client: Codex) -> None:
     def test_method_update(self, client: Codex) -> None:
         project = client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -216,6 +214,7 @@ def test_method_update(self, client: Codex) -> None:
     def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            auto_clustering_enabled=True,
             config={
                 "clustering_use_llm_matching": True,
                 "eval_config": {
@@ -298,9 +297,8 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                 "query_use_llm_matching": True,
                 "upper_llm_match_distance_threshold": 0,
             },
-            name="name",
-            auto_clustering_enabled=True,
             description="description",
+            name="name",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -309,8 +307,6 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.with_raw_response.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         )
 
         assert response.is_closed is True
@@ -323,8 +319,6 @@ def test_raw_response_update(self, client: Codex) -> None:
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.with_streaming_response.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -340,8 +334,6 @@ def test_path_params_update(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             client.projects.with_raw_response.update(
                 project_id="",
-                config={},
-                name="name",
             )
 
     @pytest.mark.skip()
@@ -919,8 +911,6 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -929,6 +919,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            auto_clustering_enabled=True,
             config={
                 "clustering_use_llm_matching": True,
                 "eval_config": {
@@ -1011,9 +1002,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                 "query_use_llm_matching": True,
                 "upper_llm_match_distance_threshold": 0,
             },
-            name="name",
-            auto_clustering_enabled=True,
             description="description",
+            name="name",
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
@@ -1022,8 +1012,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         )
 
         assert response.is_closed is True
@@ -1036,8 +1024,6 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            config={},
-            name="name",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1053,8 +1039,6 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             await async_client.projects.with_raw_response.update(
                 project_id="",
-                config={},
-                name="name",
             )
 
     @pytest.mark.skip()

From c205ac5b02a931b80cbd609f3923fc6d128f5f86 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 26 Jun 2025 21:17:43 +0000
Subject: [PATCH 180/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 31393e04..3ff2f634 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
-openapi_spec_hash: 5d686da2afda75185dc9e4190a42b75c
+openapi_spec_hash: 94969e7925542f0cc845f6e3d299ed3c
 config_hash: 14b2643a0ec60cf326dfed00939644ff

From 026828e8b41d66d9b65e9fa5b061c9b36ac15b1b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 27 Jun 2025 02:29:21 +0000
Subject: [PATCH 181/227] =?UTF-8?q?fix(ci):=20release-doctor=20=E2=80=94?=
 =?UTF-8?q?=20report=20correct=20token=20name?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bin/check-release-environment | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bin/check-release-environment b/bin/check-release-environment
index a1446a75..b845b0f4 100644
--- a/bin/check-release-environment
+++ b/bin/check-release-environment
@@ -3,7 +3,7 @@
 errors=()
 
 if [ -z "${PYPI_TOKEN}" ]; then
-  errors+=("The CODEX_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
+  errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
 fi
 
 lenErrors=${#errors[@]}

From 3c9c4aedc9457891385a7321451762aa9111df48 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 28 Jun 2025 08:28:28 +0000
Subject: [PATCH 182/227] chore(ci): only run for pushes and fork pull requests

---
 .github/workflows/ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2f7778ae..12f686e5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,7 @@ jobs:
     timeout-minutes: 10
     name: lint
     runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
     steps:
       - uses: actions/checkout@v4
 
@@ -42,6 +43,7 @@ jobs:
       contents: read
       id-token: write
     runs-on: depot-ubuntu-24.04
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
     steps:
       - uses: actions/checkout@v4
 

From 3ae3c670f5f9c5e39b2746c5e51de59de8a224c8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 30 Jun 2025 02:25:48 +0000
Subject: [PATCH 183/227] fix(ci): correct conditional

---
 .github/workflows/ci.yml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12f686e5..bbb722b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,14 +36,13 @@ jobs:
         run: ./scripts/lint
 
   upload:
-    if: github.repository == 'stainless-sdks/codex-python'
+    if: github.repository == 'stainless-sdks/codex-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
     timeout-minutes: 10
     name: upload
     permissions:
       contents: read
       id-token: write
     runs-on: depot-ubuntu-24.04
-    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
     steps:
       - uses: actions/checkout@v4
 

From 6a38d107b30cbe5ae08146ea04c447fc204f962a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 30 Jun 2025 19:17:38 +0000
Subject: [PATCH 184/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/query_logs.py    | 88 +++++++++++++++++++
 .../query_log_list_by_group_params.py         | 12 +++
 .../projects/query_log_list_groups_params.py  | 12 +++
 .../types/projects/query_log_list_params.py   |  9 ++
 .../api_resources/projects/test_query_logs.py | 22 +++++
 6 files changed, 144 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 3ff2f634..c509062f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 65
-openapi_spec_hash: 94969e7925542f0cc845f6e3d299ed3c
+openapi_spec_hash: f63d4542b4bd1530ced013eb686cab99
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 32ec7390..e97243ec 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -92,9 +92,12 @@ def list(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -118,6 +121,12 @@ def list(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           was_cache_hit: Filter by cache hit status
@@ -144,9 +153,12 @@ def list(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
                         "was_cache_hit": was_cache_hit,
@@ -164,9 +176,13 @@ def list_by_group(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
+        needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -191,6 +207,14 @@ def list_by_group(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          needs_review: Filter logs that need review
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           remediation_ids: List of groups to list child logs for
@@ -219,9 +243,13 @@ def list_by_group(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
+                        "needs_review": needs_review,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "remediation_ids": remediation_ids,
                         "sort": sort,
@@ -240,9 +268,13 @@ def list_groups(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
+        needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -267,6 +299,14 @@ def list_groups(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          needs_review: Filter log groups that need review
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           was_cache_hit: Filter by cache hit status
@@ -293,9 +333,13 @@ def list_groups(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
+                        "needs_review": needs_review,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
                         "was_cache_hit": was_cache_hit,
@@ -406,9 +450,12 @@ async def list(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -432,6 +479,12 @@ async def list(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           was_cache_hit: Filter by cache hit status
@@ -458,9 +511,12 @@ async def list(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
                         "was_cache_hit": was_cache_hit,
@@ -478,9 +534,13 @@ async def list_by_group(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
+        needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -505,6 +565,14 @@ async def list_by_group(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          needs_review: Filter logs that need review
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           remediation_ids: List of groups to list child logs for
@@ -533,9 +601,13 @@ async def list_by_group(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
+                        "needs_review": needs_review,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "remediation_ids": remediation_ids,
                         "sort": sort,
@@ -554,9 +626,13 @@ async def list_groups(
         created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
+        failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
+        guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
+        needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+        passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
         ]
@@ -581,6 +657,14 @@ async def list_groups(
 
           custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}
 
+          failed_evals: Filter by evals that failed
+
+          guardrailed: Filter by guardrailed status
+
+          needs_review: Filter log groups that need review
+
+          passed_evals: Filter by evals that passed
+
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
           was_cache_hit: Filter by cache hit status
@@ -607,9 +691,13 @@ async def list_groups(
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
                         "custom_metadata": custom_metadata,
+                        "failed_evals": failed_evals,
+                        "guardrailed": guardrailed,
                         "limit": limit,
+                        "needs_review": needs_review,
                         "offset": offset,
                         "order": order,
+                        "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
                         "was_cache_hit": was_cache_hit,
diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py
index 66166a19..b44970a1 100644
--- a/src/codex/types/projects/query_log_list_by_group_params.py
+++ b/src/codex/types/projects/query_log_list_by_group_params.py
@@ -21,12 +21,24 @@ class QueryLogListByGroupParams(TypedDict, total=False):
     custom_metadata: Optional[str]
     """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
 
+    failed_evals: Optional[List[str]]
+    """Filter by evals that failed"""
+
+    guardrailed: Optional[bool]
+    """Filter by guardrailed status"""
+
     limit: int
 
+    needs_review: Optional[bool]
+    """Filter logs that need review"""
+
     offset: int
 
     order: Literal["asc", "desc"]
 
+    passed_evals: Optional[List[str]]
+    """Filter by evals that passed"""
+
     primary_eval_issue: Optional[
         List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
     ]
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
index 558ac0b2..94d549f5 100644
--- a/src/codex/types/projects/query_log_list_groups_params.py
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -21,12 +21,24 @@ class QueryLogListGroupsParams(TypedDict, total=False):
     custom_metadata: Optional[str]
     """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
 
+    failed_evals: Optional[List[str]]
+    """Filter by evals that failed"""
+
+    guardrailed: Optional[bool]
+    """Filter by guardrailed status"""
+
     limit: int
 
+    needs_review: Optional[bool]
+    """Filter log groups that need review"""
+
     offset: int
 
     order: Literal["asc", "desc"]
 
+    passed_evals: Optional[List[str]]
+    """Filter by evals that passed"""
+
     primary_eval_issue: Optional[
         List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
     ]
diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py
index 9cf3211f..0f72b241 100644
--- a/src/codex/types/projects/query_log_list_params.py
+++ b/src/codex/types/projects/query_log_list_params.py
@@ -21,12 +21,21 @@ class QueryLogListParams(TypedDict, total=False):
     custom_metadata: Optional[str]
     """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}"""
 
+    failed_evals: Optional[List[str]]
+    """Filter by evals that failed"""
+
+    guardrailed: Optional[bool]
+    """Filter by guardrailed status"""
+
     limit: int
 
     offset: int
 
     order: Literal["asc", "desc"]
 
+    passed_evals: Optional[List[str]]
+    """Filter by evals that passed"""
+
     primary_eval_issue: Optional[
         List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
     ]
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index d75dcabe..68d78ced 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -92,9 +92,12 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
             was_cache_hit=True,
@@ -151,9 +154,13 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
+            needs_review=True,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
             sort="created_at",
@@ -211,9 +218,13 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None:
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
+            needs_review=True,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
             was_cache_hit=True,
@@ -380,9 +391,12 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
             was_cache_hit=True,
@@ -439,9 +453,13 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
+            needs_review=True,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
             sort="created_at",
@@ -499,9 +517,13 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex
             created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"),
             created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"),
             custom_metadata="custom_metadata",
+            failed_evals=["string"],
+            guardrailed=True,
             limit=1,
+            needs_review=True,
             offset=0,
             order="asc",
+            passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
             was_cache_hit=True,

From bed966ef781b12126f87bab442fc546087ae72a0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 1 Jul 2025 21:17:29 +0000
Subject: [PATCH 185/227] feat(api): api update

---
 .stats.yml                                    |    4 +-
 README.md                                     |   69 --
 api.md                                        |   32 -
 src/codex/resources/projects/__init__.py      |   28 -
 src/codex/resources/projects/clusters.py      |  332 ------
 src/codex/resources/projects/entries.py       |  948 ---------------
 src/codex/resources/projects/projects.py      |   64 --
 src/codex/types/projects/__init__.py          |   10 -
 .../types/projects/cluster_list_params.py     |   34 -
 .../types/projects/cluster_list_response.py   |  214 ----
 .../cluster_list_variants_response.py         |   14 -
 src/codex/types/projects/entry.py             |  210 ----
 .../types/projects/entry_create_params.py     |   28 -
 .../types/projects/entry_notify_sme_params.py |   30 -
 .../projects/entry_notify_sme_response.py     |   13 -
 .../types/projects/entry_query_params.py      |  300 -----
 .../types/projects/entry_query_response.py    |  194 ----
 .../types/projects/entry_update_params.py     |   18 -
 .../query_log_list_by_group_response.py       |    4 +
 .../query_log_list_groups_response.py         |    2 +-
 .../projects/query_log_retrieve_response.py   |    4 +
 tests/api_resources/projects/test_clusters.py |  247 ----
 tests/api_resources/projects/test_entries.py  | 1014 -----------------
 23 files changed, 11 insertions(+), 3802 deletions(-)
 delete mode 100644 src/codex/resources/projects/clusters.py
 delete mode 100644 src/codex/resources/projects/entries.py
 delete mode 100644 src/codex/types/projects/cluster_list_params.py
 delete mode 100644 src/codex/types/projects/cluster_list_response.py
 delete mode 100644 src/codex/types/projects/cluster_list_variants_response.py
 delete mode 100644 src/codex/types/projects/entry.py
 delete mode 100644 src/codex/types/projects/entry_create_params.py
 delete mode 100644 src/codex/types/projects/entry_notify_sme_params.py
 delete mode 100644 src/codex/types/projects/entry_notify_sme_response.py
 delete mode 100644 src/codex/types/projects/entry_query_params.py
 delete mode 100644 src/codex/types/projects/entry_query_response.py
 delete mode 100644 src/codex/types/projects/entry_update_params.py
 delete mode 100644 tests/api_resources/projects/test_clusters.py
 delete mode 100644 tests/api_resources/projects/test_entries.py

diff --git a/.stats.yml b/.stats.yml
index c509062f..cc72a8a8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 65
-openapi_spec_hash: f63d4542b4bd1530ced013eb686cab99
+configured_endpoints: 55
+openapi_spec_hash: b2b026661b19d060e5eac490807fe445
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/README.md b/README.md
index 74f841bf..756f6d69 100644
--- a/README.md
+++ b/README.md
@@ -113,75 +113,6 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
 
 Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
 
-## Pagination
-
-List methods in the Codex API are paginated.
-
-This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
-
-```python
-from codex import Codex
-
-client = Codex()
-
-all_clusters = []
-# Automatically fetches more pages as needed.
-for cluster in client.projects.clusters.list(
-    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-):
-    # Do something with cluster here
-    all_clusters.append(cluster)
-print(all_clusters)
-```
-
-Or, asynchronously:
-
-```python
-import asyncio
-from codex import AsyncCodex
-
-client = AsyncCodex()
-
-
-async def main() -> None:
-    all_clusters = []
-    # Iterate through items across all pages, issuing requests as needed.
-    async for cluster in client.projects.clusters.list(
-        project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-    ):
-        all_clusters.append(cluster)
-    print(all_clusters)
-
-
-asyncio.run(main())
-```
-
-Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
-
-```python
-first_page = await client.projects.clusters.list(
-    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-)
-if first_page.has_next_page():
-    print(f"will fetch next page using these details: {first_page.next_page_info()}")
-    next_page = await first_page.get_next_page()
-    print(f"number of items we just fetched: {len(next_page.clusters)}")
-
-# Remove `await` for non-async usage.
-```
-
-Or just work directly with the returned data:
-
-```python
-first_page = await client.projects.clusters.list(
-    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-)
-for cluster in first_page.clusters:
-    print(cluster.id)
-
-# Remove `await` for non-async usage.
-```
-
 ## Nested params
 
 Nested parameters are dictionaries, typed using `TypedDict`, for example:
diff --git a/api.md b/api.md
index 13e82147..ba4dfd63 100644
--- a/api.md
+++ b/api.md
@@ -180,38 +180,6 @@ Methods:
 - <code title="get /api/projects/id_from_access_key">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">retrieve_project_id</a>() -> <a href="./src/codex/types/projects/access_key_retrieve_project_id_response.py">AccessKeyRetrieveProjectIDResponse</a></code>
 - <code title="post /api/projects/{project_id}/access_keys/{access_key_id}/revoke">client.projects.access_keys.<a href="./src/codex/resources/projects/access_keys.py">revoke</a>(access_key_id, \*, project_id) -> None</code>
 
-## Entries
-
-Types:
-
-```python
-from codex.types.projects import Entry, EntryNotifySmeResponse, EntryQueryResponse
-```
-
-Methods:
-
-- <code title="post /api/projects/{project_id}/entries/">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/entry_create_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="get /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">retrieve</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="put /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">update</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_update_params.py">params</a>) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="delete /api/projects/{project_id}/entries/{entry_id}">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">delete</a>(entry_id, \*, project_id) -> None</code>
-- <code title="post /api/projects/{project_id}/entries/{entry_id}/notifications">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">notify_sme</a>(entry_id, \*, project_id, \*\*<a href="src/codex/types/projects/entry_notify_sme_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_notify_sme_response.py">EntryNotifySmeResponse</a></code>
-- <code title="put /api/projects/{project_id}/entries/{entry_id}/publish_draft_answer">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">publish_draft_answer</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-- <code title="post /api/projects/{project_id}/entries/query">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">query</a>(project_id, \*\*<a href="src/codex/types/projects/entry_query_params.py">params</a>) -> <a href="./src/codex/types/projects/entry_query_response.py">EntryQueryResponse</a></code>
-- <code title="put /api/projects/{project_id}/entries/{entry_id}/unpublish_answer">client.projects.entries.<a href="./src/codex/resources/projects/entries.py">unpublish_answer</a>(entry_id, \*, project_id) -> <a href="./src/codex/types/projects/entry.py">Entry</a></code>
-
-## Clusters
-
-Types:
-
-```python
-from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse
-```
-
-Methods:
-
-- <code title="get /api/projects/{project_id}/entries/clusters">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/cluster_list_params.py">params</a>) -> <a href="./src/codex/types/projects/cluster_list_response.py">SyncOffsetPageClusters[ClusterListResponse]</a></code>
-- <code title="get /api/projects/{project_id}/entries/clusters/{representative_entry_id}">client.projects.clusters.<a href="./src/codex/resources/projects/clusters.py">list_variants</a>(representative_entry_id, \*, project_id) -> <a href="./src/codex/types/projects/cluster_list_variants_response.py">ClusterListVariantsResponse</a></code>
-
 ## Evals
 
 Types:
diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py
index 178855ac..c1627d29 100644
--- a/src/codex/resources/projects/__init__.py
+++ b/src/codex/resources/projects/__init__.py
@@ -8,22 +8,6 @@
     EvalsResourceWithStreamingResponse,
     AsyncEvalsResourceWithStreamingResponse,
 )
-from .entries import (
-    EntriesResource,
-    AsyncEntriesResource,
-    EntriesResourceWithRawResponse,
-    AsyncEntriesResourceWithRawResponse,
-    EntriesResourceWithStreamingResponse,
-    AsyncEntriesResourceWithStreamingResponse,
-)
-from .clusters import (
-    ClustersResource,
-    AsyncClustersResource,
-    ClustersResourceWithRawResponse,
-    AsyncClustersResourceWithRawResponse,
-    ClustersResourceWithStreamingResponse,
-    AsyncClustersResourceWithStreamingResponse,
-)
 from .projects import (
     ProjectsResource,
     AsyncProjectsResource,
@@ -64,18 +48,6 @@
     "AsyncAccessKeysResourceWithRawResponse",
     "AccessKeysResourceWithStreamingResponse",
     "AsyncAccessKeysResourceWithStreamingResponse",
-    "EntriesResource",
-    "AsyncEntriesResource",
-    "EntriesResourceWithRawResponse",
-    "AsyncEntriesResourceWithRawResponse",
-    "EntriesResourceWithStreamingResponse",
-    "AsyncEntriesResourceWithStreamingResponse",
-    "ClustersResource",
-    "AsyncClustersResource",
-    "ClustersResourceWithRawResponse",
-    "AsyncClustersResourceWithRawResponse",
-    "ClustersResourceWithStreamingResponse",
-    "AsyncClustersResourceWithStreamingResponse",
     "EvalsResource",
     "AsyncEvalsResource",
     "EvalsResourceWithRawResponse",
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
deleted file mode 100644
index 97124642..00000000
--- a/src/codex/resources/projects/clusters.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import List, Optional
-from typing_extensions import Literal
-
-import httpx
-
-from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._utils import maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
-    to_raw_response_wrapper,
-    to_streamed_response_wrapper,
-    async_to_raw_response_wrapper,
-    async_to_streamed_response_wrapper,
-)
-from ...pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters
-from ..._base_client import AsyncPaginator, make_request_options
-from ...types.projects import cluster_list_params
-from ...types.projects.cluster_list_response import ClusterListResponse
-from ...types.projects.cluster_list_variants_response import ClusterListVariantsResponse
-
-__all__ = ["ClustersResource", "AsyncClustersResource"]
-
-
-class ClustersResource(SyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> ClustersResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return ClustersResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> ClustersResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return ClustersResourceWithStreamingResponse(self)
-
-    def list(
-        self,
-        project_id: str,
-        *,
-        eval_issue_types: List[
-            Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]
-        ]
-        | NotGiven = NOT_GIVEN,
-        instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[
-            Literal[
-                "created_at",
-                "answered_at",
-                "cluster_frequency_count",
-                "custom_rank",
-                "eval_score",
-                "html_format_score",
-                "content_structure_score",
-            ]
-        ]
-        | NotGiven = NOT_GIVEN,
-        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> SyncOffsetPageClusters[ClusterListResponse]:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get_api_list(
-            f"/api/projects/{project_id}/entries/clusters",
-            page=SyncOffsetPageClusters[ClusterListResponse],
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform(
-                    {
-                        "eval_issue_types": eval_issue_types,
-                        "instruction_adherence_failure": instruction_adherence_failure,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "states": states,
-                    },
-                    cluster_list_params.ClusterListParams,
-                ),
-            ),
-            model=ClusterListResponse,
-        )
-
-    def list_variants(
-        self,
-        representative_entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ClusterListVariantsResponse:
-        """
-        Get Cluster Variants Route
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not representative_entry_id:
-            raise ValueError(
-                f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}"
-            )
-        return self._get(
-            f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=ClusterListVariantsResponse,
-        )
-
-
-class AsyncClustersResource(AsyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> AsyncClustersResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return AsyncClustersResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> AsyncClustersResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return AsyncClustersResourceWithStreamingResponse(self)
-
-    def list(
-        self,
-        project_id: str,
-        *,
-        eval_issue_types: List[
-            Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]
-        ]
-        | NotGiven = NOT_GIVEN,
-        instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN,
-        limit: int | NotGiven = NOT_GIVEN,
-        offset: int | NotGiven = NOT_GIVEN,
-        order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
-        sort: Optional[
-            Literal[
-                "created_at",
-                "answered_at",
-                "cluster_frequency_count",
-                "custom_rank",
-                "eval_score",
-                "html_format_score",
-                "content_structure_score",
-            ]
-        ]
-        | NotGiven = NOT_GIVEN,
-        states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> AsyncPaginator[ClusterListResponse, AsyncOffsetPageClusters[ClusterListResponse]]:
-        """
-        List knowledge entries for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get_api_list(
-            f"/api/projects/{project_id}/entries/clusters",
-            page=AsyncOffsetPageClusters[ClusterListResponse],
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform(
-                    {
-                        "eval_issue_types": eval_issue_types,
-                        "instruction_adherence_failure": instruction_adherence_failure,
-                        "limit": limit,
-                        "offset": offset,
-                        "order": order,
-                        "sort": sort,
-                        "states": states,
-                    },
-                    cluster_list_params.ClusterListParams,
-                ),
-            ),
-            model=ClusterListResponse,
-        )
-
-    async def list_variants(
-        self,
-        representative_entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> ClusterListVariantsResponse:
-        """
-        Get Cluster Variants Route
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not representative_entry_id:
-            raise ValueError(
-                f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}"
-            )
-        return await self._get(
-            f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=ClusterListVariantsResponse,
-        )
-
-
-class ClustersResourceWithRawResponse:
-    def __init__(self, clusters: ClustersResource) -> None:
-        self._clusters = clusters
-
-        self.list = to_raw_response_wrapper(
-            clusters.list,
-        )
-        self.list_variants = to_raw_response_wrapper(
-            clusters.list_variants,
-        )
-
-
-class AsyncClustersResourceWithRawResponse:
-    def __init__(self, clusters: AsyncClustersResource) -> None:
-        self._clusters = clusters
-
-        self.list = async_to_raw_response_wrapper(
-            clusters.list,
-        )
-        self.list_variants = async_to_raw_response_wrapper(
-            clusters.list_variants,
-        )
-
-
-class ClustersResourceWithStreamingResponse:
-    def __init__(self, clusters: ClustersResource) -> None:
-        self._clusters = clusters
-
-        self.list = to_streamed_response_wrapper(
-            clusters.list,
-        )
-        self.list_variants = to_streamed_response_wrapper(
-            clusters.list_variants,
-        )
-
-
-class AsyncClustersResourceWithStreamingResponse:
-    def __init__(self, clusters: AsyncClustersResource) -> None:
-        self._clusters = clusters
-
-        self.list = async_to_streamed_response_wrapper(
-            clusters.list,
-        )
-        self.list_variants = async_to_streamed_response_wrapper(
-            clusters.list_variants,
-        )
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
deleted file mode 100644
index c6b43a4a..00000000
--- a/src/codex/resources/projects/entries.py
+++ /dev/null
@@ -1,948 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import typing_extensions
-from typing import Iterable, Optional
-
-import httpx
-
-from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
-    to_raw_response_wrapper,
-    to_streamed_response_wrapper,
-    async_to_raw_response_wrapper,
-    async_to_streamed_response_wrapper,
-)
-from ..._base_client import make_request_options
-from ...types.projects import entry_query_params, entry_create_params, entry_update_params, entry_notify_sme_params
-from ...types.projects.entry import Entry
-from ...types.projects.entry_query_response import EntryQueryResponse
-from ...types.projects.entry_notify_sme_response import EntryNotifySmeResponse
-
-__all__ = ["EntriesResource", "AsyncEntriesResource"]
-
-
-class EntriesResource(SyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> EntriesResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return EntriesResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> EntriesResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return EntriesResourceWithStreamingResponse(self)
-
-    def create(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
-        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Create a new knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return self._post(
-            f"/api/projects/{project_id}/entries/",
-            body=maybe_transform(
-                {
-                    "question": question,
-                    "answer": answer,
-                    "client_query_metadata": client_query_metadata,
-                    "draft_answer": draft_answer,
-                },
-                entry_create_params.EntryCreateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def retrieve(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Get a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._get(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def update(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        question: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Update a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            body=maybe_transform(
-                {
-                    "answer": answer,
-                    "draft_answer": draft_answer,
-                    "question": question,
-                },
-                entry_update_params.EntryUpdateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    def delete(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> None:
-        """
-        Delete a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
-        return self._delete(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=NoneType,
-        )
-
-    def notify_sme(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        email: str,
-        view_context: entry_notify_sme_params.ViewContext,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryNotifySmeResponse:
-        """
-        Notify a subject matter expert to review and answer a specific entry.
-
-        Returns: SMENotificationResponse with status and notification details
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._post(
-            f"/api/projects/{project_id}/entries/{entry_id}/notifications",
-            body=maybe_transform(
-                {
-                    "email": email,
-                    "view_context": view_context,
-                },
-                entry_notify_sme_params.EntryNotifySmeParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=EntryNotifySmeResponse,
-        )
-
-    def publish_draft_answer(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """Promote a draft answer to a published answer for a knowledge entry.
-
-        This always
-        results in the entry's draft answer being removed. If the entry already has a
-        published answer, it will be overwritten and permanently lost.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    @typing_extensions.deprecated("deprecated")
-    def query(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        use_llm_matching: bool | NotGiven = NOT_GIVEN,
-        client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
-        query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryQueryResponse:
-        """
-        Query Entries Route
-
-        Args:
-          client_metadata: Deprecated: Use query_metadata instead
-
-          query_metadata: Optional logging data that can be provided by the client.
-
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return self._post(
-            f"/api/projects/{project_id}/entries/query",
-            body=maybe_transform(
-                {
-                    "question": question,
-                    "client_metadata": client_metadata,
-                    "query_metadata": query_metadata,
-                },
-                entry_query_params.EntryQueryParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform({"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams),
-            ),
-            cast_to=EntryQueryResponse,
-        )
-
-    def unpublish_answer(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """Unpublish an answer for a knowledge entry.
-
-        This always results in the entry's
-        answer being removed. If the entry does not already have a draft answer, the
-        current answer will be retained as the draft answer.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-
-class AsyncEntriesResource(AsyncAPIResource):
-    @cached_property
-    def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse:
-        """
-        This property can be used as a prefix for any HTTP method call to return
-        the raw response object instead of the parsed content.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers
-        """
-        return AsyncEntriesResourceWithRawResponse(self)
-
-    @cached_property
-    def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse:
-        """
-        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
-        For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response
-        """
-        return AsyncEntriesResourceWithStreamingResponse(self)
-
-    async def create(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN,
-        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Create a new knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return await self._post(
-            f"/api/projects/{project_id}/entries/",
-            body=await async_maybe_transform(
-                {
-                    "question": question,
-                    "answer": answer,
-                    "client_query_metadata": client_query_metadata,
-                    "draft_answer": draft_answer,
-                },
-                entry_create_params.EntryCreateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def retrieve(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Get a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._get(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def update(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        answer: Optional[str] | NotGiven = NOT_GIVEN,
-        draft_answer: Optional[str] | NotGiven = NOT_GIVEN,
-        question: Optional[str] | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """
-        Update a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            body=await async_maybe_transform(
-                {
-                    "answer": answer,
-                    "draft_answer": draft_answer,
-                    "question": question,
-                },
-                entry_update_params.EntryUpdateParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    async def delete(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> None:
-        """
-        Delete a knowledge entry for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        extra_headers = {"Accept": "*/*", **(extra_headers or {})}
-        return await self._delete(
-            f"/api/projects/{project_id}/entries/{entry_id}",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=NoneType,
-        )
-
-    async def notify_sme(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        email: str,
-        view_context: entry_notify_sme_params.ViewContext,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryNotifySmeResponse:
-        """
-        Notify a subject matter expert to review and answer a specific entry.
-
-        Returns: SMENotificationResponse with status and notification details
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._post(
-            f"/api/projects/{project_id}/entries/{entry_id}/notifications",
-            body=await async_maybe_transform(
-                {
-                    "email": email,
-                    "view_context": view_context,
-                },
-                entry_notify_sme_params.EntryNotifySmeParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=EntryNotifySmeResponse,
-        )
-
-    async def publish_draft_answer(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """Promote a draft answer to a published answer for a knowledge entry.
-
-        This always
-        results in the entry's draft answer being removed. If the entry already has a
-        published answer, it will be overwritten and permanently lost.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-    @typing_extensions.deprecated("deprecated")
-    async def query(
-        self,
-        project_id: str,
-        *,
-        question: str,
-        use_llm_matching: bool | NotGiven = NOT_GIVEN,
-        client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
-        query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN,
-        x_client_library_version: str | NotGiven = NOT_GIVEN,
-        x_integration_type: str | NotGiven = NOT_GIVEN,
-        x_source: str | NotGiven = NOT_GIVEN,
-        x_stainless_package_version: str | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> EntryQueryResponse:
-        """
-        Query Entries Route
-
-        Args:
-          client_metadata: Deprecated: Use query_metadata instead
-
-          query_metadata: Optional logging data that can be provided by the client.
-
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        extra_headers = {
-            **strip_not_given(
-                {
-                    "x-client-library-version": x_client_library_version,
-                    "x-integration-type": x_integration_type,
-                    "x-source": x_source,
-                    "x-stainless-package-version": x_stainless_package_version,
-                }
-            ),
-            **(extra_headers or {}),
-        }
-        return await self._post(
-            f"/api/projects/{project_id}/entries/query",
-            body=await async_maybe_transform(
-                {
-                    "question": question,
-                    "client_metadata": client_metadata,
-                    "query_metadata": query_metadata,
-                },
-                entry_query_params.EntryQueryParams,
-            ),
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=await async_maybe_transform(
-                    {"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams
-                ),
-            ),
-            cast_to=EntryQueryResponse,
-        )
-
-    async def unpublish_answer(
-        self,
-        entry_id: str,
-        *,
-        project_id: str,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> Entry:
-        """Unpublish an answer for a knowledge entry.
-
-        This always results in the entry's
-        answer being removed. If the entry does not already have a draft answer, the
-        current answer will be retained as the draft answer.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        if not entry_id:
-            raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}")
-        return await self._put(
-            f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer",
-            options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
-            ),
-            cast_to=Entry,
-        )
-
-
-class EntriesResourceWithRawResponse:
-    def __init__(self, entries: EntriesResource) -> None:
-        self._entries = entries
-
-        self.create = to_raw_response_wrapper(
-            entries.create,
-        )
-        self.retrieve = to_raw_response_wrapper(
-            entries.retrieve,
-        )
-        self.update = to_raw_response_wrapper(
-            entries.update,
-        )
-        self.delete = to_raw_response_wrapper(
-            entries.delete,
-        )
-        self.notify_sme = to_raw_response_wrapper(
-            entries.notify_sme,
-        )
-        self.publish_draft_answer = to_raw_response_wrapper(
-            entries.publish_draft_answer,
-        )
-        self.query = (  # pyright: ignore[reportDeprecated]
-            to_raw_response_wrapper(
-                entries.query  # pyright: ignore[reportDeprecated],
-            )
-        )
-        self.unpublish_answer = to_raw_response_wrapper(
-            entries.unpublish_answer,
-        )
-
-
-class AsyncEntriesResourceWithRawResponse:
-    def __init__(self, entries: AsyncEntriesResource) -> None:
-        self._entries = entries
-
-        self.create = async_to_raw_response_wrapper(
-            entries.create,
-        )
-        self.retrieve = async_to_raw_response_wrapper(
-            entries.retrieve,
-        )
-        self.update = async_to_raw_response_wrapper(
-            entries.update,
-        )
-        self.delete = async_to_raw_response_wrapper(
-            entries.delete,
-        )
-        self.notify_sme = async_to_raw_response_wrapper(
-            entries.notify_sme,
-        )
-        self.publish_draft_answer = async_to_raw_response_wrapper(
-            entries.publish_draft_answer,
-        )
-        self.query = (  # pyright: ignore[reportDeprecated]
-            async_to_raw_response_wrapper(
-                entries.query  # pyright: ignore[reportDeprecated],
-            )
-        )
-        self.unpublish_answer = async_to_raw_response_wrapper(
-            entries.unpublish_answer,
-        )
-
-
-class EntriesResourceWithStreamingResponse:
-    def __init__(self, entries: EntriesResource) -> None:
-        self._entries = entries
-
-        self.create = to_streamed_response_wrapper(
-            entries.create,
-        )
-        self.retrieve = to_streamed_response_wrapper(
-            entries.retrieve,
-        )
-        self.update = to_streamed_response_wrapper(
-            entries.update,
-        )
-        self.delete = to_streamed_response_wrapper(
-            entries.delete,
-        )
-        self.notify_sme = to_streamed_response_wrapper(
-            entries.notify_sme,
-        )
-        self.publish_draft_answer = to_streamed_response_wrapper(
-            entries.publish_draft_answer,
-        )
-        self.query = (  # pyright: ignore[reportDeprecated]
-            to_streamed_response_wrapper(
-                entries.query  # pyright: ignore[reportDeprecated],
-            )
-        )
-        self.unpublish_answer = to_streamed_response_wrapper(
-            entries.unpublish_answer,
-        )
-
-
-class AsyncEntriesResourceWithStreamingResponse:
-    def __init__(self, entries: AsyncEntriesResource) -> None:
-        self._entries = entries
-
-        self.create = async_to_streamed_response_wrapper(
-            entries.create,
-        )
-        self.retrieve = async_to_streamed_response_wrapper(
-            entries.retrieve,
-        )
-        self.update = async_to_streamed_response_wrapper(
-            entries.update,
-        )
-        self.delete = async_to_streamed_response_wrapper(
-            entries.delete,
-        )
-        self.notify_sme = async_to_streamed_response_wrapper(
-            entries.notify_sme,
-        )
-        self.publish_draft_answer = async_to_streamed_response_wrapper(
-            entries.publish_draft_answer,
-        )
-        self.query = (  # pyright: ignore[reportDeprecated]
-            async_to_streamed_response_wrapper(
-                entries.query  # pyright: ignore[reportDeprecated],
-            )
-        )
-        self.unpublish_answer = async_to_streamed_response_wrapper(
-            entries.unpublish_answer,
-        )
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index fc5a43a4..1314b7be 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -25,24 +25,8 @@
     project_increment_queries_params,
     project_retrieve_analytics_params,
 )
-from .entries import (
-    EntriesResource,
-    AsyncEntriesResource,
-    EntriesResourceWithRawResponse,
-    AsyncEntriesResourceWithRawResponse,
-    EntriesResourceWithStreamingResponse,
-    AsyncEntriesResourceWithStreamingResponse,
-)
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
 from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
-from .clusters import (
-    ClustersResource,
-    AsyncClustersResource,
-    ClustersResourceWithRawResponse,
-    AsyncClustersResourceWithRawResponse,
-    ClustersResourceWithStreamingResponse,
-    AsyncClustersResourceWithStreamingResponse,
-)
 from ..._compat import cached_property
 from .query_logs import (
     QueryLogsResource,
@@ -91,14 +75,6 @@ class ProjectsResource(SyncAPIResource):
     def access_keys(self) -> AccessKeysResource:
         return AccessKeysResource(self._client)
 
-    @cached_property
-    def entries(self) -> EntriesResource:
-        return EntriesResource(self._client)
-
-    @cached_property
-    def clusters(self) -> ClustersResource:
-        return ClustersResource(self._client)
-
     @cached_property
     def evals(self) -> EvalsResource:
         return EvalsResource(self._client)
@@ -699,14 +675,6 @@ class AsyncProjectsResource(AsyncAPIResource):
     def access_keys(self) -> AsyncAccessKeysResource:
         return AsyncAccessKeysResource(self._client)
 
-    @cached_property
-    def entries(self) -> AsyncEntriesResource:
-        return AsyncEntriesResource(self._client)
-
-    @cached_property
-    def clusters(self) -> AsyncClustersResource:
-        return AsyncClustersResource(self._client)
-
     @cached_property
     def evals(self) -> AsyncEvalsResource:
         return AsyncEvalsResource(self._client)
@@ -1345,14 +1313,6 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithRawResponse:
         return AccessKeysResourceWithRawResponse(self._projects.access_keys)
 
-    @cached_property
-    def entries(self) -> EntriesResourceWithRawResponse:
-        return EntriesResourceWithRawResponse(self._projects.entries)
-
-    @cached_property
-    def clusters(self) -> ClustersResourceWithRawResponse:
-        return ClustersResourceWithRawResponse(self._projects.clusters)
-
     @cached_property
     def evals(self) -> EvalsResourceWithRawResponse:
         return EvalsResourceWithRawResponse(self._projects.evals)
@@ -1407,14 +1367,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse:
         return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys)
 
-    @cached_property
-    def entries(self) -> AsyncEntriesResourceWithRawResponse:
-        return AsyncEntriesResourceWithRawResponse(self._projects.entries)
-
-    @cached_property
-    def clusters(self) -> AsyncClustersResourceWithRawResponse:
-        return AsyncClustersResourceWithRawResponse(self._projects.clusters)
-
     @cached_property
     def evals(self) -> AsyncEvalsResourceWithRawResponse:
         return AsyncEvalsResourceWithRawResponse(self._projects.evals)
@@ -1469,14 +1421,6 @@ def __init__(self, projects: ProjectsResource) -> None:
     def access_keys(self) -> AccessKeysResourceWithStreamingResponse:
         return AccessKeysResourceWithStreamingResponse(self._projects.access_keys)
 
-    @cached_property
-    def entries(self) -> EntriesResourceWithStreamingResponse:
-        return EntriesResourceWithStreamingResponse(self._projects.entries)
-
-    @cached_property
-    def clusters(self) -> ClustersResourceWithStreamingResponse:
-        return ClustersResourceWithStreamingResponse(self._projects.clusters)
-
     @cached_property
     def evals(self) -> EvalsResourceWithStreamingResponse:
         return EvalsResourceWithStreamingResponse(self._projects.evals)
@@ -1531,14 +1475,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
     def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse:
         return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys)
 
-    @cached_property
-    def entries(self) -> AsyncEntriesResourceWithStreamingResponse:
-        return AsyncEntriesResourceWithStreamingResponse(self._projects.entries)
-
-    @cached_property
-    def clusters(self) -> AsyncClustersResourceWithStreamingResponse:
-        return AsyncClustersResourceWithStreamingResponse(self._projects.clusters)
-
     @cached_property
     def evals(self) -> AsyncEvalsResourceWithStreamingResponse:
         return AsyncEvalsResourceWithStreamingResponse(self._projects.evals)
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 8a26aa00..4f754703 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -2,25 +2,16 @@
 
 from __future__ import annotations
 
-from .entry import Entry as Entry
 from .access_key_schema import AccessKeySchema as AccessKeySchema
-from .entry_query_params import EntryQueryParams as EntryQueryParams
 from .eval_create_params import EvalCreateParams as EvalCreateParams
 from .eval_list_response import EvalListResponse as EvalListResponse
 from .eval_update_params import EvalUpdateParams as EvalUpdateParams
-from .cluster_list_params import ClusterListParams as ClusterListParams
-from .entry_create_params import EntryCreateParams as EntryCreateParams
-from .entry_update_params import EntryUpdateParams as EntryUpdateParams
-from .entry_query_response import EntryQueryResponse as EntryQueryResponse
-from .cluster_list_response import ClusterListResponse as ClusterListResponse
 from .query_log_list_params import QueryLogListParams as QueryLogListParams
-from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams
 from .query_log_list_response import QueryLogListResponse as QueryLogListResponse
 from .remediation_list_params import RemediationListParams as RemediationListParams
 from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams
 from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse
 from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams
-from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse
 from .remediation_create_params import RemediationCreateParams as RemediationCreateParams
 from .remediation_list_response import RemediationListResponse as RemediationListResponse
 from .remediation_pause_response import RemediationPauseResponse as RemediationPauseResponse
@@ -30,7 +21,6 @@
 from .remediation_publish_response import RemediationPublishResponse as RemediationPublishResponse
 from .remediation_unpause_response import RemediationUnpauseResponse as RemediationUnpauseResponse
 from .remediation_retrieve_response import RemediationRetrieveResponse as RemediationRetrieveResponse
-from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse
 from .query_log_list_by_group_params import QueryLogListByGroupParams as QueryLogListByGroupParams
 from .query_log_list_groups_response import QueryLogListGroupsResponse as QueryLogListGroupsResponse
 from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
deleted file mode 100644
index 20284d84..00000000
--- a/src/codex/types/projects/cluster_list_params.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import List, Optional
-from typing_extensions import Literal, TypedDict
-
-__all__ = ["ClusterListParams"]
-
-
-class ClusterListParams(TypedDict, total=False):
-    eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
-
-    instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]]
-
-    limit: int
-
-    offset: int
-
-    order: Literal["asc", "desc"]
-
-    sort: Optional[
-        Literal[
-            "created_at",
-            "answered_at",
-            "cluster_frequency_count",
-            "custom_rank",
-            "eval_score",
-            "html_format_score",
-            "content_structure_score",
-        ]
-    ]
-
-    states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
deleted file mode 100644
index 1fc8bd5e..00000000
--- a/src/codex/types/projects/cluster_list_response.py
+++ /dev/null
@@ -1,214 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-from datetime import datetime
-from typing_extensions import Literal
-
-from ..._models import BaseModel
-
-__all__ = [
-    "ClusterListResponse",
-    "ManagedMetadata",
-    "ManagedMetadataContentStructureScores",
-    "ManagedMetadataContextSufficiency",
-    "ManagedMetadataHTMLFormatScores",
-    "ManagedMetadataQueryEaseCustomized",
-    "ManagedMetadataResponseGroundedness",
-    "ManagedMetadataResponseHelpfulness",
-    "ManagedMetadataTrustworthiness",
-]
-
-
-class ManagedMetadataContentStructureScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataContextSufficiency(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataHTMLFormatScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataQueryEaseCustomized(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataResponseGroundedness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataResponseHelpfulness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataTrustworthiness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadata(BaseModel):
-    latest_context: Optional[str] = None
-    """The most recent context string."""
-
-    latest_entry_point: Optional[str] = None
-    """The most recent entry point string."""
-
-    latest_llm_response: Optional[str] = None
-    """The most recent LLM response string."""
-
-    latest_location: Optional[str] = None
-    """The most recent location string."""
-
-    content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    contexts: Optional[List[str]] = None
-
-    entry_points: Optional[List[str]] = None
-
-    html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    llm_responses: Optional[List[str]] = None
-
-    locations: Optional[List[str]] = None
-
-    query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-
-class ClusterListResponse(BaseModel):
-    id: str
-
-    cluster_frequency_count: int
-
-    created_at: datetime
-
-    managed_metadata: ManagedMetadata
-    """Extract system-defined, managed metadata from client_query_metadata."""
-
-    project_id: str
-
-    question: str
-
-    state: Literal["unanswered", "draft", "published", "published_with_draft"]
-
-    answer: Optional[str] = None
-
-    answered_at: Optional[datetime] = None
-
-    client_query_metadata: Optional[List[object]] = None
-
-    content_structure_score: Optional[float] = None
-
-    draft_answer: Optional[str] = None
-
-    draft_answer_last_edited: Optional[datetime] = None
-
-    eval_issue_type: Optional[str] = None
-
-    eval_score: Optional[float] = None
-
-    frequency_count: Optional[int] = None
-    """number of times the entry matched for a /query request"""
-
-    html_format_score: Optional[float] = None
-
-    representative_entry_id: Optional[str] = None
diff --git a/src/codex/types/projects/cluster_list_variants_response.py b/src/codex/types/projects/cluster_list_variants_response.py
deleted file mode 100644
index aa359058..00000000
--- a/src/codex/types/projects/cluster_list_variants_response.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-
-from .entry import Entry
-from ..._models import BaseModel
-
-__all__ = ["ClusterListVariantsResponse"]
-
-
-class ClusterListVariantsResponse(BaseModel):
-    entries: List[Entry]
-
-    total_count: int
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
deleted file mode 100644
index 3f7a86da..00000000
--- a/src/codex/types/projects/entry.py
+++ /dev/null
@@ -1,210 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-from datetime import datetime
-from typing_extensions import Literal
-
-from ..._models import BaseModel
-
-__all__ = [
-    "Entry",
-    "ManagedMetadata",
-    "ManagedMetadataContentStructureScores",
-    "ManagedMetadataContextSufficiency",
-    "ManagedMetadataHTMLFormatScores",
-    "ManagedMetadataQueryEaseCustomized",
-    "ManagedMetadataResponseGroundedness",
-    "ManagedMetadataResponseHelpfulness",
-    "ManagedMetadataTrustworthiness",
-]
-
-
-class ManagedMetadataContentStructureScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataContextSufficiency(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataHTMLFormatScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataQueryEaseCustomized(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataResponseGroundedness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataResponseHelpfulness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadataTrustworthiness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class ManagedMetadata(BaseModel):
-    latest_context: Optional[str] = None
-    """The most recent context string."""
-
-    latest_entry_point: Optional[str] = None
-    """The most recent entry point string."""
-
-    latest_llm_response: Optional[str] = None
-    """The most recent LLM response string."""
-
-    latest_location: Optional[str] = None
-    """The most recent location string."""
-
-    content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    contexts: Optional[List[str]] = None
-
-    entry_points: Optional[List[str]] = None
-
-    html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    llm_responses: Optional[List[str]] = None
-
-    locations: Optional[List[str]] = None
-
-    query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-
-class Entry(BaseModel):
-    id: str
-
-    created_at: datetime
-
-    managed_metadata: ManagedMetadata
-    """Extract system-defined, managed metadata from client_query_metadata."""
-
-    project_id: str
-
-    question: str
-
-    state: Literal["unanswered", "draft", "published", "published_with_draft"]
-
-    answer: Optional[str] = None
-
-    answered_at: Optional[datetime] = None
-
-    client_query_metadata: Optional[List[object]] = None
-
-    content_structure_score: Optional[float] = None
-
-    draft_answer: Optional[str] = None
-
-    draft_answer_last_edited: Optional[datetime] = None
-
-    eval_issue_type: Optional[str] = None
-
-    eval_score: Optional[float] = None
-
-    frequency_count: Optional[int] = None
-    """number of times the entry matched for a /query request"""
-
-    html_format_score: Optional[float] = None
diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py
deleted file mode 100644
index f06846bb..00000000
--- a/src/codex/types/projects/entry_create_params.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Iterable, Optional
-from typing_extensions import Required, Annotated, TypedDict
-
-from ..._utils import PropertyInfo
-
-__all__ = ["EntryCreateParams"]
-
-
-class EntryCreateParams(TypedDict, total=False):
-    question: Required[str]
-
-    answer: Optional[str]
-
-    client_query_metadata: Iterable[object]
-
-    draft_answer: Optional[str]
-
-    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
-
-    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
-
-    x_source: Annotated[str, PropertyInfo(alias="x-source")]
-
-    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py
deleted file mode 100644
index 3d06d1a6..00000000
--- a/src/codex/types/projects/entry_notify_sme_params.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Literal, Required, TypedDict
-
-__all__ = ["EntryNotifySmeParams", "ViewContext"]
-
-
-class EntryNotifySmeParams(TypedDict, total=False):
-    project_id: Required[str]
-
-    email: Required[str]
-
-    view_context: Required[ViewContext]
-
-
-class ViewContext(TypedDict, total=False):
-    page: Required[int]
-
-    filter: Literal[
-        "unanswered",
-        "answered",
-        "all",
-        "hallucination",
-        "search_failure",
-        "unhelpful",
-        "difficult_query",
-        "unsupported",
-    ]
diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py
deleted file mode 100644
index b3c5b373..00000000
--- a/src/codex/types/projects/entry_notify_sme_response.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from ..._models import BaseModel
-
-__all__ = ["EntryNotifySmeResponse"]
-
-
-class EntryNotifySmeResponse(BaseModel):
-    entry_id: str
-
-    recipient_email: str
-
-    status: str
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
deleted file mode 100644
index 2ba33b82..00000000
--- a/src/codex/types/projects/entry_query_params.py
+++ /dev/null
@@ -1,300 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Dict, List, Union, Iterable, Optional
-from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
-
-from ..._utils import PropertyInfo
-
-__all__ = [
-    "EntryQueryParams",
-    "QueryMetadata",
-    "QueryMetadataContextUnionMember3",
-    "QueryMetadataMessage",
-    "QueryMetadataMessageChatCompletionDeveloperMessageParam",
-    "QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "QueryMetadataMessageChatCompletionSystemMessageParam",
-    "QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1",
-    "QueryMetadataMessageChatCompletionUserMessageParam",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File",
-    "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile",
-    "QueryMetadataMessageChatCompletionAssistantMessageParam",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamAudio",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamToolCall",
-    "QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction",
-    "QueryMetadataMessageChatCompletionToolMessageParam",
-    "QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1",
-    "QueryMetadataMessageChatCompletionFunctionMessageParam",
-]
-
-
-class EntryQueryParams(TypedDict, total=False):
-    question: Required[str]
-
-    use_llm_matching: bool
-
-    client_metadata: Optional[object]
-    """Deprecated: Use query_metadata instead"""
-
-    query_metadata: Optional[QueryMetadata]
-    """Optional logging data that can be provided by the client."""
-
-    x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
-
-    x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
-
-    x_source: Annotated[str, PropertyInfo(alias="x-source")]
-
-    x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
-
-
-class QueryMetadataContextUnionMember3(TypedDict, total=False):
-    content: Required[str]
-    """The actual content/text of the document."""
-
-    id: Optional[str]
-    """Unique identifier for the document. Useful for tracking documents"""
-
-    source: Optional[str]
-    """Source or origin of the document. Useful for citations."""
-
-    tags: Optional[List[str]]
-    """Tags or categories for the document. Useful for filtering"""
-
-    title: Optional[str]
-    """Title or heading of the document. Useful for display and context."""
-
-
-class QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
-    text: Required[str]
-
-    type: Required[Literal["text"]]
-
-
-class QueryMetadataMessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1]]]
-
-    role: Required[Literal["developer"]]
-
-    name: str
-
-
-class QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False):
-    text: Required[str]
-
-    type: Required[Literal["text"]]
-
-
-class QueryMetadataMessageChatCompletionSystemMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1]]]
-
-    role: Required[Literal["system"]]
-
-    name: str
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
-    TypedDict, total=False
-):
-    text: Required[str]
-
-    type: Required[Literal["text"]]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL(
-    TypedDict, total=False
-):
-    url: Required[str]
-
-    detail: Literal["auto", "low", "high"]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam(
-    TypedDict, total=False
-):
-    image_url: Required[
-        QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL
-    ]
-
-    type: Required[Literal["image_url"]]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
-    TypedDict, total=False
-):
-    data: Required[str]
-
-    format: Required[Literal["wav", "mp3"]]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam(
-    TypedDict, total=False
-):
-    input_audio: Required[
-        QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
-    ]
-
-    type: Required[Literal["input_audio"]]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False):
-    file_data: str
-
-    file_id: str
-
-    filename: str
-
-
-class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False):
-    file: Required[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile]
-
-    type: Required[Literal["file"]]
-
-
-QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[
-    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
-    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam,
-    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam,
-    QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File,
-]
-
-
-class QueryMetadataMessageChatCompletionUserMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1]]]
-
-    role: Required[Literal["user"]]
-
-    name: str
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False):
-    id: Required[str]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
-    TypedDict, total=False
-):
-    text: Required[str]
-
-    type: Required[Literal["text"]]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam(
-    TypedDict, total=False
-):
-    refusal: Required[str]
-
-    type: Required[Literal["refusal"]]
-
-
-QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[
-    QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
-    QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False):
-    arguments: Required[str]
-
-    name: Required[str]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False):
-    arguments: Required[str]
-
-    name: Required[str]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False):
-    id: Required[str]
-
-    function: Required[QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction]
-
-    type: Required[Literal["function"]]
-
-
-class QueryMetadataMessageChatCompletionAssistantMessageParam(TypedDict, total=False):
-    role: Required[Literal["assistant"]]
-
-    audio: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamAudio]
-
-    content: Union[str, Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1], None]
-
-    function_call: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall]
-
-    name: str
-
-    refusal: Optional[str]
-
-    tool_calls: Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamToolCall]
-
-
-class QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
-    text: Required[str]
-
-    type: Required[Literal["text"]]
-
-
-class QueryMetadataMessageChatCompletionToolMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1]]]
-
-    role: Required[Literal["tool"]]
-
-    tool_call_id: Required[str]
-
-
-class QueryMetadataMessageChatCompletionFunctionMessageParam(TypedDict, total=False):
-    content: Required[Optional[str]]
-
-    name: Required[str]
-
-    role: Required[Literal["function"]]
-
-
-QueryMetadataMessage: TypeAlias = Union[
-    QueryMetadataMessageChatCompletionDeveloperMessageParam,
-    QueryMetadataMessageChatCompletionSystemMessageParam,
-    QueryMetadataMessageChatCompletionUserMessageParam,
-    QueryMetadataMessageChatCompletionAssistantMessageParam,
-    QueryMetadataMessageChatCompletionToolMessageParam,
-    QueryMetadataMessageChatCompletionFunctionMessageParam,
-]
-
-
-class QueryMetadata(TypedDict, total=False):
-    context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None]
-    """RAG context used for the query"""
-
-    custom_metadata: Optional[object]
-    """Arbitrary metadata supplied by the user/system"""
-
-    eval_scores: Optional[Dict[str, float]]
-    """Evaluation scores for the original response"""
-
-    evaluated_response: Optional[str]
-    """The response being evaluated from the RAG system(before any remediation)"""
-
-    messages: Optional[Iterable[QueryMetadataMessage]]
-    """Optional message history to provide conversation context for the query.
-
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
-    """
-
-    original_question: Optional[str]
-    """The original question that was asked before any rewriting or processing.
-
-    For all non-conversational RAG, original_question should be the same as the
-    final question seen in Codex.
-    """
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
deleted file mode 100644
index cd5a4c97..00000000
--- a/src/codex/types/projects/entry_query_response.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from ..._models import BaseModel
-
-__all__ = [
-    "EntryQueryResponse",
-    "Entry",
-    "EntryManagedMetadata",
-    "EntryManagedMetadataContentStructureScores",
-    "EntryManagedMetadataContextSufficiency",
-    "EntryManagedMetadataHTMLFormatScores",
-    "EntryManagedMetadataQueryEaseCustomized",
-    "EntryManagedMetadataResponseGroundedness",
-    "EntryManagedMetadataResponseHelpfulness",
-    "EntryManagedMetadataTrustworthiness",
-]
-
-
-class EntryManagedMetadataContentStructureScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataContextSufficiency(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataHTMLFormatScores(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataQueryEaseCustomized(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataResponseGroundedness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataResponseHelpfulness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadataTrustworthiness(BaseModel):
-    average: Optional[float] = None
-    """The average of all scores."""
-
-    latest: Optional[float] = None
-    """The most recent score."""
-
-    max: Optional[float] = None
-    """The maximum score."""
-
-    min: Optional[float] = None
-    """The minimum score."""
-
-    scores: Optional[List[float]] = None
-
-
-class EntryManagedMetadata(BaseModel):
-    latest_context: Optional[str] = None
-    """The most recent context string."""
-
-    latest_entry_point: Optional[str] = None
-    """The most recent entry point string."""
-
-    latest_llm_response: Optional[str] = None
-    """The most recent LLM response string."""
-
-    latest_location: Optional[str] = None
-    """The most recent location string."""
-
-    content_structure_scores: Optional[EntryManagedMetadataContentStructureScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    contexts: Optional[List[str]] = None
-
-    entry_points: Optional[List[str]] = None
-
-    html_format_scores: Optional[EntryManagedMetadataHTMLFormatScores] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    llm_responses: Optional[List[str]] = None
-
-    locations: Optional[List[str]] = None
-
-    query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_groundedness: Optional[EntryManagedMetadataResponseGroundedness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-    trustworthiness: Optional[EntryManagedMetadataTrustworthiness] = None
-    """Holds a list of scores and computes aggregate statistics."""
-
-
-class Entry(BaseModel):
-    id: str
-
-    managed_metadata: EntryManagedMetadata
-    """Extract system-defined, managed metadata from client_query_metadata."""
-
-    question: str
-
-    answer: Optional[str] = None
-
-    client_query_metadata: Optional[List[object]] = None
-
-    draft_answer: Optional[str] = None
-
-
-class EntryQueryResponse(BaseModel):
-    entry: Entry
-
-    answer: Optional[str] = None
diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py
deleted file mode 100644
index aac256f9..00000000
--- a/src/codex/types/projects/entry_update_params.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Required, TypedDict
-
-__all__ = ["EntryUpdateParams"]
-
-
-class EntryUpdateParams(TypedDict, total=False):
-    project_id: Required[str]
-
-    answer: Optional[str]
-
-    draft_answer: Optional[str]
-
-    question: Optional[str]
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index 9d1e0e69..c294d93d 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -71,12 +71,16 @@ class QueryLogsByGroupQueryLog(BaseModel):
 
     is_bad_response: bool
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
     remediation_id: str
 
+    remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 7b77cc05..ce5806b6 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -78,7 +78,7 @@ class QueryLogGroup(BaseModel):
 
     remediation_id: str
 
-    status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
+    remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
     total_count: int
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 8bb61283..5214cff5 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -69,12 +69,16 @@ class QueryLogRetrieveResponse(BaseModel):
 
     is_bad_response: bool
 
+    needs_review: bool
+
     project_id: str
 
     question: str
 
     remediation_id: str
 
+    remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py
deleted file mode 100644
index 87734277..00000000
--- a/tests/api_resources/projects/test_clusters.py
+++ /dev/null
@@ -1,247 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from codex import Codex, AsyncCodex
-from tests.utils import assert_matches_type
-from codex.pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters
-from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestClusters:
-    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list(self, client: Codex) -> None:
-        cluster = client.projects.clusters.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list_with_all_params(self, client: Codex) -> None:
-        cluster = client.projects.clusters.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            eval_issue_types=["hallucination"],
-            instruction_adherence_failure="html_format",
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            states=["unanswered"],
-        )
-        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_list(self, client: Codex) -> None:
-        response = client.projects.clusters.with_raw_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        cluster = response.parse()
-        assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_list(self, client: Codex) -> None:
-        with client.projects.clusters.with_streaming_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            cluster = response.parse()
-            assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_list(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.clusters.with_raw_response.list(
-                project_id="",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_list_variants(self, client: Codex) -> None:
-        cluster = client.projects.clusters.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_list_variants(self, client: Codex) -> None:
-        response = client.projects.clusters.with_raw_response.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        cluster = response.parse()
-        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_list_variants(self, client: Codex) -> None:
-        with client.projects.clusters.with_streaming_response.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            cluster = response.parse()
-            assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_list_variants(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.clusters.with_raw_response.list_variants(
-                representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(
-            ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''"
-        ):
-            client.projects.clusters.with_raw_response.list_variants(
-                representative_entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-
-class TestAsyncClusters:
-    parametrize = pytest.mark.parametrize(
-        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
-    )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list(self, async_client: AsyncCodex) -> None:
-        cluster = await async_client.projects.clusters.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
-        cluster = await async_client.projects.clusters.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            eval_issue_types=["hallucination"],
-            instruction_adherence_failure="html_format",
-            limit=1,
-            offset=0,
-            order="asc",
-            sort="created_at",
-            states=["unanswered"],
-        )
-        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.clusters.with_raw_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        cluster = await response.parse()
-        assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.clusters.with_streaming_response.list(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            cluster = await response.parse()
-            assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_list(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.clusters.with_raw_response.list(
-                project_id="",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_list_variants(self, async_client: AsyncCodex) -> None:
-        cluster = await async_client.projects.clusters.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_list_variants(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.clusters.with_raw_response.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        cluster = await response.parse()
-        assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_list_variants(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.clusters.with_streaming_response.list_variants(
-            representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            cluster = await response.parse()
-            assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_list_variants(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.clusters.with_raw_response.list_variants(
-                representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(
-            ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''"
-        ):
-            await async_client.projects.clusters.with_raw_response.list_variants(
-                representative_entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
deleted file mode 100644
index 1b077d10..00000000
--- a/tests/api_resources/projects/test_entries.py
+++ /dev/null
@@ -1,1014 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from codex import Codex, AsyncCodex
-from tests.utils import assert_matches_type
-from codex.types.projects import (
-    Entry,
-    EntryQueryResponse,
-    EntryNotifySmeResponse,
-)
-
-# pyright: reportDeprecated=false
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestEntries:
-    parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_create(self, client: Codex) -> None:
-        entry = client.projects.entries.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_create_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            answer="answer",
-            client_query_metadata=[{}],
-            draft_answer="draft_answer",
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_create(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_create(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_create(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.create(
-                project_id="",
-                question="question",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_retrieve(self, client: Codex) -> None:
-        entry = client.projects.entries.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_retrieve(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_retrieve(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_retrieve(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.retrieve(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.retrieve(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_update(self, client: Codex) -> None:
-        entry = client.projects.entries.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_update_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            answer="answer",
-            draft_answer="draft_answer",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_update(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_update(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_update(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.update(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.update(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_delete(self, client: Codex) -> None:
-        entry = client.projects.entries.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert entry is None
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_delete(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert entry is None
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_delete(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert entry is None
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_delete(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.delete(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.delete(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_notify_sme(self, client: Codex) -> None:
-        entry = client.projects.entries.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        )
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_notify_sme_with_all_params(self, client: Codex) -> None:
-        entry = client.projects.entries.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={
-                "page": 0,
-                "filter": "unanswered",
-            },
-        )
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_notify_sme(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_notify_sme(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_notify_sme(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.notify_sme(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-                email="email",
-                view_context={"page": 0},
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.notify_sme(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                email="email",
-                view_context={"page": 0},
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_publish_draft_answer(self, client: Codex) -> None:
-        entry = client.projects.entries.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_publish_draft_answer(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_publish_draft_answer(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_publish_draft_answer(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.publish_draft_answer(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.publish_draft_answer(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_query(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            entry = client.projects.entries.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            )
-
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_query_with_all_params(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            entry = client.projects.entries.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-                use_llm_matching=True,
-                client_metadata={},
-                query_metadata={
-                    "context": "string",
-                    "custom_metadata": {},
-                    "eval_scores": {"foo": 0},
-                    "evaluated_response": "evaluated_response",
-                    "messages": [
-                        {
-                            "content": "string",
-                            "role": "developer",
-                            "name": "name",
-                        }
-                    ],
-                    "original_question": "original_question",
-                },
-                x_client_library_version="x-client-library-version",
-                x_integration_type="x-integration-type",
-                x_source="x-source",
-                x_stainless_package_version="x-stainless-package-version",
-            )
-
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_query(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            response = client.projects.entries.with_raw_response.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_query(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with client.projects.entries.with_streaming_response.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            ) as response:
-                assert not response.is_closed
-                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-                entry = response.parse()
-                assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_query(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-                client.projects.entries.with_raw_response.query(
-                    project_id="",
-                    question="question",
-                )
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_unpublish_answer(self, client: Codex) -> None:
-        entry = client.projects.entries.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_unpublish_answer(self, client: Codex) -> None:
-        response = client.projects.entries.with_raw_response.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_unpublish_answer(self, client: Codex) -> None:
-        with client.projects.entries.with_streaming_response.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_unpublish_answer(self, client: Codex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            client.projects.entries.with_raw_response.unpublish_answer(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            client.projects.entries.with_raw_response.unpublish_answer(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-
-class TestAsyncEntries:
-    parametrize = pytest.mark.parametrize(
-        "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
-    )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_create(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-            answer="answer",
-            client_query_metadata=[{}],
-            draft_answer="draft_answer",
-            x_client_library_version="x-client-library-version",
-            x_integration_type="x-integration-type",
-            x_source="x-source",
-            x_stainless_package_version="x-stainless-package-version",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.create(
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_create(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.create(
-                project_id="",
-                question="question",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.retrieve(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.retrieve(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.retrieve(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_update(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            answer="answer",
-            draft_answer="draft_answer",
-            question="question",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.update(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_update(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.update(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.update(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_delete(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert entry is None
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert entry is None
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.delete(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert entry is None
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.delete(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.delete(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_notify_sme(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        )
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_notify_sme_with_all_params(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={
-                "page": 0,
-                "filter": "unanswered",
-            },
-        )
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_notify_sme(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_notify_sme(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.notify_sme(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            email="email",
-            view_context={"page": 0},
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(EntryNotifySmeResponse, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_notify_sme(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.notify_sme(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-                email="email",
-                view_context={"page": 0},
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.notify_sme(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                email="email",
-                view_context={"page": 0},
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_publish_draft_answer(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_publish_draft_answer(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_publish_draft_answer(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.publish_draft_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.publish_draft_answer(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.publish_draft_answer(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_query(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            entry = await async_client.projects.entries.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            )
-
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            entry = await async_client.projects.entries.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-                use_llm_matching=True,
-                client_metadata={},
-                query_metadata={
-                    "context": "string",
-                    "custom_metadata": {},
-                    "eval_scores": {"foo": 0},
-                    "evaluated_response": "evaluated_response",
-                    "messages": [
-                        {
-                            "content": "string",
-                            "role": "developer",
-                            "name": "name",
-                        }
-                    ],
-                    "original_question": "original_question",
-                },
-                x_client_library_version="x-client-library-version",
-                x_integration_type="x-integration-type",
-                x_source="x-source",
-                x_stainless_package_version="x-stainless-package-version",
-            )
-
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_query(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            response = await async_client.projects.entries.with_raw_response.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_query(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            async with async_client.projects.entries.with_streaming_response.query(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                question="question",
-            ) as response:
-                assert not response.is_closed
-                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-                entry = await response.parse()
-                assert_matches_type(EntryQueryResponse, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_query(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-                await async_client.projects.entries.with_raw_response.query(
-                    project_id="",
-                    question="question",
-                )
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_unpublish_answer(self, async_client: AsyncCodex) -> None:
-        entry = await async_client.projects.entries.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_unpublish_answer(self, async_client: AsyncCodex) -> None:
-        response = await async_client.projects.entries.with_raw_response.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        entry = await response.parse()
-        assert_matches_type(Entry, entry, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_unpublish_answer(self, async_client: AsyncCodex) -> None:
-        async with async_client.projects.entries.with_streaming_response.unpublish_answer(
-            entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-        ) as response:
-            assert not response.is_closed
-            assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-            entry = await response.parse()
-            assert_matches_type(Entry, entry, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_unpublish_answer(self, async_client: AsyncCodex) -> None:
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.unpublish_answer(
-                entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                project_id="",
-            )
-
-        with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"):
-            await async_client.projects.entries.with_raw_response.unpublish_answer(
-                entry_id="",
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )

From 84d6674954b7bcf161e0c582a1a28c8adb87e4c3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 2 Jul 2025 05:00:55 +0000
Subject: [PATCH 186/227] chore(ci): change upload type

---
 .github/workflows/ci.yml         | 18 ++++++++++++++++--
 scripts/utils/upload-artifact.sh | 12 +++++++-----
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bbb722b8..a6979ebb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,10 +35,10 @@ jobs:
       - name: Run lints
         run: ./scripts/lint
 
-  upload:
+  build:
     if: github.repository == 'stainless-sdks/codex-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
     timeout-minutes: 10
-    name: upload
+    name: build
     permissions:
       contents: read
       id-token: write
@@ -46,6 +46,20 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
+      - name: Install Rye
+        run: |
+          curl -sSf https://rye.astral.sh/get | bash
+          echo "$HOME/.rye/shims" >> $GITHUB_PATH
+        env:
+          RYE_VERSION: '0.44.0'
+          RYE_INSTALL_OPTION: '--yes'
+
+      - name: Install dependencies
+        run: rye sync --all-features
+
+      - name: Run build
+        run: rye build
+
       - name: Get GitHub OIDC Token
         id: github-oidc
         uses: actions/github-script@v6
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index 62d150a4..aac03848 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -1,7 +1,9 @@
 #!/usr/bin/env bash
 set -exuo pipefail
 
-RESPONSE=$(curl -X POST "$URL" \
+FILENAME=$(basename dist/*.whl)
+
+RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \
   -H "Authorization: Bearer $AUTH" \
   -H "Content-Type: application/json")
 
@@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then
   exit 1
 fi
 
-UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
-  -H "Content-Type: application/gzip" \
-  --data-binary @- "$SIGNED_URL" 2>&1)
+UPLOAD_RESPONSE=$(curl -v -X PUT \
+  -H "Content-Type: binary/octet-stream" \
+  --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1)
 
 if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
   echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
-  echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m"
+  echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/codex-python/$SHA/$FILENAME'\033[0m"
 else
   echo -e "\033[31mFailed to upload artifact.\033[0m"
   exit 1

From 632d1ee76670fa77564ced8443b22336913ebcc8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 2 Jul 2025 19:17:27 +0000
Subject: [PATCH 187/227] feat(api): api update

---
 .stats.yml                                            |  2 +-
 .../projects/query_log_list_by_group_response.py      | 11 +++++++++++
 .../types/projects/query_log_list_groups_response.py  |  9 +++++++++
 src/codex/types/projects/query_log_list_response.py   |  9 +++++++++
 .../types/projects/query_log_retrieve_response.py     |  9 +++++++++
 .../remediation_list_resolved_logs_response.py        |  9 +++++++++
 6 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index cc72a8a8..08bc5f74 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: b2b026661b19d060e5eac490807fe445
+openapi_spec_hash: 06bae836118b9c7ac2cf1b5e1a576296
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index c294d93d..eeb4b638 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -13,6 +13,7 @@
     "QueryLogsByGroupQueryLogFormattedEscalationEvalScores",
     "QueryLogsByGroupQueryLogFormattedEvalScores",
     "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores",
+    "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
 ]
 
@@ -35,6 +36,12 @@ class QueryLogsByGroupQueryLogFormattedGuardrailEvalScores(BaseModel):
     status: Literal["pass", "fail"]
 
 
+class QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class QueryLogsByGroupQueryLogContext(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -69,6 +76,10 @@ class QueryLogsByGroupQueryLog(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None
 
+    formatted_non_guardrail_eval_scores: Optional[
+        Dict[str, QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores]
+    ] = None
+
     is_bad_response: bool
 
     needs_review: bool
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index ce5806b6..074dfaca 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -12,6 +12,7 @@
     "QueryLogGroupFormattedEscalationEvalScores",
     "QueryLogGroupFormattedEvalScores",
     "QueryLogGroupFormattedGuardrailEvalScores",
+    "QueryLogGroupFormattedNonGuardrailEvalScores",
     "QueryLogGroupContext",
 ]
 
@@ -34,6 +35,12 @@ class QueryLogGroupFormattedGuardrailEvalScores(BaseModel):
     status: Literal["pass", "fail"]
 
 
+class QueryLogGroupFormattedNonGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class QueryLogGroupContext(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -68,6 +75,8 @@ class QueryLogGroup(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None
 
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedNonGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     needs_review: bool
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index fa04904d..09a9067a 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -12,6 +12,7 @@
     "QueryLogFormattedEscalationEvalScores",
     "QueryLogFormattedEvalScores",
     "QueryLogFormattedGuardrailEvalScores",
+    "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
 ]
 
@@ -34,6 +35,12 @@ class QueryLogFormattedGuardrailEvalScores(BaseModel):
     status: Literal["pass", "fail"]
 
 
+class QueryLogFormattedNonGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class QueryLogContext(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -68,6 +75,8 @@ class QueryLog(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
 
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 5214cff5..3b2277d5 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -11,6 +11,7 @@
     "FormattedEscalationEvalScores",
     "FormattedEvalScores",
     "FormattedGuardrailEvalScores",
+    "FormattedNonGuardrailEvalScores",
     "Context",
 ]
 
@@ -33,6 +34,12 @@ class FormattedGuardrailEvalScores(BaseModel):
     status: Literal["pass", "fail"]
 
 
+class FormattedNonGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class Context(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -67,6 +74,8 @@ class QueryLogRetrieveResponse(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     needs_review: bool
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index b2315aa4..7e40fb45 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -12,6 +12,7 @@
     "QueryLogFormattedEscalationEvalScores",
     "QueryLogFormattedEvalScores",
     "QueryLogFormattedGuardrailEvalScores",
+    "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
 ]
 
@@ -34,6 +35,12 @@ class QueryLogFormattedGuardrailEvalScores(BaseModel):
     status: Literal["pass", "fail"]
 
 
+class QueryLogFormattedNonGuardrailEvalScores(BaseModel):
+    score: float
+
+    status: Literal["pass", "fail"]
+
+
 class QueryLogContext(BaseModel):
     content: str
     """The actual content/text of the document."""
@@ -68,6 +75,8 @@ class QueryLog(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
 
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None
+
     is_bad_response: bool
 
     project_id: str

From bb81d205574a987b632b1cead05c716313aad25a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 17:17:40 +0000
Subject: [PATCH 188/227] feat(api): api update

---
 .stats.yml                                        |  2 +-
 requirements-dev.lock                             |  2 +-
 requirements.lock                                 |  2 +-
 src/codex/resources/projects/query_logs.py        | 12 ++++++------
 src/codex/types/project_validate_response.py      | 15 +++++++++++++--
 .../projects/query_log_list_by_group_params.py    |  2 +-
 .../projects/query_log_list_by_group_response.py  | 12 ++++++++++++
 .../projects/query_log_list_groups_params.py      |  2 +-
 .../projects/query_log_list_groups_response.py    | 12 ++++++++++++
 src/codex/types/projects/query_log_list_params.py |  2 +-
 .../types/projects/query_log_list_response.py     | 12 ++++++++++++
 .../types/projects/query_log_retrieve_response.py | 12 ++++++++++++
 .../remediation_list_resolved_logs_response.py    | 12 ++++++++++++
 13 files changed, 85 insertions(+), 14 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 08bc5f74..371ed3d3 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: 06bae836118b9c7ac2cf1b5e1a576296
+openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 11a27038..a84b5f44 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -56,7 +56,7 @@ httpx==0.28.1
     # via codex-sdk
     # via httpx-aiohttp
     # via respx
-httpx-aiohttp==0.1.6
+httpx-aiohttp==0.1.8
     # via codex-sdk
 idna==3.4
     # via anyio
diff --git a/requirements.lock b/requirements.lock
index 5eda9d9c..a0807d8f 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -43,7 +43,7 @@ httpcore==1.0.2
 httpx==0.28.1
     # via codex-sdk
     # via httpx-aiohttp
-httpx-aiohttp==0.1.6
+httpx-aiohttp==0.1.8
     # via codex-sdk
 idna==3.4
     # via anyio
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index e97243ec..148df9f6 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -99,7 +99,7 @@ def list(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
@@ -184,7 +184,7 @@ def list_by_group(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         remediation_ids: List[str] | NotGiven = NOT_GIVEN,
@@ -276,7 +276,7 @@ def list_groups(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
@@ -457,7 +457,7 @@ async def list(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
@@ -542,7 +542,7 @@ async def list_by_group(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         remediation_ids: List[str] | NotGiven = NOT_GIVEN,
@@ -634,7 +634,7 @@ async def list_groups(
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
         passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         primary_eval_issue: Optional[
-            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+            List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py
index db65f676..3b06db2d 100644
--- a/src/codex/types/project_validate_response.py
+++ b/src/codex/types/project_validate_response.py
@@ -1,10 +1,18 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import Dict, Optional
+from typing import Dict, List, Optional
 
 from .._models import BaseModel
 
-__all__ = ["ProjectValidateResponse", "EvalScores"]
+__all__ = ["ProjectValidateResponse", "DeterministicGuardrailsResults", "EvalScores"]
+
+
+class DeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
 
 
 class EvalScores(BaseModel):
@@ -22,6 +30,9 @@ class EvalScores(BaseModel):
 
 
 class ProjectValidateResponse(BaseModel):
+    deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None
+    """Results from deterministic guardrails applied to the response."""
+
     escalated_to_sme: bool
     """
     True if the question should be escalated to Codex for an SME to review, False
diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py
index b44970a1..90bd3867 100644
--- a/src/codex/types/projects/query_log_list_by_group_params.py
+++ b/src/codex/types/projects/query_log_list_by_group_params.py
@@ -40,7 +40,7 @@ class QueryLogListByGroupParams(TypedDict, total=False):
     """Filter by evals that passed"""
 
     primary_eval_issue: Optional[
-        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
     ]
     """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
 
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index eeb4b638..16850735 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -15,6 +15,7 @@
     "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores",
     "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
+    "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
 ]
 
 
@@ -59,6 +60,14 @@ class QueryLogsByGroupQueryLogContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
+class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
+
+
 class QueryLogsByGroupQueryLog(BaseModel):
     id: str
 
@@ -104,6 +113,9 @@ class QueryLogsByGroupQueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    deterministic_guardrails_results: Optional[Dict[str, QueryLogsByGroupQueryLogDeterministicGuardrailsResults]] = None
+    """Results of deterministic guardrails applied to the query"""
+
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
index 94d549f5..cd82d9a7 100644
--- a/src/codex/types/projects/query_log_list_groups_params.py
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -40,7 +40,7 @@ class QueryLogListGroupsParams(TypedDict, total=False):
     """Filter by evals that passed"""
 
     primary_eval_issue: Optional[
-        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
     ]
     """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 074dfaca..e77dd87e 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -14,6 +14,7 @@
     "QueryLogGroupFormattedGuardrailEvalScores",
     "QueryLogGroupFormattedNonGuardrailEvalScores",
     "QueryLogGroupContext",
+    "QueryLogGroupDeterministicGuardrailsResults",
 ]
 
 
@@ -58,6 +59,14 @@ class QueryLogGroupContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
+class QueryLogGroupDeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
+
+
 class QueryLogGroup(BaseModel):
     id: str
 
@@ -103,6 +112,9 @@ class QueryLogGroup(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    deterministic_guardrails_results: Optional[Dict[str, QueryLogGroupDeterministicGuardrailsResults]] = None
+    """Results of deterministic guardrails applied to the query"""
+
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py
index 0f72b241..5892d3c9 100644
--- a/src/codex/types/projects/query_log_list_params.py
+++ b/src/codex/types/projects/query_log_list_params.py
@@ -37,7 +37,7 @@ class QueryLogListParams(TypedDict, total=False):
     """Filter by evals that passed"""
 
     primary_eval_issue: Optional[
-        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]]
+        List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
     ]
     """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
 
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index 09a9067a..147478cf 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -14,6 +14,7 @@
     "QueryLogFormattedGuardrailEvalScores",
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
+    "QueryLogDeterministicGuardrailsResults",
 ]
 
 
@@ -58,6 +59,14 @@ class QueryLogContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
+class QueryLogDeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
+
+
 class QueryLog(BaseModel):
     id: str
 
@@ -97,6 +106,9 @@ class QueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None
+    """Results of deterministic guardrails applied to the query"""
+
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 3b2277d5..380bacba 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -13,6 +13,7 @@
     "FormattedGuardrailEvalScores",
     "FormattedNonGuardrailEvalScores",
     "Context",
+    "DeterministicGuardrailsResults",
 ]
 
 
@@ -57,6 +58,14 @@ class Context(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
+class DeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
+
+
 class QueryLogRetrieveResponse(BaseModel):
     id: str
 
@@ -100,6 +109,9 @@ class QueryLogRetrieveResponse(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None
+    """Results of deterministic guardrails applied to the query"""
+
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 7e40fb45..876e7cec 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -14,6 +14,7 @@
     "QueryLogFormattedGuardrailEvalScores",
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
+    "QueryLogDeterministicGuardrailsResults",
 ]
 
 
@@ -58,6 +59,14 @@ class QueryLogContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
+class QueryLogDeterministicGuardrailsResults(BaseModel):
+    guardrail_name: str
+
+    should_guardrail: bool
+
+    matches: Optional[List[str]] = None
+
+
 class QueryLog(BaseModel):
     id: str
 
@@ -97,6 +106,9 @@ class QueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
+    deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None
+    """Results of deterministic guardrails applied to the query"""
+
     escalated: Optional[bool] = None
     """If true, the question was escalated to Codex for an SME to review"""
 

From 29c6653719046b08ba309d881447aa5727a8bcc8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:36:29 +0000
Subject: [PATCH 189/227] feat(api): api update

---
 .stats.yml                                 |   2 +-
 src/codex/resources/projects/projects.py   |  42 ++--
 src/codex/types/project_validate_params.py | 279 ++++++++++++++++++++-
 tests/api_resources/test_projects.py       |  34 ++-
 4 files changed, 315 insertions(+), 42 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 371ed3d3..c8908e83 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
+openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09
 config_hash: 14b2643a0ec60cf326dfed00939644ff
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 1314b7be..dc01b112 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -488,17 +488,18 @@ def validate(
         project_id: str,
         *,
         context: str,
-        prompt: str,
         query: str,
-        response: str,
+        response: project_validate_params.Response,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
+        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -526,9 +527,8 @@ def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Optional message history to provide conversation context for the query. Used to
-              rewrite query into a self-contained version of itself. If not provided, the
-              query will be treated as self-contained.
+          messages: Message history to provide conversation context for the query. Messages contain
+              up to and including the latest user prompt to the LLM.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -615,8 +615,14 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
+              generated from the messages.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
+          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
+              used instead of the original query.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -643,7 +649,6 @@ def validate(
             body=maybe_transform(
                 {
                     "context": context,
-                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -652,7 +657,9 @@ def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
+                    "prompt": prompt,
                     "quality_preset": quality_preset,
+                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
@@ -1090,17 +1097,18 @@ async def validate(
         project_id: str,
         *,
         context: str,
-        prompt: str,
         query: str,
-        response: str,
+        response: project_validate_params.Response,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
+        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -1128,9 +1136,8 @@ async def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Optional message history to provide conversation context for the query. Used to
-              rewrite query into a self-contained version of itself. If not provided, the
-              query will be treated as self-contained.
+          messages: Message history to provide conversation context for the query. Messages contain
+              up to and including the latest user prompt to the LLM.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -1217,8 +1224,14 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
+              generated from the messages.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
+          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
+              used instead of the original query.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1245,7 +1258,6 @@ async def validate(
             body=await async_maybe_transform(
                 {
                     "context": context,
-                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -1254,7 +1266,9 @@ async def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
+                    "prompt": prompt,
                     "quality_preset": quality_preset,
+                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 8b38ebfa..0862cbc1 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import builtins
 from typing import Dict, List, Union, Iterable, Optional
 from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
 
@@ -9,6 +10,24 @@
 
 __all__ = [
     "ProjectValidateParams",
+    "Response",
+    "ResponseChatCompletion",
+    "ResponseChatCompletionChoice",
+    "ResponseChatCompletionChoiceMessage",
+    "ResponseChatCompletionChoiceMessageAnnotation",
+    "ResponseChatCompletionChoiceMessageAnnotationURLCitation",
+    "ResponseChatCompletionChoiceMessageAudio",
+    "ResponseChatCompletionChoiceMessageFunctionCall",
+    "ResponseChatCompletionChoiceMessageToolCall",
+    "ResponseChatCompletionChoiceMessageToolCallFunction",
+    "ResponseChatCompletionChoiceLogprobs",
+    "ResponseChatCompletionChoiceLogprobsContent",
+    "ResponseChatCompletionChoiceLogprobsContentTopLogprob",
+    "ResponseChatCompletionChoiceLogprobsRefusal",
+    "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob",
+    "ResponseChatCompletionUsage",
+    "ResponseChatCompletionUsageCompletionTokensDetails",
+    "ResponseChatCompletionUsagePromptTokensDetails",
     "Message",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
@@ -41,11 +60,9 @@
 class ProjectValidateParams(TypedDict, total=False):
     context: Required[str]
 
-    prompt: Required[str]
-
     query: Required[str]
 
-    response: Required[str]
+    response: Required[Response]
 
     use_llm_matching: bool
 
@@ -66,11 +83,10 @@ class ProjectValidateParams(TypedDict, total=False):
     If not provided, TLM will be used to generate scores.
     """
 
-    messages: Optional[Iterable[Message]]
-    """Optional message history to provide conversation context for the query.
+    messages: Iterable[Message]
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Messages contain up to and including the latest user prompt to the LLM.
     """
 
     options: Optional[Options]
@@ -161,9 +177,21 @@ class ProjectValidateParams(TypedDict, total=False):
         - criteria: Instructions specifying the evaluation criteria.
     """
 
+    prompt: Optional[str]
+    """The prompt to use for the TLM call.
+
+    If not provided, the prompt will be generated from the messages.
+    """
+
     quality_preset: Literal["best", "high", "medium", "low", "base"]
     """The quality preset to use for the TLM or Trustworthy RAG API."""
 
+    rewritten_question: Optional[str]
+    """
+    The re-written query if it was provided by the client to Codex from a user to be
+    used instead of the original query.
+    """
+
     task: Optional[str]
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
@@ -175,6 +203,243 @@ class ProjectValidateParams(TypedDict, total=False):
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
 
 
+class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False):
+    end_index: Required[int]
+
+    start_index: Required[int]
+
+    title: Required[str]
+
+    url: Required[str]
+
+
+ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False):
+    type: Required[Literal["url_citation"]]
+
+    url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation]
+
+
+ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False):
+    id: Required[str]
+
+    data: Required[str]
+
+    expires_at: Required[int]
+
+    transcript: Required[str]
+
+
+ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False):
+    id: Required[str]
+
+    function: Required[ResponseChatCompletionChoiceMessageToolCallFunction]
+
+    type: Required[Literal["function"]]
+
+
+ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False):
+    role: Required[Literal["assistant"]]
+
+    annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]]
+
+    audio: Optional[ResponseChatCompletionChoiceMessageAudio]
+
+    content: Optional[str]
+
+    function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall]
+
+    refusal: Optional[str]
+
+    tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]]
+
+
+ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False):
+    content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]]
+
+    refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]]
+
+
+ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionChoiceTyped(TypedDict, total=False):
+    finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]]
+
+    index: Required[int]
+
+    message: Required[ResponseChatCompletionChoiceMessage]
+
+    logprobs: Optional[ResponseChatCompletionChoiceLogprobs]
+
+
+ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False):
+    accepted_prediction_tokens: Optional[int]
+
+    audio_tokens: Optional[int]
+
+    reasoning_tokens: Optional[int]
+
+    rejected_prediction_tokens: Optional[int]
+
+
+ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[
+    ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False):
+    audio_tokens: Optional[int]
+
+    cached_tokens: Optional[int]
+
+
+ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[
+    ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionUsageTyped(TypedDict, total=False):
+    completion_tokens: Required[int]
+
+    prompt_tokens: Required[int]
+
+    total_tokens: Required[int]
+
+    completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails]
+
+    prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails]
+
+
+ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionTyped(TypedDict, total=False):
+    id: Required[str]
+
+    choices: Required[Iterable[ResponseChatCompletionChoice]]
+
+    created: Required[int]
+
+    model: Required[str]
+
+    object: Required[Literal["chat.completion"]]
+
+    service_tier: Optional[Literal["scale", "default"]]
+
+    system_fingerprint: Optional[str]
+
+    usage: Optional[ResponseChatCompletionUsage]
+
+
+ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]]
+
+Response: TypeAlias = Union[str, ResponseChatCompletion]
+
+
 class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 0764d9a1..9ecffa09 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -632,9 +632,8 @@ def test_method_validate(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -644,9 +643,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -670,7 +668,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
+            prompt="prompt",
             quality_preset="best",
+            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -685,9 +685,8 @@ def test_raw_response_validate(self, client: Codex) -> None:
         response = client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
 
         assert response.is_closed is True
@@ -701,9 +700,8 @@ def test_streaming_response_validate(self, client: Codex) -> None:
         with client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -720,9 +718,8 @@ def test_path_params_validate(self, client: Codex) -> None:
             client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                prompt="prompt",
                 query="query",
-                response="response",
+                response="string",
             )
 
 
@@ -1337,9 +1334,8 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -1349,9 +1345,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -1375,7 +1370,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
+            prompt="prompt",
             quality_preset="best",
+            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -1390,9 +1387,8 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
 
         assert response.is_closed is True
@@ -1406,9 +1402,8 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No
         async with async_client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1425,7 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
             await async_client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                prompt="prompt",
                 query="query",
-                response="response",
+                response="string",
             )

From 58f4575d7f26720829ceccb74f4bcb86ff7a30af Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:38:16 +0000
Subject: [PATCH 190/227] feat(api): remove entries routes

---
 .stats.yml                                 |   4 +-
 src/codex/pagination.py                    |  62 -----
 src/codex/resources/projects/projects.py   |  42 ++--
 src/codex/types/project_validate_params.py | 279 +--------------------
 tests/api_resources/test_projects.py       |  34 +--
 5 files changed, 43 insertions(+), 378 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index c8908e83..d7fdc0e2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09
-config_hash: 14b2643a0ec60cf326dfed00939644ff
+openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
+config_hash: e62d723b4a5c564dc05390753a876f31
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 1f590df5..8057f2e8 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -14,8 +14,6 @@
     "AsyncMyOffsetPageTopLevelArray",
     "SyncOffsetPageClusters",
     "AsyncOffsetPageClusters",
-    "SyncOffsetPageEntries",
-    "AsyncOffsetPageEntries",
 ]
 
 _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
@@ -143,63 +141,3 @@ def next_page_info(self) -> Optional[PageInfo]:
             return PageInfo(params={"offset": current_count})
 
         return None
-
-
-class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
-    entries: List[_T]
-    total_count: Optional[int] = None
-
-    @override
-    def _get_page_items(self) -> List[_T]:
-        entries = self.entries
-        if not entries:
-            return []
-        return entries
-
-    @override
-    def next_page_info(self) -> Optional[PageInfo]:
-        offset = self._options.params.get("offset") or 0
-        if not isinstance(offset, int):
-            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
-
-        length = len(self._get_page_items())
-        current_count = offset + length
-
-        total_count = self.total_count
-        if total_count is None:
-            return None
-
-        if current_count < total_count:
-            return PageInfo(params={"offset": current_count})
-
-        return None
-
-
-class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
-    entries: List[_T]
-    total_count: Optional[int] = None
-
-    @override
-    def _get_page_items(self) -> List[_T]:
-        entries = self.entries
-        if not entries:
-            return []
-        return entries
-
-    @override
-    def next_page_info(self) -> Optional[PageInfo]:
-        offset = self._options.params.get("offset") or 0
-        if not isinstance(offset, int):
-            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
-
-        length = len(self._get_page_items())
-        current_count = offset + length
-
-        total_count = self.total_count
-        if total_count is None:
-            return None
-
-        if current_count < total_count:
-            return PageInfo(params={"offset": current_count})
-
-        return None
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index dc01b112..1314b7be 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -488,18 +488,17 @@ def validate(
         project_id: str,
         *,
         context: str,
+        prompt: str,
         query: str,
-        response: project_validate_params.Response,
+        response: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
+        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
-        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
-        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -527,8 +526,9 @@ def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Message history to provide conversation context for the query. Messages contain
-              up to and including the latest user prompt to the LLM.
+          messages: Optional message history to provide conversation context for the query. Used to
+              rewrite query into a self-contained version of itself. If not provided, the
+              query will be treated as self-contained.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -615,14 +615,8 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
-              generated from the messages.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
-          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
-              used instead of the original query.
-
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -649,6 +643,7 @@ def validate(
             body=maybe_transform(
                 {
                     "context": context,
+                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -657,9 +652,7 @@ def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
-                    "prompt": prompt,
                     "quality_preset": quality_preset,
-                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
@@ -1097,18 +1090,17 @@ async def validate(
         project_id: str,
         *,
         context: str,
+        prompt: str,
         query: str,
-        response: project_validate_params.Response,
+        response: str,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
+        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
-        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
-        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -1136,8 +1128,9 @@ async def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Message history to provide conversation context for the query. Messages contain
-              up to and including the latest user prompt to the LLM.
+          messages: Optional message history to provide conversation context for the query. Used to
+              rewrite query into a self-contained version of itself. If not provided, the
+              query will be treated as self-contained.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -1224,14 +1217,8 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
-              generated from the messages.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
-          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
-              used instead of the original query.
-
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1258,6 +1245,7 @@ async def validate(
             body=await async_maybe_transform(
                 {
                     "context": context,
+                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -1266,9 +1254,7 @@ async def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
-                    "prompt": prompt,
                     "quality_preset": quality_preset,
-                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 0862cbc1..8b38ebfa 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-import builtins
 from typing import Dict, List, Union, Iterable, Optional
 from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
 
@@ -10,24 +9,6 @@
 
 __all__ = [
     "ProjectValidateParams",
-    "Response",
-    "ResponseChatCompletion",
-    "ResponseChatCompletionChoice",
-    "ResponseChatCompletionChoiceMessage",
-    "ResponseChatCompletionChoiceMessageAnnotation",
-    "ResponseChatCompletionChoiceMessageAnnotationURLCitation",
-    "ResponseChatCompletionChoiceMessageAudio",
-    "ResponseChatCompletionChoiceMessageFunctionCall",
-    "ResponseChatCompletionChoiceMessageToolCall",
-    "ResponseChatCompletionChoiceMessageToolCallFunction",
-    "ResponseChatCompletionChoiceLogprobs",
-    "ResponseChatCompletionChoiceLogprobsContent",
-    "ResponseChatCompletionChoiceLogprobsContentTopLogprob",
-    "ResponseChatCompletionChoiceLogprobsRefusal",
-    "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob",
-    "ResponseChatCompletionUsage",
-    "ResponseChatCompletionUsageCompletionTokensDetails",
-    "ResponseChatCompletionUsagePromptTokensDetails",
     "Message",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
@@ -60,9 +41,11 @@
 class ProjectValidateParams(TypedDict, total=False):
     context: Required[str]
 
+    prompt: Required[str]
+
     query: Required[str]
 
-    response: Required[Response]
+    response: Required[str]
 
     use_llm_matching: bool
 
@@ -83,10 +66,11 @@ class ProjectValidateParams(TypedDict, total=False):
     If not provided, TLM will be used to generate scores.
     """
 
-    messages: Iterable[Message]
-    """Message history to provide conversation context for the query.
+    messages: Optional[Iterable[Message]]
+    """Optional message history to provide conversation context for the query.
 
-    Messages contain up to and including the latest user prompt to the LLM.
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
     """
 
     options: Optional[Options]
@@ -177,21 +161,9 @@ class ProjectValidateParams(TypedDict, total=False):
         - criteria: Instructions specifying the evaluation criteria.
     """
 
-    prompt: Optional[str]
-    """The prompt to use for the TLM call.
-
-    If not provided, the prompt will be generated from the messages.
-    """
-
     quality_preset: Literal["best", "high", "medium", "low", "base"]
     """The quality preset to use for the TLM or Trustworthy RAG API."""
 
-    rewritten_question: Optional[str]
-    """
-    The re-written query if it was provided by the client to Codex from a user to be
-    used instead of the original query.
-    """
-
     task: Optional[str]
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
@@ -203,243 +175,6 @@ class ProjectValidateParams(TypedDict, total=False):
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
 
 
-class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False):
-    end_index: Required[int]
-
-    start_index: Required[int]
-
-    title: Required[str]
-
-    url: Required[str]
-
-
-ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False):
-    type: Required[Literal["url_citation"]]
-
-    url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation]
-
-
-ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False):
-    id: Required[str]
-
-    data: Required[str]
-
-    expires_at: Required[int]
-
-    transcript: Required[str]
-
-
-ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False):
-    arguments: Required[str]
-
-    name: Required[str]
-
-
-ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False):
-    arguments: Required[str]
-
-    name: Required[str]
-
-
-ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False):
-    id: Required[str]
-
-    function: Required[ResponseChatCompletionChoiceMessageToolCallFunction]
-
-    type: Required[Literal["function"]]
-
-
-ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[
-    ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False):
-    role: Required[Literal["assistant"]]
-
-    annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]]
-
-    audio: Optional[ResponseChatCompletionChoiceMessageAudio]
-
-    content: Optional[str]
-
-    function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall]
-
-    refusal: Optional[str]
-
-    tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]]
-
-
-ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]]
-
-
-class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False):
-    token: Required[str]
-
-    logprob: Required[float]
-
-    bytes: Optional[Iterable[int]]
-
-
-ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[
-    ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False):
-    token: Required[str]
-
-    logprob: Required[float]
-
-    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]]
-
-    bytes: Optional[Iterable[int]]
-
-
-ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[
-    ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False):
-    token: Required[str]
-
-    logprob: Required[float]
-
-    bytes: Optional[Iterable[int]]
-
-
-ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[
-    ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False):
-    token: Required[str]
-
-    logprob: Required[float]
-
-    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]]
-
-    bytes: Optional[Iterable[int]]
-
-
-ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[
-    ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False):
-    content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]]
-
-    refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]]
-
-
-ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]]
-
-
-class ResponseChatCompletionChoiceTyped(TypedDict, total=False):
-    finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]]
-
-    index: Required[int]
-
-    message: Required[ResponseChatCompletionChoiceMessage]
-
-    logprobs: Optional[ResponseChatCompletionChoiceLogprobs]
-
-
-ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]]
-
-
-class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False):
-    accepted_prediction_tokens: Optional[int]
-
-    audio_tokens: Optional[int]
-
-    reasoning_tokens: Optional[int]
-
-    rejected_prediction_tokens: Optional[int]
-
-
-ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[
-    ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False):
-    audio_tokens: Optional[int]
-
-    cached_tokens: Optional[int]
-
-
-ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[
-    ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object]
-]
-
-
-class ResponseChatCompletionUsageTyped(TypedDict, total=False):
-    completion_tokens: Required[int]
-
-    prompt_tokens: Required[int]
-
-    total_tokens: Required[int]
-
-    completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails]
-
-    prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails]
-
-
-ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]]
-
-
-class ResponseChatCompletionTyped(TypedDict, total=False):
-    id: Required[str]
-
-    choices: Required[Iterable[ResponseChatCompletionChoice]]
-
-    created: Required[int]
-
-    model: Required[str]
-
-    object: Required[Literal["chat.completion"]]
-
-    service_tier: Optional[Literal["scale", "default"]]
-
-    system_fingerprint: Optional[str]
-
-    usage: Optional[ResponseChatCompletionUsage]
-
-
-ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]]
-
-Response: TypeAlias = Union[str, ResponseChatCompletion]
-
-
 class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 9ecffa09..0764d9a1 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -632,8 +632,9 @@ def test_method_validate(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -643,8 +644,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -668,9 +670,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
-            prompt="prompt",
             quality_preset="best",
-            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -685,8 +685,9 @@ def test_raw_response_validate(self, client: Codex) -> None:
         response = client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         )
 
         assert response.is_closed is True
@@ -700,8 +701,9 @@ def test_streaming_response_validate(self, client: Codex) -> None:
         with client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -718,8 +720,9 @@ def test_path_params_validate(self, client: Codex) -> None:
             client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
+                prompt="prompt",
                 query="query",
-                response="string",
+                response="response",
             )
 
 
@@ -1334,8 +1337,9 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -1345,8 +1349,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -1370,9 +1375,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
-            prompt="prompt",
             quality_preset="best",
-            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -1387,8 +1390,9 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         )
 
         assert response.is_closed is True
@@ -1402,8 +1406,9 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No
         async with async_client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
+            prompt="prompt",
             query="query",
-            response="string",
+            response="response",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1420,6 +1425,7 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
             await async_client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
+                prompt="prompt",
                 query="query",
-                response="string",
+                response="response",
             )

From cb9e15a9954e2383e19ca53ba704091c3ed51c6e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:39:41 +0000
Subject: [PATCH 191/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index d7fdc0e2..429b3f80 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
-config_hash: e62d723b4a5c564dc05390753a876f31
+config_hash: f44faee9ff5bdf66e9e38fc69f6e4daa

From edbf9290a2a47d6975b16d84423e0eef7961b045 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:42:15 +0000
Subject: [PATCH 192/227] feat(api): define pagination schemes

---
 .stats.yml                                    |   2 +-
 README.md                                     |  69 ++++++++++
 api.md                                        |   4 +-
 src/codex/pagination.py                       | 124 ++++++++++++++++++
 src/codex/resources/projects/query_logs.py    |  21 +--
 src/codex/resources/projects/remediations.py  |  21 +--
 .../types/projects/query_log_list_response.py |  47 +++----
 .../projects/remediation_list_response.py     |  12 +-
 .../api_resources/projects/test_query_logs.py |  17 +--
 .../projects/test_remediations.py             |  17 +--
 10 files changed, 260 insertions(+), 74 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 429b3f80..ac24d567 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
-config_hash: f44faee9ff5bdf66e9e38fc69f6e4daa
+config_hash: 86582a50eb22b2866777cbd4d94f4e8d
diff --git a/README.md b/README.md
index 756f6d69..ae3be8a6 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,75 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
 
 Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
 
+## Pagination
+
+List methods in the Codex API are paginated.
+
+This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
+
+```python
+from codex import Codex
+
+client = Codex()
+
+all_query_logs = []
+# Automatically fetches more pages as needed.
+for query_log in client.projects.query_logs.list(
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+):
+    # Do something with query_log here
+    all_query_logs.append(query_log)
+print(all_query_logs)
+```
+
+Or, asynchronously:
+
+```python
+import asyncio
+from codex import AsyncCodex
+
+client = AsyncCodex()
+
+
+async def main() -> None:
+    all_query_logs = []
+    # Iterate through items across all pages, issuing requests as needed.
+    async for query_log in client.projects.query_logs.list(
+        project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+    ):
+        all_query_logs.append(query_log)
+    print(all_query_logs)
+
+
+asyncio.run(main())
+```
+
+Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
+
+```python
+first_page = await client.projects.query_logs.list(
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+if first_page.has_next_page():
+    print(f"will fetch next page using these details: {first_page.next_page_info()}")
+    next_page = await first_page.get_next_page()
+    print(f"number of items we just fetched: {len(next_page.query_logs)}")
+
+# Remove `await` for non-async usage.
+```
+
+Or just work directly with the returned data:
+
+```python
+first_page = await client.projects.query_logs.list(
+    project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+for query_log in first_page.query_logs:
+    print(query_log.id)
+
+# Remove `await` for non-async usage.
+```
+
 ## Nested params
 
 Nested parameters are dictionaries, typed using `TypedDict`, for example:
diff --git a/api.md b/api.md
index ba4dfd63..edc03cd3 100644
--- a/api.md
+++ b/api.md
@@ -212,7 +212,7 @@ from codex.types.projects import (
 Methods:
 
 - <code title="get /api/projects/{project_id}/query_logs/{query_log_id}">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">retrieve</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_retrieve_response.py">QueryLogRetrieveResponse</a></code>
-- <code title="get /api/projects/{project_id}/query_logs/">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_response.py">QueryLogListResponse</a></code>
+- <code title="get /api/projects/{project_id}/query_logs/">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_response.py">SyncOffsetPageQueryLogs[QueryLogListResponse]</a></code>
 - <code title="get /api/projects/{project_id}/query_logs/logs_by_group">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_by_group</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_by_group_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_by_group_response.py">QueryLogListByGroupResponse</a></code>
 - <code title="get /api/projects/{project_id}/query_logs/groups">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_groups</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_groups_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_groups_response.py">QueryLogListGroupsResponse</a></code>
 - <code title="post /api/projects/{project_id}/query_logs/{query_log_id}/start_remediation">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">start_remediation</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_start_remediation_response.py">QueryLogStartRemediationResponse</a></code>
@@ -240,7 +240,7 @@ Methods:
 
 - <code title="post /api/projects/{project_id}/remediations/">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/remediation_create_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_create_response.py">RemediationCreateResponse</a></code>
 - <code title="get /api/projects/{project_id}/remediations/{remediation_id}">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">retrieve</a>(remediation_id, \*, project_id) -> <a href="./src/codex/types/projects/remediation_retrieve_response.py">RemediationRetrieveResponse</a></code>
-- <code title="get /api/projects/{project_id}/remediations/">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/remediation_list_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_list_response.py">RemediationListResponse</a></code>
+- <code title="get /api/projects/{project_id}/remediations/">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/remediation_list_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_list_response.py">SyncOffsetPageRemediations[RemediationListResponse]</a></code>
 - <code title="delete /api/projects/{project_id}/remediations/{remediation_id}">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">delete</a>(remediation_id, \*, project_id) -> None</code>
 - <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/edit_answer">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">edit_answer</a>(remediation_id, \*, project_id, \*\*<a href="src/codex/types/projects/remediation_edit_answer_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_edit_answer_response.py">RemediationEditAnswerResponse</a></code>
 - <code title="patch /api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer">client.projects.remediations.<a href="./src/codex/resources/projects/remediations.py">edit_draft_answer</a>(remediation_id, \*, project_id, \*\*<a href="src/codex/types/projects/remediation_edit_draft_answer_params.py">params</a>) -> <a href="./src/codex/types/projects/remediation_edit_draft_answer_response.py">RemediationEditDraftAnswerResponse</a></code>
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 8057f2e8..89ed0cfb 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -14,6 +14,10 @@
     "AsyncMyOffsetPageTopLevelArray",
     "SyncOffsetPageClusters",
     "AsyncOffsetPageClusters",
+    "SyncOffsetPageQueryLogs",
+    "AsyncOffsetPageQueryLogs",
+    "SyncOffsetPageRemediations",
+    "AsyncOffsetPageRemediations",
 ]
 
 _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
@@ -141,3 +145,123 @@ def next_page_info(self) -> Optional[PageInfo]:
             return PageInfo(params={"offset": current_count})
 
         return None
+
+
+class SyncOffsetPageQueryLogs(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    query_logs: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_logs = self.query_logs
+        if not query_logs:
+            return []
+        return query_logs
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class AsyncOffsetPageQueryLogs(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    query_logs: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_logs = self.query_logs
+        if not query_logs:
+            return []
+        return query_logs
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class SyncOffsetPageRemediations(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    remediations: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        remediations = self.remediations
+        if not remediations:
+            return []
+        return remediations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class AsyncOffsetPageRemediations(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    remediations: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        remediations = self.remediations
+        if not remediations:
+            return []
+        return remediations
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 148df9f6..24535d4b 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -18,7 +18,8 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ..._base_client import make_request_options
+from ...pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs
+from ..._base_client import AsyncPaginator, make_request_options
 from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params
 from ...types.projects.query_log_list_response import QueryLogListResponse
 from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse
@@ -110,7 +111,7 @@ def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> QueryLogListResponse:
+    ) -> SyncOffsetPageQueryLogs[QueryLogListResponse]:
         """
         List query logs by project ID.
 
@@ -141,8 +142,9 @@ def list(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/query_logs/",
+            page=SyncOffsetPageQueryLogs[QueryLogListResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
@@ -166,7 +168,7 @@ def list(
                     query_log_list_params.QueryLogListParams,
                 ),
             ),
-            cast_to=QueryLogListResponse,
+            model=QueryLogListResponse,
         )
 
     def list_by_group(
@@ -443,7 +445,7 @@ async def retrieve(
             cast_to=QueryLogRetrieveResponse,
         )
 
-    async def list(
+    def list(
         self,
         project_id: str,
         *,
@@ -468,7 +470,7 @@ async def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> QueryLogListResponse:
+    ) -> AsyncPaginator[QueryLogListResponse, AsyncOffsetPageQueryLogs[QueryLogListResponse]]:
         """
         List query logs by project ID.
 
@@ -499,14 +501,15 @@ async def list(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return await self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/query_logs/",
+            page=AsyncOffsetPageQueryLogs[QueryLogListResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
                 extra_body=extra_body,
                 timeout=timeout,
-                query=await async_maybe_transform(
+                query=maybe_transform(
                     {
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
@@ -524,7 +527,7 @@ async def list(
                     query_log_list_params.QueryLogListParams,
                 ),
             ),
-            cast_to=QueryLogListResponse,
+            model=QueryLogListResponse,
         )
 
     async def list_by_group(
diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py
index 65015a14..ea0168c7 100644
--- a/src/codex/resources/projects/remediations.py
+++ b/src/codex/resources/projects/remediations.py
@@ -18,7 +18,8 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ..._base_client import make_request_options
+from ...pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations
+from ..._base_client import AsyncPaginator, make_request_options
 from ...types.projects import (
     remediation_list_params,
     remediation_create_params,
@@ -159,7 +160,7 @@ def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> RemediationListResponse:
+    ) -> SyncOffsetPageRemediations[RemediationListResponse]:
         """
         List remediations by project ID.
 
@@ -186,8 +187,9 @@ def list(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/remediations/",
+            page=SyncOffsetPageRemediations[RemediationListResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
@@ -209,7 +211,7 @@ def list(
                     remediation_list_params.RemediationListParams,
                 ),
             ),
-            cast_to=RemediationListResponse,
+            model=RemediationListResponse,
         )
 
     def delete(
@@ -608,7 +610,7 @@ async def retrieve(
             cast_to=RemediationRetrieveResponse,
         )
 
-    async def list(
+    def list(
         self,
         project_id: str,
         *,
@@ -628,7 +630,7 @@ async def list(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> RemediationListResponse:
+    ) -> AsyncPaginator[RemediationListResponse, AsyncOffsetPageRemediations[RemediationListResponse]]:
         """
         List remediations by project ID.
 
@@ -655,14 +657,15 @@ async def list(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return await self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/remediations/",
+            page=AsyncOffsetPageRemediations[RemediationListResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
                 extra_body=extra_body,
                 timeout=timeout,
-                query=await async_maybe_transform(
+                query=maybe_transform(
                     {
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
@@ -678,7 +681,7 @@ async def list(
                     remediation_list_params.RemediationListParams,
                 ),
             ),
-            cast_to=RemediationListResponse,
+            model=RemediationListResponse,
         )
 
     async def delete(
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index 147478cf..ccdeb038 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -8,41 +8,40 @@
 
 __all__ = [
     "QueryLogListResponse",
-    "QueryLog",
-    "QueryLogFormattedEscalationEvalScores",
-    "QueryLogFormattedEvalScores",
-    "QueryLogFormattedGuardrailEvalScores",
-    "QueryLogFormattedNonGuardrailEvalScores",
-    "QueryLogContext",
-    "QueryLogDeterministicGuardrailsResults",
+    "FormattedEscalationEvalScores",
+    "FormattedEvalScores",
+    "FormattedGuardrailEvalScores",
+    "FormattedNonGuardrailEvalScores",
+    "Context",
+    "DeterministicGuardrailsResults",
 ]
 
 
-class QueryLogFormattedEscalationEvalScores(BaseModel):
+class FormattedEscalationEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogFormattedEvalScores(BaseModel):
+class FormattedEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogFormattedGuardrailEvalScores(BaseModel):
+class FormattedGuardrailEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogFormattedNonGuardrailEvalScores(BaseModel):
+class FormattedNonGuardrailEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogContext(BaseModel):
+class Context(BaseModel):
     content: str
     """The actual content/text of the document."""
 
@@ -59,7 +58,7 @@ class QueryLogContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
-class QueryLogDeterministicGuardrailsResults(BaseModel):
+class DeterministicGuardrailsResults(BaseModel):
     guardrail_name: str
 
     should_guardrail: bool
@@ -67,14 +66,14 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLog(BaseModel):
+class QueryLogListResponse(BaseModel):
     id: str
 
     created_at: datetime
 
-    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None
 
-    formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None
+    formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -82,9 +81,9 @@ class QueryLog(BaseModel):
     eval_scores is None.
     """
 
-    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
+    formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
-    formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
 
     is_bad_response: bool
 
@@ -97,7 +96,7 @@ class QueryLog(BaseModel):
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
-    context: Optional[List[QueryLogContext]] = None
+    context: Optional[List[Context]] = None
     """RAG context used for the query"""
 
     custom_metadata: Optional[object] = None
@@ -106,7 +105,7 @@ class QueryLog(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
-    deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None
+    deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None
     """Results of deterministic guardrails applied to the query"""
 
     escalated: Optional[bool] = None
@@ -138,11 +137,3 @@ class QueryLog(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
-
-
-class QueryLogListResponse(BaseModel):
-    custom_metadata_columns: List[str]
-
-    query_logs: List[QueryLog]
-
-    total_count: int
diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py
index 9d5cda86..83410539 100644
--- a/src/codex/types/projects/remediation_list_response.py
+++ b/src/codex/types/projects/remediation_list_response.py
@@ -1,15 +1,15 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
-from typing import List, Optional
+from typing import Optional
 from datetime import datetime
 from typing_extensions import Literal
 
 from ..._models import BaseModel
 
-__all__ = ["RemediationListResponse", "Remediation"]
+__all__ = ["RemediationListResponse"]
 
 
-class Remediation(BaseModel):
+class RemediationListResponse(BaseModel):
     id: str
 
     answered_at: Optional[datetime] = None
@@ -35,9 +35,3 @@ class Remediation(BaseModel):
     answer: Optional[str] = None
 
     draft_answer: Optional[str] = None
-
-
-class RemediationListResponse(BaseModel):
-    remediations: List[Remediation]
-
-    total_count: int
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index 68d78ced..be867e22 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -10,6 +10,7 @@
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
+from codex.pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs
 from codex.types.projects import (
     QueryLogListResponse,
     QueryLogRetrieveResponse,
@@ -82,7 +83,7 @@ def test_method_list(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -102,7 +103,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             sort="created_at",
             was_cache_hit=True,
         )
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -114,7 +115,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         query_log = response.parse()
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -126,7 +127,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             query_log = response.parse()
-            assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+            assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -381,7 +382,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -401,7 +402,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             sort="created_at",
             was_cache_hit=True,
         )
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -413,7 +414,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         query_log = await response.parse()
-        assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -425,7 +426,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             query_log = await response.parse()
-            assert_matches_type(QueryLogListResponse, query_log, path=["response"])
+            assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py
index c75a255c..947850f7 100644
--- a/tests/api_resources/projects/test_remediations.py
+++ b/tests/api_resources/projects/test_remediations.py
@@ -10,6 +10,7 @@
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
+from codex.pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations
 from codex.types.projects import (
     RemediationListResponse,
     RemediationPauseResponse,
@@ -144,7 +145,7 @@ def test_method_list(self, client: Codex) -> None:
         remediation = client.projects.remediations.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -162,7 +163,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             sort="created_at",
             status=["ACTIVE"],
         )
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -174,7 +175,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         remediation = response.parse()
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -186,7 +187,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             remediation = response.parse()
-            assert_matches_type(RemediationListResponse, remediation, path=["response"])
+            assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -745,7 +746,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.list(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -763,7 +764,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             sort="created_at",
             status=["ACTIVE"],
         )
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -775,7 +776,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         remediation = await response.parse()
-        assert_matches_type(RemediationListResponse, remediation, path=["response"])
+        assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -787,7 +788,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             remediation = await response.parse()
-            assert_matches_type(RemediationListResponse, remediation, path=["response"])
+            assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 

From 4f67d97b8700ec1d8fe800f2a943b314d2011bcc Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:43:46 +0000
Subject: [PATCH 193/227] feat(api): more pagination schemes

---
 .stats.yml                                    |   2 +-
 api.md                                        |   2 +-
 src/codex/pagination.py                       | 124 ++++++++++++++++++
 src/codex/resources/projects/query_logs.py    |  25 ++--
 .../query_log_list_groups_response.py         |  47 +++----
 .../api_resources/projects/test_query_logs.py |  23 ++--
 6 files changed, 175 insertions(+), 48 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index ac24d567..01dc7b27 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
-config_hash: 86582a50eb22b2866777cbd4d94f4e8d
+config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/api.md b/api.md
index edc03cd3..0c027913 100644
--- a/api.md
+++ b/api.md
@@ -214,7 +214,7 @@ Methods:
 - <code title="get /api/projects/{project_id}/query_logs/{query_log_id}">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">retrieve</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_retrieve_response.py">QueryLogRetrieveResponse</a></code>
 - <code title="get /api/projects/{project_id}/query_logs/">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_response.py">SyncOffsetPageQueryLogs[QueryLogListResponse]</a></code>
 - <code title="get /api/projects/{project_id}/query_logs/logs_by_group">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_by_group</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_by_group_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_by_group_response.py">QueryLogListByGroupResponse</a></code>
-- <code title="get /api/projects/{project_id}/query_logs/groups">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_groups</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_groups_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_groups_response.py">QueryLogListGroupsResponse</a></code>
+- <code title="get /api/projects/{project_id}/query_logs/groups">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">list_groups</a>(project_id, \*\*<a href="src/codex/types/projects/query_log_list_groups_params.py">params</a>) -> <a href="./src/codex/types/projects/query_log_list_groups_response.py">SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]</a></code>
 - <code title="post /api/projects/{project_id}/query_logs/{query_log_id}/start_remediation">client.projects.query_logs.<a href="./src/codex/resources/projects/query_logs.py">start_remediation</a>(query_log_id, \*, project_id) -> <a href="./src/codex/types/projects/query_log_start_remediation_response.py">QueryLogStartRemediationResponse</a></code>
 
 ## Remediations
diff --git a/src/codex/pagination.py b/src/codex/pagination.py
index 89ed0cfb..36af0f63 100644
--- a/src/codex/pagination.py
+++ b/src/codex/pagination.py
@@ -18,6 +18,10 @@
     "AsyncOffsetPageQueryLogs",
     "SyncOffsetPageRemediations",
     "AsyncOffsetPageRemediations",
+    "SyncOffsetPageQueryLogGroups",
+    "AsyncOffsetPageQueryLogGroups",
+    "SyncOffsetPageQueryLogsByGroup",
+    "AsyncOffsetPageQueryLogsByGroup",
 ]
 
 _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
@@ -265,3 +269,123 @@ def next_page_info(self) -> Optional[PageInfo]:
             return PageInfo(params={"offset": current_count})
 
         return None
+
+
+class SyncOffsetPageQueryLogGroups(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    query_log_groups: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_log_groups = self.query_log_groups
+        if not query_log_groups:
+            return []
+        return query_log_groups
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class AsyncOffsetPageQueryLogGroups(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    query_log_groups: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_log_groups = self.query_log_groups
+        if not query_log_groups:
+            return []
+        return query_log_groups
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class SyncOffsetPageQueryLogsByGroup(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+    query_logs_by_group: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_logs_by_group = self.query_logs_by_group
+        if not query_logs_by_group:
+            return []
+        return query_logs_by_group
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
+
+
+class AsyncOffsetPageQueryLogsByGroup(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+    query_logs_by_group: List[_T]
+    total_count: Optional[int] = None
+
+    @override
+    def _get_page_items(self) -> List[_T]:
+        query_logs_by_group = self.query_logs_by_group
+        if not query_logs_by_group:
+            return []
+        return query_logs_by_group
+
+    @override
+    def next_page_info(self) -> Optional[PageInfo]:
+        offset = self._options.params.get("offset") or 0
+        if not isinstance(offset, int):
+            raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
+
+        length = len(self._get_page_items())
+        current_count = offset + length
+
+        total_count = self.total_count
+        if total_count is None:
+            return None
+
+        if current_count < total_count:
+            return PageInfo(params={"offset": current_count})
+
+        return None
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 24535d4b..9ccecbed 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -18,7 +18,12 @@
     async_to_raw_response_wrapper,
     async_to_streamed_response_wrapper,
 )
-from ...pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs
+from ...pagination import (
+    SyncOffsetPageQueryLogs,
+    AsyncOffsetPageQueryLogs,
+    SyncOffsetPageQueryLogGroups,
+    AsyncOffsetPageQueryLogGroups,
+)
 from ..._base_client import AsyncPaginator, make_request_options
 from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params
 from ...types.projects.query_log_list_response import QueryLogListResponse
@@ -290,7 +295,7 @@ def list_groups(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> QueryLogListGroupsResponse:
+    ) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]:
         """
         List query log groups by project ID.
 
@@ -323,8 +328,9 @@ def list_groups(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/query_logs/groups",
+            page=SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
@@ -349,7 +355,7 @@ def list_groups(
                     query_log_list_groups_params.QueryLogListGroupsParams,
                 ),
             ),
-            cast_to=QueryLogListGroupsResponse,
+            model=QueryLogListGroupsResponse,
         )
 
     def start_remediation(
@@ -622,7 +628,7 @@ async def list_by_group(
             cast_to=QueryLogListByGroupResponse,
         )
 
-    async def list_groups(
+    def list_groups(
         self,
         project_id: str,
         *,
@@ -649,7 +655,7 @@ async def list_groups(
         extra_query: Query | None = None,
         extra_body: Body | None = None,
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> QueryLogListGroupsResponse:
+    ) -> AsyncPaginator[QueryLogListGroupsResponse, AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]]:
         """
         List query log groups by project ID.
 
@@ -682,14 +688,15 @@ async def list_groups(
         """
         if not project_id:
             raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return await self._get(
+        return self._get_api_list(
             f"/api/projects/{project_id}/query_logs/groups",
+            page=AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse],
             options=make_request_options(
                 extra_headers=extra_headers,
                 extra_query=extra_query,
                 extra_body=extra_body,
                 timeout=timeout,
-                query=await async_maybe_transform(
+                query=maybe_transform(
                     {
                         "created_at_end": created_at_end,
                         "created_at_start": created_at_start,
@@ -708,7 +715,7 @@ async def list_groups(
                     query_log_list_groups_params.QueryLogListGroupsParams,
                 ),
             ),
-            cast_to=QueryLogListGroupsResponse,
+            model=QueryLogListGroupsResponse,
         )
 
     async def start_remediation(
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index e77dd87e..5d9222b1 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -8,41 +8,40 @@
 
 __all__ = [
     "QueryLogListGroupsResponse",
-    "QueryLogGroup",
-    "QueryLogGroupFormattedEscalationEvalScores",
-    "QueryLogGroupFormattedEvalScores",
-    "QueryLogGroupFormattedGuardrailEvalScores",
-    "QueryLogGroupFormattedNonGuardrailEvalScores",
-    "QueryLogGroupContext",
-    "QueryLogGroupDeterministicGuardrailsResults",
+    "FormattedEscalationEvalScores",
+    "FormattedEvalScores",
+    "FormattedGuardrailEvalScores",
+    "FormattedNonGuardrailEvalScores",
+    "Context",
+    "DeterministicGuardrailsResults",
 ]
 
 
-class QueryLogGroupFormattedEscalationEvalScores(BaseModel):
+class FormattedEscalationEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogGroupFormattedEvalScores(BaseModel):
+class FormattedEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogGroupFormattedGuardrailEvalScores(BaseModel):
+class FormattedGuardrailEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogGroupFormattedNonGuardrailEvalScores(BaseModel):
+class FormattedNonGuardrailEvalScores(BaseModel):
     score: float
 
     status: Literal["pass", "fail"]
 
 
-class QueryLogGroupContext(BaseModel):
+class Context(BaseModel):
     content: str
     """The actual content/text of the document."""
 
@@ -59,7 +58,7 @@ class QueryLogGroupContext(BaseModel):
     """Title or heading of the document. Useful for display and context."""
 
 
-class QueryLogGroupDeterministicGuardrailsResults(BaseModel):
+class DeterministicGuardrailsResults(BaseModel):
     guardrail_name: str
 
     should_guardrail: bool
@@ -67,14 +66,14 @@ class QueryLogGroupDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLogGroup(BaseModel):
+class QueryLogListGroupsResponse(BaseModel):
     id: str
 
     created_at: datetime
 
-    formatted_escalation_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEscalationEvalScores]] = None
+    formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None
 
-    formatted_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEvalScores]] = None
+    formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None
     """Format evaluation scores for frontend display with pass/fail status.
 
     Returns: Dictionary mapping eval keys to their formatted representation: {
@@ -82,9 +81,9 @@ class QueryLogGroup(BaseModel):
     eval_scores is None.
     """
 
-    formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None
+    formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
-    formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedNonGuardrailEvalScores]] = None
+    formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
 
     is_bad_response: bool
 
@@ -103,7 +102,7 @@ class QueryLogGroup(BaseModel):
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
-    context: Optional[List[QueryLogGroupContext]] = None
+    context: Optional[List[Context]] = None
     """RAG context used for the query"""
 
     custom_metadata: Optional[object] = None
@@ -112,7 +111,7 @@ class QueryLogGroup(BaseModel):
     custom_metadata_keys: Optional[List[str]] = None
     """Keys of the custom metadata"""
 
-    deterministic_guardrails_results: Optional[Dict[str, QueryLogGroupDeterministicGuardrailsResults]] = None
+    deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None
     """Results of deterministic guardrails applied to the query"""
 
     escalated: Optional[bool] = None
@@ -144,11 +143,3 @@ class QueryLogGroup(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
-
-
-class QueryLogListGroupsResponse(BaseModel):
-    custom_metadata_columns: List[str]
-
-    query_log_groups: List[QueryLogGroup]
-
-    total_count: int
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index be867e22..cd4cd7d2 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -10,7 +10,12 @@
 from codex import Codex, AsyncCodex
 from tests.utils import assert_matches_type
 from codex._utils import parse_datetime
-from codex.pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs
+from codex.pagination import (
+    SyncOffsetPageQueryLogs,
+    AsyncOffsetPageQueryLogs,
+    SyncOffsetPageQueryLogGroups,
+    AsyncOffsetPageQueryLogGroups,
+)
 from codex.types.projects import (
     QueryLogListResponse,
     QueryLogRetrieveResponse,
@@ -209,7 +214,7 @@ def test_method_list_groups(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list_groups(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -230,7 +235,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None:
             sort="created_at",
             was_cache_hit=True,
         )
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -242,7 +247,7 @@ def test_raw_response_list_groups(self, client: Codex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         query_log = response.parse()
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -254,7 +259,7 @@ def test_streaming_response_list_groups(self, client: Codex) -> None:
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             query_log = response.parse()
-            assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+            assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 
@@ -508,7 +513,7 @@ async def test_method_list_groups(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list_groups(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -529,7 +534,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex
             sort="created_at",
             was_cache_hit=True,
         )
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -541,7 +546,7 @@ async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None:
         assert response.is_closed is True
         assert response.http_request.headers.get("X-Stainless-Lang") == "python"
         query_log = await response.parse()
-        assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+        assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
     @pytest.mark.skip()
     @parametrize
@@ -553,7 +558,7 @@ async def test_streaming_response_list_groups(self, async_client: AsyncCodex) ->
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
 
             query_log = await response.parse()
-            assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"])
+            assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
         assert cast(Any, response.is_closed) is True
 

From 6c5beb23983efa332a143d7f1d5f644b9808a1b4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:47:39 +0000
Subject: [PATCH 194/227] feat(api): api update

---
 .stats.yml                                 |   2 +-
 src/codex/resources/projects/projects.py   |  42 ++--
 src/codex/types/project_validate_params.py | 279 ++++++++++++++++++++-
 tests/api_resources/test_projects.py       |  34 ++-
 4 files changed, 315 insertions(+), 42 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 01dc7b27..889336e3 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: f17890d85522687a4c68702da9ad2efb
+openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 1314b7be..dc01b112 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -488,17 +488,18 @@ def validate(
         project_id: str,
         *,
         context: str,
-        prompt: str,
         query: str,
-        response: str,
+        response: project_validate_params.Response,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
+        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -526,9 +527,8 @@ def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Optional message history to provide conversation context for the query. Used to
-              rewrite query into a self-contained version of itself. If not provided, the
-              query will be treated as self-contained.
+          messages: Message history to provide conversation context for the query. Messages contain
+              up to and including the latest user prompt to the LLM.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -615,8 +615,14 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
+              generated from the messages.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
+          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
+              used instead of the original query.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -643,7 +649,6 @@ def validate(
             body=maybe_transform(
                 {
                     "context": context,
-                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -652,7 +657,9 @@ def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
+                    "prompt": prompt,
                     "quality_preset": quality_preset,
+                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
@@ -1090,17 +1097,18 @@ async def validate(
         project_id: str,
         *,
         context: str,
-        prompt: str,
         query: str,
-        response: str,
+        response: project_validate_params.Response,
         use_llm_matching: bool | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
         eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
-        messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN,
+        messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN,
         options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN,
+        prompt: Optional[str] | NotGiven = NOT_GIVEN,
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
+        rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -1128,9 +1136,8 @@ async def validate(
           eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will
               be used to generate scores.
 
-          messages: Optional message history to provide conversation context for the query. Used to
-              rewrite query into a self-contained version of itself. If not provided, the
-              query will be treated as self-contained.
+          messages: Message history to provide conversation context for the query. Messages contain
+              up to and including the latest user prompt to the LLM.
 
           options: Typed dict of advanced configuration options for the Trustworthy Language Model.
               Many of these configurations are determined by the quality preset selected
@@ -1217,8 +1224,14 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+          prompt: The prompt to use for the TLM call. If not provided, the prompt will be
+              generated from the messages.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
+          rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
+              used instead of the original query.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1245,7 +1258,6 @@ async def validate(
             body=await async_maybe_transform(
                 {
                     "context": context,
-                    "prompt": prompt,
                     "query": query,
                     "response": response,
                     "constrain_outputs": constrain_outputs,
@@ -1254,7 +1266,9 @@ async def validate(
                     "eval_scores": eval_scores,
                     "messages": messages,
                     "options": options,
+                    "prompt": prompt,
                     "quality_preset": quality_preset,
+                    "rewritten_question": rewritten_question,
                     "task": task,
                 },
                 project_validate_params.ProjectValidateParams,
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 8b38ebfa..0862cbc1 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import builtins
 from typing import Dict, List, Union, Iterable, Optional
 from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
 
@@ -9,6 +10,24 @@
 
 __all__ = [
     "ProjectValidateParams",
+    "Response",
+    "ResponseChatCompletion",
+    "ResponseChatCompletionChoice",
+    "ResponseChatCompletionChoiceMessage",
+    "ResponseChatCompletionChoiceMessageAnnotation",
+    "ResponseChatCompletionChoiceMessageAnnotationURLCitation",
+    "ResponseChatCompletionChoiceMessageAudio",
+    "ResponseChatCompletionChoiceMessageFunctionCall",
+    "ResponseChatCompletionChoiceMessageToolCall",
+    "ResponseChatCompletionChoiceMessageToolCallFunction",
+    "ResponseChatCompletionChoiceLogprobs",
+    "ResponseChatCompletionChoiceLogprobsContent",
+    "ResponseChatCompletionChoiceLogprobsContentTopLogprob",
+    "ResponseChatCompletionChoiceLogprobsRefusal",
+    "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob",
+    "ResponseChatCompletionUsage",
+    "ResponseChatCompletionUsageCompletionTokensDetails",
+    "ResponseChatCompletionUsagePromptTokensDetails",
     "Message",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
@@ -41,11 +60,9 @@
 class ProjectValidateParams(TypedDict, total=False):
     context: Required[str]
 
-    prompt: Required[str]
-
     query: Required[str]
 
-    response: Required[str]
+    response: Required[Response]
 
     use_llm_matching: bool
 
@@ -66,11 +83,10 @@ class ProjectValidateParams(TypedDict, total=False):
     If not provided, TLM will be used to generate scores.
     """
 
-    messages: Optional[Iterable[Message]]
-    """Optional message history to provide conversation context for the query.
+    messages: Iterable[Message]
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Messages contain up to and including the latest user prompt to the LLM.
     """
 
     options: Optional[Options]
@@ -161,9 +177,21 @@ class ProjectValidateParams(TypedDict, total=False):
         - criteria: Instructions specifying the evaluation criteria.
     """
 
+    prompt: Optional[str]
+    """The prompt to use for the TLM call.
+
+    If not provided, the prompt will be generated from the messages.
+    """
+
     quality_preset: Literal["best", "high", "medium", "low", "base"]
     """The quality preset to use for the TLM or Trustworthy RAG API."""
 
+    rewritten_question: Optional[str]
+    """
+    The re-written query if it was provided by the client to Codex from a user to be
+    used instead of the original query.
+    """
+
     task: Optional[str]
 
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
@@ -175,6 +203,243 @@ class ProjectValidateParams(TypedDict, total=False):
     x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")]
 
 
+class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False):
+    end_index: Required[int]
+
+    start_index: Required[int]
+
+    title: Required[str]
+
+    url: Required[str]
+
+
+ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False):
+    type: Required[Literal["url_citation"]]
+
+    url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation]
+
+
+ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False):
+    id: Required[str]
+
+    data: Required[str]
+
+    expires_at: Required[int]
+
+    transcript: Required[str]
+
+
+ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False):
+    id: Required[str]
+
+    function: Required[ResponseChatCompletionChoiceMessageToolCallFunction]
+
+    type: Required[Literal["function"]]
+
+
+ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[
+    ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False):
+    role: Required[Literal["assistant"]]
+
+    annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]]
+
+    audio: Optional[ResponseChatCompletionChoiceMessageAudio]
+
+    content: Optional[str]
+
+    function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall]
+
+    refusal: Optional[str]
+
+    tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]]
+
+
+ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False):
+    token: Required[str]
+
+    logprob: Required[float]
+
+    top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]]
+
+    bytes: Optional[Iterable[int]]
+
+
+ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[
+    ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False):
+    content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]]
+
+    refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]]
+
+
+ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionChoiceTyped(TypedDict, total=False):
+    finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]]
+
+    index: Required[int]
+
+    message: Required[ResponseChatCompletionChoiceMessage]
+
+    logprobs: Optional[ResponseChatCompletionChoiceLogprobs]
+
+
+ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False):
+    accepted_prediction_tokens: Optional[int]
+
+    audio_tokens: Optional[int]
+
+    reasoning_tokens: Optional[int]
+
+    rejected_prediction_tokens: Optional[int]
+
+
+ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[
+    ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False):
+    audio_tokens: Optional[int]
+
+    cached_tokens: Optional[int]
+
+
+ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[
+    ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object]
+]
+
+
+class ResponseChatCompletionUsageTyped(TypedDict, total=False):
+    completion_tokens: Required[int]
+
+    prompt_tokens: Required[int]
+
+    total_tokens: Required[int]
+
+    completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails]
+
+    prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails]
+
+
+ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]]
+
+
+class ResponseChatCompletionTyped(TypedDict, total=False):
+    id: Required[str]
+
+    choices: Required[Iterable[ResponseChatCompletionChoice]]
+
+    created: Required[int]
+
+    model: Required[str]
+
+    object: Required[Literal["chat.completion"]]
+
+    service_tier: Optional[Literal["scale", "default"]]
+
+    system_fingerprint: Optional[str]
+
+    usage: Optional[ResponseChatCompletionUsage]
+
+
+ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]]
+
+Response: TypeAlias = Union[str, ResponseChatCompletion]
+
+
 class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 0764d9a1..9ecffa09 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -632,9 +632,8 @@ def test_method_validate(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -644,9 +643,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -670,7 +668,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
+            prompt="prompt",
             quality_preset="best",
+            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -685,9 +685,8 @@ def test_raw_response_validate(self, client: Codex) -> None:
         response = client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
 
         assert response.is_closed is True
@@ -701,9 +700,8 @@ def test_streaming_response_validate(self, client: Codex) -> None:
         with client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -720,9 +718,8 @@ def test_path_params_validate(self, client: Codex) -> None:
             client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                prompt="prompt",
                 query="query",
-                response="response",
+                response="string",
             )
 
 
@@ -1337,9 +1334,8 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
@@ -1349,9 +1345,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
             custom_eval_thresholds={"foo": 0},
@@ -1375,7 +1370,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
             },
+            prompt="prompt",
             quality_preset="best",
+            rewritten_question="rewritten_question",
             task="task",
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
@@ -1390,9 +1387,8 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         )
 
         assert response.is_closed is True
@@ -1406,9 +1402,8 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No
         async with async_client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            prompt="prompt",
             query="query",
-            response="response",
+            response="string",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1425,7 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
             await async_client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                prompt="prompt",
                 query="query",
-                response="response",
+                response="string",
             )

From 52449ccfbc4314ed3cb38ba9f27db91619a9dad3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:52:12 +0000
Subject: [PATCH 195/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index aa848759..1c0bb885 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.22"
+  ".": "0.1.0-alpha.23"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index a2a3d7c2..15dc82f3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.22"
+version = "0.1.0-alpha.23"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index a88a1c39..18f2dcb7 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.22"  # x-release-please-version
+__version__ = "0.1.0-alpha.23"  # x-release-please-version

From 55e790a28b0eb17015fdbc6f3bd900ecc0cef609 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 9 Jul 2025 02:26:12 +0000
Subject: [PATCH 196/227] chore(internal): bump pinned h11 dep

---
 requirements-dev.lock | 4 ++--
 requirements.lock     | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/requirements-dev.lock b/requirements-dev.lock
index a84b5f44..7999ff41 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -48,9 +48,9 @@ filelock==3.12.4
 frozenlist==1.6.2
     # via aiohttp
     # via aiosignal
-h11==0.14.0
+h11==0.16.0
     # via httpcore
-httpcore==1.0.2
+httpcore==1.0.9
     # via httpx
 httpx==0.28.1
     # via codex-sdk
diff --git a/requirements.lock b/requirements.lock
index a0807d8f..bde9133e 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -36,9 +36,9 @@ exceptiongroup==1.2.2
 frozenlist==1.6.2
     # via aiohttp
     # via aiosignal
-h11==0.14.0
+h11==0.16.0
     # via httpcore
-httpcore==1.0.2
+httpcore==1.0.9
     # via httpx
 httpx==0.28.1
     # via codex-sdk

From 7a023f652f75390b63f4f473fb700ba8067c5ca3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 9 Jul 2025 02:45:16 +0000
Subject: [PATCH 197/227] chore(package): mark python 3.13 as supported

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index 15dc82f3..06612be9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,7 @@ classifiers = [
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
   "Programming Language :: Python :: 3.12",
+  "Programming Language :: Python :: 3.13",
   "Operating System :: OS Independent",
   "Operating System :: POSIX",
   "Operating System :: MacOS",

From 65839923960a3a26d4e51ec973845603aba02e53 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Jul 2025 02:40:45 +0000
Subject: [PATCH 198/227] fix(parsing): correctly handle nested discriminated
 unions

---
 src/codex/_models.py | 13 ++++++++-----
 tests/test_models.py | 45 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index 4f214980..528d5680 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -2,9 +2,10 @@
 
 import os
 import inspect
-from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast
+from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
 from datetime import date, datetime
 from typing_extensions import (
+    List,
     Unpack,
     Literal,
     ClassVar,
@@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
     if type_ is None:
         raise RuntimeError(f"Unexpected field type is None for {key}")
 
-    return construct_type(value=value, type_=type_)
+    return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
 
 
 def is_basemodel(type_: type) -> bool:
@@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
     return cast(_T, construct_type(value=value, type_=type_))
 
 
-def construct_type(*, value: object, type_: object) -> object:
+def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
     """Loose coercion to the expected type with construction of nested values.
 
     If the given value does not match the expected type then it is returned as-is.
@@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object:
         type_ = type_.__value__  # type: ignore[unreachable]
 
     # unwrap `Annotated[T, ...]` -> `T`
-    if is_annotated_type(type_):
-        meta: tuple[Any, ...] = get_args(type_)[1:]
+    if metadata is not None:
+        meta: tuple[Any, ...] = tuple(metadata)
+    elif is_annotated_type(type_):
+        meta = get_args(type_)[1:]
         type_ = extract_type_arg(type_, 0)
     else:
         meta = tuple()
diff --git a/tests/test_models.py b/tests/test_models.py
index c96609ce..3452a61b 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -889,3 +889,48 @@ class ModelB(BaseModel):
     )
 
     assert isinstance(m, ModelB)
+
+
+def test_nested_discriminated_union() -> None:
+    class InnerType1(BaseModel):
+        type: Literal["type_1"]
+
+    class InnerModel(BaseModel):
+        inner_value: str
+
+    class InnerType2(BaseModel):
+        type: Literal["type_2"]
+        some_inner_model: InnerModel
+
+    class Type1(BaseModel):
+        base_type: Literal["base_type_1"]
+        value: Annotated[
+            Union[
+                InnerType1,
+                InnerType2,
+            ],
+            PropertyInfo(discriminator="type"),
+        ]
+
+    class Type2(BaseModel):
+        base_type: Literal["base_type_2"]
+
+    T = Annotated[
+        Union[
+            Type1,
+            Type2,
+        ],
+        PropertyInfo(discriminator="base_type"),
+    ]
+
+    model = construct_type(
+        type_=T,
+        value={
+            "base_type": "base_type_1",
+            "value": {
+                "type": "type_2",
+            },
+        },
+    )
+    assert isinstance(model, Type1)
+    assert isinstance(model.value, InnerType2)

From bc55fc805669aa42da078a62893177524a302d76 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 11 Jul 2025 02:58:34 +0000
Subject: [PATCH 199/227] chore(readme): fix version rendering on pypi

---
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index ae3be8a6..7d673351 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 # Codex SDK API library
 
-[![PyPI version](<https://img.shields.io/pypi/v/codex-sdk.svg?label=pypi%20(stable)>)](https://pypi.org/project/codex-sdk/)
+<!-- prettier-ignore -->
+[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/codex-sdk/)
 
 The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+
 application. The library includes type definitions for all request params and response fields,

From 4bf59bd427760308cac0eee75161ea2dc5d3b32c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Jul 2025 02:05:53 +0000
Subject: [PATCH 200/227] fix(client): don't send Content-Type header on GET
 requests

---
 pyproject.toml            |  2 +-
 src/codex/_base_client.py | 11 +++++++++--
 tests/test_client.py      |  4 ++--
 3 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 06612be9..0e315f1b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ Homepage = "https://github.com/cleanlab/codex-python"
 Repository = "https://github.com/cleanlab/codex-python"
 
 [project.optional-dependencies]
-aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
+aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
 
 [tool.rye]
 managed = true
diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 1eca89e0..6da89f6c 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -529,6 +529,15 @@ def _build_request(
             # work around https://github.com/encode/httpx/discussions/2880
             kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
 
+        is_body_allowed = options.method.lower() != "get"
+
+        if is_body_allowed:
+            kwargs["json"] = json_data if is_given(json_data) else None
+            kwargs["files"] = files
+        else:
+            headers.pop("Content-Type", None)
+            kwargs.pop("data", None)
+
         # TODO: report this error to httpx
         return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
             headers=headers,
@@ -540,8 +549,6 @@ def _build_request(
             # so that passing a `TypedDict` doesn't cause an error.
             # https://github.com/microsoft/pyright/issues/3526#event-6715453066
             params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
-            json=json_data if is_given(json_data) else None,
-            files=files,
             **kwargs,
         )
 
diff --git a/tests/test_client.py b/tests/test_client.py
index c012f4d5..24749152 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -427,7 +427,7 @@ def test_request_extra_query(self) -> None:
     def test_multipart_repeating_array(self, client: Codex) -> None:
         request = client._build_request(
             FinalRequestOptions.construct(
-                method="get",
+                method="post",
                 url="/foo",
                 headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
                 json_data={"array": ["foo", "bar"]},
@@ -1211,7 +1211,7 @@ def test_request_extra_query(self) -> None:
     def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None:
         request = async_client._build_request(
             FinalRequestOptions.construct(
-                method="get",
+                method="post",
                 url="/foo",
                 headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
                 json_data={"array": ["foo", "bar"]},

From 9e7a5a711d157d7443c41747a8f16df22e9aba59 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 14 Jul 2025 18:17:47 +0000
Subject: [PATCH 201/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 src/codex/types/project_validate_params.py    | 102 ++++----
 src/codex/types/project_validate_response.py  |   4 +-
 .../query_log_list_by_group_response.py       | 239 +++++++++++++++++-
 .../query_log_list_groups_response.py         | 231 ++++++++++++++++-
 .../types/projects/query_log_list_response.py | 231 ++++++++++++++++-
 .../projects/query_log_retrieve_response.py   | 231 ++++++++++++++++-
 ...remediation_list_resolved_logs_response.py | 237 ++++++++++++++++-
 .../projects/test_remediations.py             |  20 +-
 tests/api_resources/test_projects.py          |  20 +-
 10 files changed, 1238 insertions(+), 79 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 889336e3..20ee8270 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09
+openapi_spec_hash: b3a1a58600b52a20671bef2b25f5dbc4
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 0862cbc1..7b85d061 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -33,23 +33,23 @@
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
     "MessageChatCompletionSystemMessageParam",
     "MessageChatCompletionSystemMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParam",
-    "MessageChatCompletionUserMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam",
-    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "MessageChatCompletionUserMessageParamContentUnionMember1File",
-    "MessageChatCompletionUserMessageParamContentUnionMember1FileFile",
-    "MessageChatCompletionAssistantMessageParam",
-    "MessageChatCompletionAssistantMessageParamAudio",
-    "MessageChatCompletionAssistantMessageParamContentUnionMember1",
-    "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam",
-    "MessageChatCompletionAssistantMessageParamFunctionCall",
-    "MessageChatCompletionAssistantMessageParamToolCall",
-    "MessageChatCompletionAssistantMessageParamToolCallFunction",
+    "MessageChatCompletionUserMessageParamInput",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile",
+    "MessageChatCompletionAssistantMessageParamInput",
+    "MessageChatCompletionAssistantMessageParamInputAudio",
+    "MessageChatCompletionAssistantMessageParamInputContentUnionMember1",
+    "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "MessageChatCompletionAssistantMessageParamInputFunctionCall",
+    "MessageChatCompletionAssistantMessageParamInputToolCall",
+    "MessageChatCompletionAssistantMessageParamInputToolCallFunction",
     "MessageChatCompletionToolMessageParam",
     "MessageChatCompletionToolMessageParamContentUnionMember1",
     "MessageChatCompletionFunctionMessageParam",
@@ -468,7 +468,7 @@ class MessageChatCompletionSystemMessageParam(TypedDict, total=False):
     name: str
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam(
     TypedDict, total=False
 ):
     text: Required[str]
@@ -476,7 +476,7 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
     TypedDict, total=False
 ):
     url: Required[str]
@@ -484,17 +484,17 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont
     detail: Literal["auto", "low", "high"]
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam(
+class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam(
     TypedDict, total=False
 ):
     image_url: Required[
-        MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL
+        MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL
     ]
 
     type: Required[Literal["image_url"]]
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
     TypedDict, total=False
 ):
     data: Required[str]
@@ -502,17 +502,17 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont
     format: Required[Literal["wav", "mp3"]]
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam(
+class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam(
     TypedDict, total=False
 ):
     input_audio: Required[
-        MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+        MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
     ]
 
     type: Required[Literal["input_audio"]]
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False):
+class MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile(TypedDict, total=False):
     file_data: str
 
     file_id: str
@@ -520,33 +520,33 @@ class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict
     filename: str
 
 
-class MessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False):
-    file: Required[MessageChatCompletionUserMessageParamContentUnionMember1FileFile]
+class MessageChatCompletionUserMessageParamInputContentUnionMember1File(TypedDict, total=False):
+    file: Required[MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile]
 
     type: Required[Literal["file"]]
 
 
-MessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam,
-    MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam,
-    MessageChatCompletionUserMessageParamContentUnionMember1File,
+MessageChatCompletionUserMessageParamInputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam,
+    MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    MessageChatCompletionUserMessageParamInputContentUnionMember1File,
 ]
 
 
-class MessageChatCompletionUserMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamContentUnionMember1]]]
+class MessageChatCompletionUserMessageParamInput(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamInputContentUnionMember1]]]
 
     role: Required[Literal["user"]]
 
     name: str
 
 
-class MessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False):
     id: Required[str]
 
 
-class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam(
+class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam(
     TypedDict, total=False
 ):
     text: Required[str]
@@ -554,7 +554,7 @@ class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletio
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam(
+class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam(
     TypedDict, total=False
 ):
     refusal: Required[str]
@@ -562,46 +562,46 @@ class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletio
     type: Required[Literal["refusal"]]
 
 
-MessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam,
+MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam,
 ]
 
 
-class MessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False):
     arguments: Required[str]
 
     name: Required[str]
 
 
-class MessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False):
     arguments: Required[str]
 
     name: Required[str]
 
 
-class MessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False):
     id: Required[str]
 
-    function: Required[MessageChatCompletionAssistantMessageParamToolCallFunction]
+    function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction]
 
     type: Required[Literal["function"]]
 
 
-class MessageChatCompletionAssistantMessageParam(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False):
     role: Required[Literal["assistant"]]
 
-    audio: Optional[MessageChatCompletionAssistantMessageParamAudio]
+    audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio]
 
-    content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamContentUnionMember1], None]
+    content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None]
 
-    function_call: Optional[MessageChatCompletionAssistantMessageParamFunctionCall]
+    function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall]
 
     name: str
 
     refusal: Optional[str]
 
-    tool_calls: Iterable[MessageChatCompletionAssistantMessageParamToolCall]
+    tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall]
 
 
 class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
@@ -629,8 +629,8 @@ class MessageChatCompletionFunctionMessageParam(TypedDict, total=False):
 Message: TypeAlias = Union[
     MessageChatCompletionDeveloperMessageParam,
     MessageChatCompletionSystemMessageParam,
-    MessageChatCompletionUserMessageParam,
-    MessageChatCompletionAssistantMessageParam,
+    MessageChatCompletionUserMessageParamInput,
+    MessageChatCompletionAssistantMessageParamInput,
     MessageChatCompletionToolMessageParam,
     MessageChatCompletionFunctionMessageParam,
 ]
diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py
index 3b06db2d..44883119 100644
--- a/src/codex/types/project_validate_response.py
+++ b/src/codex/types/project_validate_response.py
@@ -48,8 +48,8 @@ class ProjectValidateResponse(BaseModel):
 
     expert_answer: Optional[str] = None
     """
-    Alternate SME-provided answer from Codex if the response was flagged as bad and
-    an answer was found in the Codex Project, or None otherwise.
+    Alternate SME-provided answer from Codex if a relevant answer was found in the
+    Codex Project, or None otherwise.
     """
 
     is_bad_response: bool
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index 16850735..ccd2d5ec 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -2,7 +2,7 @@
 
 from typing import Dict, List, Union, Optional
 from datetime import datetime
-from typing_extensions import Literal
+from typing_extensions import Literal, TypeAlias
 
 from ..._models import BaseModel
 
@@ -16,6 +16,31 @@
     "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
     "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
+    "QueryLogsByGroupQueryLogMessage",
+    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall",
+    "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction",
+    "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam",
 ]
 
 
@@ -68,6 +93,200 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+
+    role: Literal["developer"]
+
+    name: Optional[str] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
+
+    role: Literal["system"]
+
+    name: Optional[str] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    BaseModel
+):
+    url: str
+
+    detail: Optional[Literal["auto", "low", "high"]] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(
+    BaseModel
+):
+    image_url: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL
+
+    type: Literal["image_url"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    BaseModel
+):
+    data: str
+
+    format: Literal["wav", "mp3"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(
+    BaseModel
+):
+    input_audio: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+
+    type: Literal["input_audio"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel):
+    file_data: Optional[str] = None
+
+    file_id: Optional[str] = None
+
+    filename: Optional[str] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel):
+    file: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile
+
+    type: Literal["file"]
+
+
+QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File,
+]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1]]
+
+    role: Literal["user"]
+
+    name: Optional[str] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
+
+
+QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[
+        str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None
+    ] = None
+
+    function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
+
+    name: Optional[str] = None
+
+    refusal: Optional[str] = None
+
+    tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
+
+    role: Literal["tool"]
+
+    tool_call_id: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
+
+    name: str
+
+    role: Literal["function"]
+
+
+QueryLogsByGroupQueryLogMessage: TypeAlias = Union[
+    QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput,
+    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput,
+    QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryLogsByGroupQueryLog(BaseModel):
     id: str
 
@@ -85,10 +304,14 @@ class QueryLogsByGroupQueryLog(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None
 
+    formatted_messages: Optional[str] = None
+
     formatted_non_guardrail_eval_scores: Optional[
         Dict[str, QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores]
     ] = None
 
+    formatted_original_question: Optional[str] = None
+
     is_bad_response: bool
 
     needs_review: bool
@@ -140,6 +363,20 @@ class QueryLogsByGroupQueryLog(BaseModel):
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
+    messages: Optional[List[QueryLogsByGroupQueryLogMessage]] = None
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str] = None
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 5d9222b1..9adb4227 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -2,7 +2,7 @@
 
 from typing import Dict, List, Union, Optional
 from datetime import datetime
-from typing_extensions import Literal
+from typing_extensions import Literal, TypeAlias
 
 from ..._models import BaseModel
 
@@ -14,6 +14,31 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "Message",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionAssistantMessageParamOutput",
+    "MessageChatCompletionAssistantMessageParamOutputAudio",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "MessageChatCompletionAssistantMessageParamOutputFunctionCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
+    "MessageChatCompletionToolMessageParam",
+    "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionFunctionMessageParam",
 ]
 
 
@@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+
+    role: Literal["developer"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+
+    role: Literal["system"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    BaseModel
+):
+    url: str
+
+    detail: Optional[Literal["auto", "low", "high"]] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel):
+    image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL
+
+    type: Literal["image_url"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    BaseModel
+):
+    data: str
+
+    format: Literal["wav", "mp3"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel):
+    input_audio: (
+        MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+    )
+
+    type: Literal["input_audio"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel):
+    file_data: Optional[str] = None
+
+    file_id: Optional[str] = None
+
+    filename: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel):
+    file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile
+
+    type: Literal["file"]
+
+
+MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1File,
+]
+
+
+class MessageChatCompletionUserMessageParamOutput(BaseModel):
+    content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]]
+
+    role: Literal["user"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
+
+    name: Optional[str] = None
+
+    refusal: Optional[str] = None
+
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
+
+    role: Literal["tool"]
+
+    tool_call_id: str
+
+
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
+
+    name: str
+
+    role: Literal["function"]
+
+
+Message: TypeAlias = Union[
+    MessageChatCompletionDeveloperMessageParam,
+    MessageChatCompletionSystemMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionAssistantMessageParamOutput,
+    MessageChatCompletionToolMessageParam,
+    MessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryLogListGroupsResponse(BaseModel):
     id: str
 
@@ -83,8 +294,12 @@ class QueryLogListGroupsResponse(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
+    formatted_messages: Optional[str] = None
+
     formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
 
+    formatted_original_question: Optional[str] = None
+
     is_bad_response: bool
 
     needs_review: bool
@@ -138,6 +353,20 @@ class QueryLogListGroupsResponse(BaseModel):
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
+    messages: Optional[List[Message]] = None
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str] = None
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index ccdeb038..f6fbba15 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -2,7 +2,7 @@
 
 from typing import Dict, List, Union, Optional
 from datetime import datetime
-from typing_extensions import Literal
+from typing_extensions import Literal, TypeAlias
 
 from ..._models import BaseModel
 
@@ -14,6 +14,31 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "Message",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionAssistantMessageParamOutput",
+    "MessageChatCompletionAssistantMessageParamOutputAudio",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "MessageChatCompletionAssistantMessageParamOutputFunctionCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
+    "MessageChatCompletionToolMessageParam",
+    "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionFunctionMessageParam",
 ]
 
 
@@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+
+    role: Literal["developer"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+
+    role: Literal["system"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    BaseModel
+):
+    url: str
+
+    detail: Optional[Literal["auto", "low", "high"]] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel):
+    image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL
+
+    type: Literal["image_url"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    BaseModel
+):
+    data: str
+
+    format: Literal["wav", "mp3"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel):
+    input_audio: (
+        MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+    )
+
+    type: Literal["input_audio"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel):
+    file_data: Optional[str] = None
+
+    file_id: Optional[str] = None
+
+    filename: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel):
+    file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile
+
+    type: Literal["file"]
+
+
+MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1File,
+]
+
+
+class MessageChatCompletionUserMessageParamOutput(BaseModel):
+    content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]]
+
+    role: Literal["user"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
+
+    name: Optional[str] = None
+
+    refusal: Optional[str] = None
+
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
+
+    role: Literal["tool"]
+
+    tool_call_id: str
+
+
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
+
+    name: str
+
+    role: Literal["function"]
+
+
+Message: TypeAlias = Union[
+    MessageChatCompletionDeveloperMessageParam,
+    MessageChatCompletionSystemMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionAssistantMessageParamOutput,
+    MessageChatCompletionToolMessageParam,
+    MessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryLogListResponse(BaseModel):
     id: str
 
@@ -83,8 +294,12 @@ class QueryLogListResponse(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
+    formatted_messages: Optional[str] = None
+
     formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
 
+    formatted_original_question: Optional[str] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -132,6 +347,20 @@ class QueryLogListResponse(BaseModel):
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
+    messages: Optional[List[Message]] = None
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str] = None
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 380bacba..784009cf 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -2,7 +2,7 @@
 
 from typing import Dict, List, Union, Optional
 from datetime import datetime
-from typing_extensions import Literal
+from typing_extensions import Literal, TypeAlias
 
 from ..._models import BaseModel
 
@@ -14,6 +14,31 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "Message",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionAssistantMessageParamOutput",
+    "MessageChatCompletionAssistantMessageParamOutputAudio",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "MessageChatCompletionAssistantMessageParamOutputFunctionCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCall",
+    "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
+    "MessageChatCompletionToolMessageParam",
+    "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionFunctionMessageParam",
 ]
 
 
@@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+
+    role: Literal["developer"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+
+    role: Literal["system"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    BaseModel
+):
+    url: str
+
+    detail: Optional[Literal["auto", "low", "high"]] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel):
+    image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL
+
+    type: Literal["image_url"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    BaseModel
+):
+    data: str
+
+    format: Literal["wav", "mp3"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel):
+    input_audio: (
+        MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+    )
+
+    type: Literal["input_audio"]
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel):
+    file_data: Optional[str] = None
+
+    file_id: Optional[str] = None
+
+    filename: Optional[str] = None
+
+
+class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel):
+    file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile
+
+    type: Literal["file"]
+
+
+MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    MessageChatCompletionUserMessageParamOutputContentUnionMember1File,
+]
+
+
+class MessageChatCompletionUserMessageParamOutput(BaseModel):
+    content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]]
+
+    role: Literal["user"]
+
+    name: Optional[str] = None
+
+
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
+
+    name: Optional[str] = None
+
+    refusal: Optional[str] = None
+
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
+
+    role: Literal["tool"]
+
+    tool_call_id: str
+
+
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
+
+    name: str
+
+    role: Literal["function"]
+
+
+Message: TypeAlias = Union[
+    MessageChatCompletionDeveloperMessageParam,
+    MessageChatCompletionSystemMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionAssistantMessageParamOutput,
+    MessageChatCompletionToolMessageParam,
+    MessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryLogRetrieveResponse(BaseModel):
     id: str
 
@@ -83,8 +294,12 @@ class QueryLogRetrieveResponse(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None
 
+    formatted_messages: Optional[str] = None
+
     formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None
 
+    formatted_original_question: Optional[str] = None
+
     is_bad_response: bool
 
     needs_review: bool
@@ -136,6 +351,20 @@ class QueryLogRetrieveResponse(BaseModel):
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
+    messages: Optional[List[Message]] = None
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str] = None
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 876e7cec..1e0154cd 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -2,7 +2,7 @@
 
 from typing import Dict, List, Union, Optional
 from datetime import datetime
-from typing_extensions import Literal
+from typing_extensions import Literal, TypeAlias
 
 from ..._models import BaseModel
 
@@ -15,6 +15,31 @@
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
     "QueryLogDeterministicGuardrailsResults",
+    "QueryLogMessage",
+    "QueryLogMessageChatCompletionDeveloperMessageParam",
+    "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogMessageChatCompletionSystemMessageParam",
+    "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
+    "QueryLogMessageChatCompletionUserMessageParamOutput",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutput",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall",
+    "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction",
+    "QueryLogMessageChatCompletionToolMessageParam",
+    "QueryLogMessageChatCompletionToolMessageParamContentUnionMember1",
+    "QueryLogMessageChatCompletionFunctionMessageParam",
 ]
 
 
@@ -67,6 +92,198 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+
+    role: Literal["developer"]
+
+    name: Optional[str] = None
+
+
+class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogMessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
+
+    role: Literal["system"]
+
+    name: Optional[str] = None
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL(
+    BaseModel
+):
+    url: str
+
+    detail: Optional[Literal["auto", "low", "high"]] = None
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(
+    BaseModel
+):
+    image_url: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL
+
+    type: Literal["image_url"]
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio(
+    BaseModel
+):
+    data: str
+
+    format: Literal["wav", "mp3"]
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(
+    BaseModel
+):
+    input_audio: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio
+
+    type: Literal["input_audio"]
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel):
+    file_data: Optional[str] = None
+
+    file_id: Optional[str] = None
+
+    filename: Optional[str] = None
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel):
+    file: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile
+
+    type: Literal["file"]
+
+
+QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam,
+    QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam,
+    QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File,
+]
+
+
+class QueryLogMessageChatCompletionUserMessageParamOutput(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1]]
+
+    role: Literal["user"]
+
+    name: Optional[str] = None
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
+
+
+QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
+
+    name: Optional[str] = None
+
+    refusal: Optional[str] = None
+
+    tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+    text: str
+
+    type: Literal["text"]
+
+
+class QueryLogMessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
+
+    role: Literal["tool"]
+
+    tool_call_id: str
+
+
+class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
+
+    name: str
+
+    role: Literal["function"]
+
+
+QueryLogMessage: TypeAlias = Union[
+    QueryLogMessageChatCompletionDeveloperMessageParam,
+    QueryLogMessageChatCompletionSystemMessageParam,
+    QueryLogMessageChatCompletionUserMessageParamOutput,
+    QueryLogMessageChatCompletionAssistantMessageParamOutput,
+    QueryLogMessageChatCompletionToolMessageParam,
+    QueryLogMessageChatCompletionFunctionMessageParam,
+]
+
+
 class QueryLog(BaseModel):
     id: str
 
@@ -84,8 +301,12 @@ class QueryLog(BaseModel):
 
     formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None
 
+    formatted_messages: Optional[str] = None
+
     formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None
 
+    formatted_original_question: Optional[str] = None
+
     is_bad_response: bool
 
     project_id: str
@@ -133,6 +354,20 @@ class QueryLog(BaseModel):
     guardrailed: Optional[bool] = None
     """If true, the response was guardrailed"""
 
+    messages: Optional[List[QueryLogMessage]] = None
+    """Optional message history to provide conversation context for the query.
+
+    Used to rewrite query into a self-contained version of itself. If not provided,
+    the query will be treated as self-contained.
+    """
+
+    original_question: Optional[str] = None
+    """The original question that was asked before any rewriting or processing.
+
+    For all non-conversational RAG, original_question should be the same as the
+    final question seen in Codex.
+    """
+
     primary_eval_issue: Optional[str] = None
     """Primary issue identified in evaluation"""
 
diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py
index 947850f7..5866dbe7 100644
--- a/tests/api_resources/projects/test_remediations.py
+++ b/tests/api_resources/projects/test_remediations.py
@@ -35,7 +35,7 @@ class TestRemediations:
     def test_method_create(self, client: Codex) -> None:
         remediation = client.projects.remediations.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
@@ -44,7 +44,7 @@ def test_method_create(self, client: Codex) -> None:
     def test_method_create_with_all_params(self, client: Codex) -> None:
         remediation = client.projects.remediations.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
             answer="answer",
             draft_answer="draft_answer",
         )
@@ -55,7 +55,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         )
 
         assert response.is_closed is True
@@ -68,7 +68,7 @@ def test_raw_response_create(self, client: Codex) -> None:
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -84,7 +84,7 @@ def test_path_params_create(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             client.projects.remediations.with_raw_response.create(
                 project_id="",
-                question="question",
+                question="x",
             )
 
     @pytest.mark.skip()
@@ -636,7 +636,7 @@ class TestAsyncRemediations:
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
@@ -645,7 +645,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
             answer="answer",
             draft_answer="draft_answer",
         )
@@ -656,7 +656,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         )
 
         assert response.is_closed is True
@@ -669,7 +669,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.create(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            question="question",
+            question="x",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -685,7 +685,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             await async_client.projects.remediations.with_raw_response.create(
                 project_id="",
-                question="question",
+                question="x",
             )
 
     @pytest.mark.skip()
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 9ecffa09..4507741b 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -632,7 +632,7 @@ def test_method_validate(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
@@ -643,7 +643,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
         project = client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
@@ -685,7 +685,7 @@ def test_raw_response_validate(self, client: Codex) -> None:
         response = client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         )
 
@@ -700,7 +700,7 @@ def test_streaming_response_validate(self, client: Codex) -> None:
         with client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         ) as response:
             assert not response.is_closed
@@ -718,7 +718,7 @@ def test_path_params_validate(self, client: Codex) -> None:
             client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                query="query",
+                query="x",
                 response="string",
             )
 
@@ -1334,7 +1334,7 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
@@ -1345,7 +1345,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
         project = await async_client.projects.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
             use_llm_matching=True,
             constrain_outputs=["string"],
@@ -1387,7 +1387,7 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         )
 
@@ -1402,7 +1402,7 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No
         async with async_client.projects.with_streaming_response.validate(
             project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             context="context",
-            query="query",
+            query="x",
             response="string",
         ) as response:
             assert not response.is_closed
@@ -1420,6 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
             await async_client.projects.with_raw_response.validate(
                 project_id="",
                 context="context",
-                query="query",
+                query="x",
                 response="string",
             )

From 2abd377954bca4d7ee69cd5ea20e7f121a1e56a6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Jul 2025 22:17:46 +0000
Subject: [PATCH 202/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 api.md                                        |  2 +-
 src/codex/resources/projects/evals.py         | 38 ++++++++++++++++---
 src/codex/types/projects/__init__.py          |  1 +
 src/codex/types/projects/eval_list_params.py  | 16 ++++++++
 .../types/projects/eval_list_response.py      | 11 ++++--
 tests/api_resources/projects/test_evals.py    | 38 +++++++++++++++----
 7 files changed, 89 insertions(+), 19 deletions(-)
 create mode 100644 src/codex/types/projects/eval_list_params.py

diff --git a/.stats.yml b/.stats.yml
index 20ee8270..b16c0569 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: b3a1a58600b52a20671bef2b25f5dbc4
+openapi_spec_hash: 1e86d5a7384400f4c3ddfb824fb31d84
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/api.md b/api.md
index 0c027913..22281ae1 100644
--- a/api.md
+++ b/api.md
@@ -192,7 +192,7 @@ Methods:
 
 - <code title="post /api/projects/{project_id}/evals">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">create</a>(project_id, \*\*<a href="src/codex/types/projects/eval_create_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
 - <code title="put /api/projects/{project_id}/evals/{eval_key}">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">update</a>(path_eval_key, \*, project_id, \*\*<a href="src/codex/types/projects/eval_update_params.py">params</a>) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
-- <code title="get /api/projects/{project_id}/evals">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">list</a>(project_id) -> <a href="./src/codex/types/projects/eval_list_response.py">EvalListResponse</a></code>
+- <code title="get /api/projects/{project_id}/evals">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">list</a>(project_id, \*\*<a href="src/codex/types/projects/eval_list_params.py">params</a>) -> <a href="./src/codex/types/projects/eval_list_response.py">EvalListResponse</a></code>
 - <code title="delete /api/projects/{project_id}/evals/{eval_key}">client.projects.evals.<a href="./src/codex/resources/projects/evals.py">delete</a>(eval_key, \*, project_id) -> <a href="./src/codex/types/project_return_schema.py">ProjectReturnSchema</a></code>
 
 ## QueryLogs
diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py
index 1fc95890..9de41b79 100644
--- a/src/codex/resources/projects/evals.py
+++ b/src/codex/resources/projects/evals.py
@@ -18,7 +18,7 @@
     async_to_streamed_response_wrapper,
 )
 from ..._base_client import make_request_options
-from ...types.projects import eval_create_params, eval_update_params
+from ...types.projects import eval_list_params, eval_create_params, eval_update_params
 from ...types.project_return_schema import ProjectReturnSchema
 from ...types.projects.eval_list_response import EvalListResponse
 
@@ -324,6 +324,9 @@ def list(
         self,
         project_id: str,
         *,
+        guardrails_only: bool | NotGiven = NOT_GIVEN,
+        limit: Optional[int] | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -332,7 +335,7 @@ def list(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EvalListResponse:
         """
-        Get the evaluations config for a project.
+        Get the evaluations config for a project with optional pagination.
 
         Args:
           extra_headers: Send extra headers
@@ -348,7 +351,18 @@ def list(
         return self._get(
             f"/api/projects/{project_id}/evals",
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=maybe_transform(
+                    {
+                        "guardrails_only": guardrails_only,
+                        "limit": limit,
+                        "offset": offset,
+                    },
+                    eval_list_params.EvalListParams,
+                ),
             ),
             cast_to=EvalListResponse,
         )
@@ -689,6 +703,9 @@ async def list(
         self,
         project_id: str,
         *,
+        guardrails_only: bool | NotGiven = NOT_GIVEN,
+        limit: Optional[int] | NotGiven = NOT_GIVEN,
+        offset: int | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
         extra_headers: Headers | None = None,
@@ -697,7 +714,7 @@ async def list(
         timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
     ) -> EvalListResponse:
         """
-        Get the evaluations config for a project.
+        Get the evaluations config for a project with optional pagination.
 
         Args:
           extra_headers: Send extra headers
@@ -713,7 +730,18 @@ async def list(
         return await self._get(
             f"/api/projects/{project_id}/evals",
             options=make_request_options(
-                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+                extra_headers=extra_headers,
+                extra_query=extra_query,
+                extra_body=extra_body,
+                timeout=timeout,
+                query=await async_maybe_transform(
+                    {
+                        "guardrails_only": guardrails_only,
+                        "limit": limit,
+                        "offset": offset,
+                    },
+                    eval_list_params.EvalListParams,
+                ),
             ),
             cast_to=EvalListResponse,
         )
diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py
index 4f754703..cb2989f3 100644
--- a/src/codex/types/projects/__init__.py
+++ b/src/codex/types/projects/__init__.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+from .eval_list_params import EvalListParams as EvalListParams
 from .access_key_schema import AccessKeySchema as AccessKeySchema
 from .eval_create_params import EvalCreateParams as EvalCreateParams
 from .eval_list_response import EvalListResponse as EvalListResponse
diff --git a/src/codex/types/projects/eval_list_params.py b/src/codex/types/projects/eval_list_params.py
new file mode 100644
index 00000000..b0f2fb6e
--- /dev/null
+++ b/src/codex/types/projects/eval_list_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["EvalListParams"]
+
+
+class EvalListParams(TypedDict, total=False):
+    guardrails_only: bool
+
+    limit: Optional[int]
+
+    offset: int
diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py
index 48859b8d..eb2cb9a3 100644
--- a/src/codex/types/projects/eval_list_response.py
+++ b/src/codex/types/projects/eval_list_response.py
@@ -1,14 +1,14 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 from typing import List, Optional
-from typing_extensions import Literal, TypeAlias
+from typing_extensions import Literal
 
 from ..._models import BaseModel
 
-__all__ = ["EvalListResponse", "EvalListResponseItem"]
+__all__ = ["EvalListResponse", "Eval"]
 
 
-class EvalListResponseItem(BaseModel):
+class Eval(BaseModel):
     criteria: str
     """
     The evaluation criteria text that describes what aspect is being evaluated and
@@ -69,4 +69,7 @@ class EvalListResponseItem(BaseModel):
     """Whether the evaluation fails when score is above or below the threshold"""
 
 
-EvalListResponse: TypeAlias = List[EvalListResponseItem]
+class EvalListResponse(BaseModel):
+    evals: List[Eval]
+
+    total_count: int
diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py
index 22b83803..f36de276 100644
--- a/tests/api_resources/projects/test_evals.py
+++ b/tests/api_resources/projects/test_evals.py
@@ -259,7 +259,18 @@ def test_path_params_update_overload_2(self, client: Codex) -> None:
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         eval = client.projects.evals.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    def test_method_list_with_all_params(self, client: Codex) -> None:
+        eval = client.projects.evals.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            guardrails_only=True,
+            limit=1,
+            offset=0,
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
@@ -267,7 +278,7 @@ def test_method_list(self, client: Codex) -> None:
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -279,7 +290,7 @@ def test_raw_response_list(self, client: Codex) -> None:
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -294,7 +305,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
     def test_path_params_list(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             client.projects.evals.with_raw_response.list(
-                "",
+                project_id="",
             )
 
     @pytest.mark.skip()
@@ -596,7 +607,18 @@ async def test_path_params_update_overload_2(self, async_client: AsyncCodex) ->
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+        )
+        assert_matches_type(EvalListResponse, eval, path=["response"])
+
+    @pytest.mark.skip()
+    @parametrize
+    async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
+        eval = await async_client.projects.evals.list(
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            guardrails_only=True,
+            limit=1,
+            offset=0,
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
@@ -604,7 +626,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         )
 
         assert response.is_closed is True
@@ -616,7 +638,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.list(
-            "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+            project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
         ) as response:
             assert not response.is_closed
             assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -631,7 +653,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
     async def test_path_params_list(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
             await async_client.projects.evals.with_raw_response.list(
-                "",
+                project_id="",
             )
 
     @pytest.mark.skip()

From 2c523e886f4757eca7939e916efa794ef7a97533 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 16 Jul 2025 17:17:47 +0000
Subject: [PATCH 203/227] feat(api): api update

---
 .stats.yml                                                  | 2 +-
 .../types/projects/query_log_list_by_group_response.py      | 6 +++---
 src/codex/types/projects/query_log_list_groups_response.py  | 6 +++---
 src/codex/types/projects/query_log_list_response.py         | 6 +++---
 src/codex/types/projects/query_log_retrieve_response.py     | 6 +++---
 .../projects/remediation_list_resolved_logs_response.py     | 6 +++---
 6 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index b16c0569..138e8d5c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 55
-openapi_spec_hash: 1e86d5a7384400f4c3ddfb824fb31d84
+openapi_spec_hash: fd2542df68972f34edeb819c58600791
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index ccd2d5ec..0ff5909b 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -364,10 +364,10 @@ class QueryLogsByGroupQueryLog(BaseModel):
     """If true, the response was guardrailed"""
 
     messages: Optional[List[QueryLogsByGroupQueryLogMessage]] = None
-    """Optional message history to provide conversation context for the query.
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Used for TrustworthyRAG and to rewrite query into a self-contained version of
+    itself.
     """
 
     original_question: Optional[str] = None
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 9adb4227..495fc563 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -354,10 +354,10 @@ class QueryLogListGroupsResponse(BaseModel):
     """If true, the response was guardrailed"""
 
     messages: Optional[List[Message]] = None
-    """Optional message history to provide conversation context for the query.
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Used for TrustworthyRAG and to rewrite query into a self-contained version of
+    itself.
     """
 
     original_question: Optional[str] = None
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index f6fbba15..72a8cab6 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -348,10 +348,10 @@ class QueryLogListResponse(BaseModel):
     """If true, the response was guardrailed"""
 
     messages: Optional[List[Message]] = None
-    """Optional message history to provide conversation context for the query.
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Used for TrustworthyRAG and to rewrite query into a self-contained version of
+    itself.
     """
 
     original_question: Optional[str] = None
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 784009cf..43242699 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -352,10 +352,10 @@ class QueryLogRetrieveResponse(BaseModel):
     """If true, the response was guardrailed"""
 
     messages: Optional[List[Message]] = None
-    """Optional message history to provide conversation context for the query.
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Used for TrustworthyRAG and to rewrite query into a self-contained version of
+    itself.
     """
 
     original_question: Optional[str] = None
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 1e0154cd..cebfaf49 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -355,10 +355,10 @@ class QueryLog(BaseModel):
     """If true, the response was guardrailed"""
 
     messages: Optional[List[QueryLogMessage]] = None
-    """Optional message history to provide conversation context for the query.
+    """Message history to provide conversation context for the query.
 
-    Used to rewrite query into a self-contained version of itself. If not provided,
-    the query will be treated as self-contained.
+    Used for TrustworthyRAG and to rewrite query into a self-contained version of
+    itself.
     """
 
     original_question: Optional[str] = None

From c07c8df625040741da965b5e37ae5a0949f83fc9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Jul 2025 02:05:22 +0000
Subject: [PATCH 204/227] fix(parsing): ignore empty metadata

---
 src/codex/_models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index 528d5680..ffcbf67b 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -439,7 +439,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]
         type_ = type_.__value__  # type: ignore[unreachable]
 
     # unwrap `Annotated[T, ...]` -> `T`
-    if metadata is not None:
+    if metadata is not None and len(metadata) > 0:
         meta: tuple[Any, ...] = tuple(metadata)
     elif is_annotated_type(type_):
         meta = get_args(type_)[1:]

From f17da74ada41dc39463bad1abd3e0d546e318b37 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Jul 2025 22:17:30 +0000
Subject: [PATCH 205/227] feat(api): api update

---
 .stats.yml                                    |   4 +-
 api.md                                        |   1 -
 src/codex/resources/projects/projects.py      | 102 ---------------
 src/codex/types/__init__.py                   |   1 -
 .../types/project_increment_queries_params.py |  11 --
 tests/api_resources/test_projects.py          | 118 ------------------
 6 files changed, 2 insertions(+), 235 deletions(-)
 delete mode 100644 src/codex/types/project_increment_queries_params.py

diff --git a/.stats.yml b/.stats.yml
index 138e8d5c..c31fbb05 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
-configured_endpoints: 55
-openapi_spec_hash: fd2542df68972f34edeb819c58600791
+configured_endpoints: 54
+openapi_spec_hash: 168bdf5a611596d39812ce7259416529
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/api.md b/api.md
index 22281ae1..1646d0b9 100644
--- a/api.md
+++ b/api.md
@@ -153,7 +153,6 @@ Methods:
 - <code title="get /api/projects/">client.projects.<a href="./src/codex/resources/projects/projects.py">list</a>(\*\*<a href="src/codex/types/project_list_params.py">params</a>) -> <a href="./src/codex/types/project_list_response.py">ProjectListResponse</a></code>
 - <code title="delete /api/projects/{project_id}">client.projects.<a href="./src/codex/resources/projects/projects.py">delete</a>(project_id) -> None</code>
 - <code title="get /api/projects/{project_id}/export">client.projects.<a href="./src/codex/resources/projects/projects.py">export</a>(project_id) -> object</code>
-- <code title="post /api/projects/{project_id}/increment_queries">client.projects.<a href="./src/codex/resources/projects/projects.py">increment_queries</a>(project_id, \*\*<a href="src/codex/types/project_increment_queries_params.py">params</a>) -> object</code>
 - <code title="post /api/projects/{project_id}/notifications">client.projects.<a href="./src/codex/resources/projects/projects.py">invite_sme</a>(project_id, \*\*<a href="src/codex/types/project_invite_sme_params.py">params</a>) -> <a href="./src/codex/types/project_invite_sme_response.py">ProjectInviteSmeResponse</a></code>
 - <code title="get /api/projects/{project_id}/analytics/">client.projects.<a href="./src/codex/resources/projects/projects.py">retrieve_analytics</a>(project_id, \*\*<a href="src/codex/types/project_retrieve_analytics_params.py">params</a>) -> <a href="./src/codex/types/project_retrieve_analytics_response.py">ProjectRetrieveAnalyticsResponse</a></code>
 - <code title="post /api/projects/{project_id}/validate">client.projects.<a href="./src/codex/resources/projects/projects.py">validate</a>(project_id, \*\*<a href="src/codex/types/project_validate_params.py">params</a>) -> <a href="./src/codex/types/project_validate_response.py">ProjectValidateResponse</a></code>
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index dc01b112..3a109edf 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-import typing_extensions
 from typing import Dict, List, Iterable, Optional
 from typing_extensions import Literal
 
@@ -22,7 +21,6 @@
     project_update_params,
     project_validate_params,
     project_invite_sme_params,
-    project_increment_queries_params,
     project_retrieve_analytics_params,
 )
 from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
@@ -349,45 +347,6 @@ def export(
             cast_to=object,
         )
 
-    @typing_extensions.deprecated("deprecated")
-    def increment_queries(
-        self,
-        project_id: str,
-        *,
-        count: int | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> object:
-        """
-        Increment the queries metric for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return self._post(
-            f"/api/projects/{project_id}/increment_queries",
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=maybe_transform({"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams),
-            ),
-            cast_to=object,
-        )
-
     def invite_sme(
         self,
         project_id: str,
@@ -956,47 +915,6 @@ async def export(
             cast_to=object,
         )
 
-    @typing_extensions.deprecated("deprecated")
-    async def increment_queries(
-        self,
-        project_id: str,
-        *,
-        count: int | NotGiven = NOT_GIVEN,
-        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
-        # The extra values given here take precedence over values defined on the client or passed to this method.
-        extra_headers: Headers | None = None,
-        extra_query: Query | None = None,
-        extra_body: Body | None = None,
-        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
-    ) -> object:
-        """
-        Increment the queries metric for a project.
-
-        Args:
-          extra_headers: Send extra headers
-
-          extra_query: Add additional query parameters to the request
-
-          extra_body: Add additional JSON properties to the request
-
-          timeout: Override the client-level default timeout for this request, in seconds
-        """
-        if not project_id:
-            raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
-        return await self._post(
-            f"/api/projects/{project_id}/increment_queries",
-            options=make_request_options(
-                extra_headers=extra_headers,
-                extra_query=extra_query,
-                extra_body=extra_body,
-                timeout=timeout,
-                query=await async_maybe_transform(
-                    {"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams
-                ),
-            ),
-            cast_to=object,
-        )
-
     async def invite_sme(
         self,
         project_id: str,
@@ -1308,11 +1226,6 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_raw_response_wrapper(
             projects.export,
         )
-        self.increment_queries = (  # pyright: ignore[reportDeprecated]
-            to_raw_response_wrapper(
-                projects.increment_queries  # pyright: ignore[reportDeprecated],
-            )
-        )
         self.invite_sme = to_raw_response_wrapper(
             projects.invite_sme,
         )
@@ -1362,11 +1275,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_raw_response_wrapper(
             projects.export,
         )
-        self.increment_queries = (  # pyright: ignore[reportDeprecated]
-            async_to_raw_response_wrapper(
-                projects.increment_queries  # pyright: ignore[reportDeprecated],
-            )
-        )
         self.invite_sme = async_to_raw_response_wrapper(
             projects.invite_sme,
         )
@@ -1416,11 +1324,6 @@ def __init__(self, projects: ProjectsResource) -> None:
         self.export = to_streamed_response_wrapper(
             projects.export,
         )
-        self.increment_queries = (  # pyright: ignore[reportDeprecated]
-            to_streamed_response_wrapper(
-                projects.increment_queries  # pyright: ignore[reportDeprecated],
-            )
-        )
         self.invite_sme = to_streamed_response_wrapper(
             projects.invite_sme,
         )
@@ -1470,11 +1373,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
         self.export = async_to_streamed_response_wrapper(
             projects.export,
         )
-        self.increment_queries = (  # pyright: ignore[reportDeprecated]
-            async_to_streamed_response_wrapper(
-                projects.increment_queries  # pyright: ignore[reportDeprecated],
-            )
-        )
         self.invite_sme = async_to_streamed_response_wrapper(
             projects.invite_sme,
         )
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 70713a3b..daa16358 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -19,7 +19,6 @@
 from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
 from .project_invite_sme_response import ProjectInviteSmeResponse as ProjectInviteSmeResponse
 from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
-from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams
 from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams
 from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse
 from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse
diff --git a/src/codex/types/project_increment_queries_params.py b/src/codex/types/project_increment_queries_params.py
deleted file mode 100644
index f6043a76..00000000
--- a/src/codex/types/project_increment_queries_params.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import TypedDict
-
-__all__ = ["ProjectIncrementQueriesParams"]
-
-
-class ProjectIncrementQueriesParams(TypedDict, total=False):
-    count: int
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 4507741b..ce4b7f53 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -18,8 +18,6 @@
 )
 from tests.utils import assert_matches_type
 
-# pyright: reportDeprecated=false
-
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
 
@@ -462,64 +460,6 @@ def test_path_params_export(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_increment_queries(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            project = client.projects.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_method_increment_queries_with_all_params(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            project = client.projects.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                count=0,
-            )
-
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_raw_response_increment_queries(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            response = client.projects.with_raw_response.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        project = response.parse()
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_streaming_response_increment_queries(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with client.projects.with_streaming_response.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            ) as response:
-                assert not response.is_closed
-                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-                project = response.parse()
-                assert_matches_type(object, project, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    def test_path_params_increment_queries(self, client: Codex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-                client.projects.with_raw_response.increment_queries(
-                    project_id="",
-                )
-
     @pytest.mark.skip()
     @parametrize
     def test_method_invite_sme(self, client: Codex) -> None:
@@ -1164,64 +1104,6 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_increment_queries(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            project = await async_client.projects.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            project = await async_client.projects.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-                count=0,
-            )
-
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            response = await async_client.projects.with_raw_response.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            )
-
-        assert response.is_closed is True
-        assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-        project = await response.parse()
-        assert_matches_type(object, project, path=["response"])
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            async with async_client.projects.with_streaming_response.increment_queries(
-                project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
-            ) as response:
-                assert not response.is_closed
-                assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
-                project = await response.parse()
-                assert_matches_type(object, project, path=["response"])
-
-        assert cast(Any, response.is_closed) is True
-
-    @pytest.mark.skip()
-    @parametrize
-    async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None:
-        with pytest.warns(DeprecationWarning):
-            with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
-                await async_client.projects.with_raw_response.increment_queries(
-                    project_id="",
-                )
-
     @pytest.mark.skip()
     @parametrize
     async def test_method_invite_sme(self, async_client: AsyncCodex) -> None:

From 4fc59ec3153ed5d4869c4d08fda845d5bd86b1c6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Jul 2025 02:06:52 +0000
Subject: [PATCH 206/227] fix(parsing): parse extra field types

---
 src/codex/_models.py | 25 +++++++++++++++++++++++--
 tests/test_models.py | 29 ++++++++++++++++++++++++++++-
 2 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/src/codex/_models.py b/src/codex/_models.py
index ffcbf67b..b8387ce9 100644
--- a/src/codex/_models.py
+++ b/src/codex/_models.py
@@ -208,14 +208,18 @@ def construct(  # pyright: ignore[reportIncompatibleMethodOverride]
             else:
                 fields_values[name] = field_get_default(field)
 
+        extra_field_type = _get_extra_fields_type(__cls)
+
         _extra = {}
         for key, value in values.items():
             if key not in model_fields:
+                parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value
+
                 if PYDANTIC_V2:
-                    _extra[key] = value
+                    _extra[key] = parsed
                 else:
                     _fields_set.add(key)
-                    fields_values[key] = value
+                    fields_values[key] = parsed
 
         object.__setattr__(m, "__dict__", fields_values)
 
@@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
     return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
 
 
+def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
+    if not PYDANTIC_V2:
+        # TODO
+        return None
+
+    schema = cls.__pydantic_core_schema__
+    if schema["type"] == "model":
+        fields = schema["schema"]
+        if fields["type"] == "model-fields":
+            extras = fields.get("extras_schema")
+            if extras and "cls" in extras:
+                # mypy can't narrow the type
+                return extras["cls"]  # type: ignore[no-any-return]
+
+    return None
+
+
 def is_basemodel(type_: type) -> bool:
     """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
     if is_union(type_):
diff --git a/tests/test_models.py b/tests/test_models.py
index 3452a61b..a9897029 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,5 +1,5 @@
 import json
-from typing import Any, Dict, List, Union, Optional, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
 from datetime import datetime, timezone
 from typing_extensions import Literal, Annotated, TypeAliasType
 
@@ -934,3 +934,30 @@ class Type2(BaseModel):
     )
     assert isinstance(model, Type1)
     assert isinstance(model.value, InnerType2)
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now")
+def test_extra_properties() -> None:
+    class Item(BaseModel):
+        prop: int
+
+    class Model(BaseModel):
+        __pydantic_extra__: Dict[str, Item] = Field(init=False)  # pyright: ignore[reportIncompatibleVariableOverride]
+
+        other: str
+
+        if TYPE_CHECKING:
+
+            def __getattr__(self, attr: str) -> Item: ...
+
+    model = construct_type(
+        type_=Model,
+        value={
+            "a": {"prop": 1},
+            "other": "foo",
+        },
+    )
+    assert isinstance(model, Model)
+    assert model.a.prop == 1
+    assert isinstance(model.a, Item)
+    assert model.other == "foo"

From 95c6aab1a87026b300ebd880b57a33caf01a229b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Jul 2025 20:17:58 +0000
Subject: [PATCH 207/227] feat(api): api update

---
 .stats.yml                                               | 2 +-
 src/codex/resources/projects/query_logs.py               | 4 ++--
 src/codex/types/projects/query_log_list_groups_params.py | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index c31fbb05..4cb0619e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 168bdf5a611596d39812ce7259416529
+openapi_spec_hash: 1ef62145e2247a442b75c87b23267e2d
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 9ccecbed..6fa490e8 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -286,7 +286,7 @@ def list_groups(
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
         | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -646,7 +646,7 @@ def list_groups(
             List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]]
         ]
         | NotGiven = NOT_GIVEN,
-        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+        sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
         | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
index cd82d9a7..f75ee299 100644
--- a/src/codex/types/projects/query_log_list_groups_params.py
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -44,7 +44,7 @@ class QueryLogListGroupsParams(TypedDict, total=False):
     ]
     """Filter logs that have ANY of these primary evaluation issues (OR operation)"""
 
-    sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]]
+    sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
 
     was_cache_hit: Optional[bool]
     """Filter by cache hit status"""

From 7b478f8059d12f8483dbb250715ad0f05b519c00 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Jul 2025 18:17:59 +0000
Subject: [PATCH 208/227] feat(api): api update

---
 .stats.yml                                   | 2 +-
 src/codex/types/project_create_params.py     | 2 ++
 src/codex/types/project_list_response.py     | 2 ++
 src/codex/types/project_retrieve_response.py | 2 ++
 src/codex/types/project_return_schema.py     | 2 ++
 src/codex/types/project_update_params.py     | 2 ++
 tests/api_resources/test_projects.py         | 4 ++++
 7 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 4cb0619e..de1764db 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 1ef62145e2247a442b75c87b23267e2d
+openapi_spec_hash: 1c6e7d5ed06d72868a57e64381bc473c
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index a8369782..c75023f0 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -333,4 +333,6 @@ class Config(TypedDict, total=False):
 
     query_use_llm_matching: bool
 
+    tlm_evals_model: str
+
     upper_llm_match_distance_threshold: float
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index c39bf086..4ac38497 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -323,6 +323,8 @@ class ProjectConfig(BaseModel):
 
     query_use_llm_matching: Optional[bool] = None
 
+    tlm_evals_model: Optional[str] = None
+
     upper_llm_match_distance_threshold: Optional[float] = None
 
 
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
index 7d1f8edd..6e87d655 100644
--- a/src/codex/types/project_retrieve_response.py
+++ b/src/codex/types/project_retrieve_response.py
@@ -322,6 +322,8 @@ class Config(BaseModel):
 
     query_use_llm_matching: Optional[bool] = None
 
+    tlm_evals_model: Optional[str] = None
+
     upper_llm_match_distance_threshold: Optional[float] = None
 
 
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 170d7994..bb087cd0 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -322,6 +322,8 @@ class Config(BaseModel):
 
     query_use_llm_matching: Optional[bool] = None
 
+    tlm_evals_model: Optional[str] = None
+
     upper_llm_match_distance_threshold: Optional[float] = None
 
 
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 3e244411..c550b436 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -331,4 +331,6 @@ class Config(TypedDict, total=False):
 
     query_use_llm_matching: bool
 
+    tlm_evals_model: str
+
     upper_llm_match_distance_threshold: float
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index ce4b7f53..9312ca0a 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -118,6 +118,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
+                "tlm_evals_model": "tlm_evals_model",
                 "upper_llm_match_distance_threshold": 0,
             },
             name="name",
@@ -293,6 +294,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
+                "tlm_evals_model": "tlm_evals_model",
                 "upper_llm_match_distance_threshold": 0,
             },
             description="description",
@@ -762,6 +764,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
+                "tlm_evals_model": "tlm_evals_model",
                 "upper_llm_match_distance_threshold": 0,
             },
             name="name",
@@ -937,6 +940,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
                 "lower_llm_match_distance_threshold": 0,
                 "max_distance": 0,
                 "query_use_llm_matching": True,
+                "tlm_evals_model": "tlm_evals_model",
                 "upper_llm_match_distance_threshold": 0,
             },
             description="description",

From df7fb101acaa6d9c3327cd1daaaf0ae571a514e8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Jul 2025 03:08:09 +0000
Subject: [PATCH 209/227] chore(project): add settings file for vscode

---
 .gitignore            | 1 -
 .vscode/settings.json | 3 +++
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 .vscode/settings.json

diff --git a/.gitignore b/.gitignore
index 87797408..95ceb189 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 .prism.log
-.vscode
 _dev
 
 __pycache__
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..5b010307
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "python.analysis.importFormat": "relative",
+}

From ec8118f9ddabd922ad95e7006552d764992141ea Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Jul 2025 21:18:01 +0000
Subject: [PATCH 210/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 src/codex/types/project_validate_params.py    | 172 ++++++++---------
 .../query_log_list_by_group_response.py       | 176 +++++++++---------
 .../query_log_list_groups_response.py         | 168 ++++++++---------
 .../types/projects/query_log_list_response.py | 168 ++++++++---------
 .../projects/query_log_retrieve_response.py   | 168 ++++++++---------
 ...remediation_list_resolved_logs_response.py | 172 ++++++++---------
 tests/api_resources/test_projects.py          |  36 +++-
 8 files changed, 547 insertions(+), 515 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index de1764db..9fb51405 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 1c6e7d5ed06d72868a57e64381bc473c
+openapi_spec_hash: f7b67b502828e6d0ca3944d40d00d89b
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 7b85d061..081dd2a5 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -29,19 +29,6 @@
     "ResponseChatCompletionUsageCompletionTokensDetails",
     "ResponseChatCompletionUsagePromptTokensDetails",
     "Message",
-    "MessageChatCompletionDeveloperMessageParam",
-    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "MessageChatCompletionSystemMessageParam",
-    "MessageChatCompletionSystemMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParamInput",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1File",
-    "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile",
     "MessageChatCompletionAssistantMessageParamInput",
     "MessageChatCompletionAssistantMessageParamInputAudio",
     "MessageChatCompletionAssistantMessageParamInputContentUnionMember1",
@@ -52,7 +39,20 @@
     "MessageChatCompletionAssistantMessageParamInputToolCallFunction",
     "MessageChatCompletionToolMessageParam",
     "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamInput",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
     "MessageChatCompletionFunctionMessageParam",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
     "Options",
 ]
 
@@ -440,32 +440,80 @@ class ResponseChatCompletionTyped(TypedDict, total=False):
 Response: TypeAlias = Union[str, ResponseChatCompletion]
 
 
-class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
+class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False):
+    id: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam(
+    TypedDict, total=False
+):
     text: Required[str]
 
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]]
+class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    TypedDict, total=False
+):
+    refusal: Required[str]
+
+    type: Required[Literal["refusal"]]
 
-    role: Required[Literal["developer"]]
+
+MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False):
+    arguments: Required[str]
+
+    name: Required[str]
+
+
+class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False):
+    id: Required[str]
+
+    function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction]
+
+    type: Required[Literal["function"]]
+
+
+class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False):
+    role: Required[Literal["assistant"]]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio]
+
+    content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None]
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall]
 
     name: str
 
+    refusal: Optional[str]
 
-class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False):
+    tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall]
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionSystemMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]]
+class MessageChatCompletionToolMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]]
 
-    role: Required[Literal["system"]]
+    role: Required[Literal["tool"]]
 
-    name: str
+    tool_call_id: Required[str]
 
 
 class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam(
@@ -542,97 +590,49 @@ class MessageChatCompletionUserMessageParamInput(TypedDict, total=False):
     name: str
 
 
-class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False):
-    id: Required[str]
-
-
-class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam(
-    TypedDict, total=False
-):
+class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    TypedDict, total=False
-):
-    refusal: Required[str]
-
-    type: Required[Literal["refusal"]]
-
-
-MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class MessageChatCompletionSystemMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]]
 
-class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False):
-    arguments: Required[str]
+    role: Required[Literal["system"]]
 
-    name: Required[str]
+    name: str
 
 
-class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False):
-    arguments: Required[str]
+class MessageChatCompletionFunctionMessageParam(TypedDict, total=False):
+    content: Required[Optional[str]]
 
     name: Required[str]
 
-
-class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False):
-    id: Required[str]
-
-    function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction]
-
-    type: Required[Literal["function"]]
-
-
-class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False):
-    role: Required[Literal["assistant"]]
-
-    audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio]
-
-    content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None]
-
-    function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall]
-
-    name: str
-
-    refusal: Optional[str]
-
-    tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall]
+    role: Required[Literal["function"]]
 
 
-class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False):
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False):
     text: Required[str]
 
     type: Required[Literal["text"]]
 
 
-class MessageChatCompletionToolMessageParam(TypedDict, total=False):
-    content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]]
-
-    role: Required[Literal["tool"]]
-
-    tool_call_id: Required[str]
-
-
-class MessageChatCompletionFunctionMessageParam(TypedDict, total=False):
-    content: Required[Optional[str]]
+class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
+    content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]]
 
-    name: Required[str]
+    role: Required[Literal["developer"]]
 
-    role: Required[Literal["function"]]
+    name: str
 
 
 Message: TypeAlias = Union[
-    MessageChatCompletionDeveloperMessageParam,
-    MessageChatCompletionSystemMessageParam,
-    MessageChatCompletionUserMessageParamInput,
     MessageChatCompletionAssistantMessageParamInput,
     MessageChatCompletionToolMessageParam,
+    MessageChatCompletionUserMessageParamInput,
+    MessageChatCompletionSystemMessageParam,
     MessageChatCompletionFunctionMessageParam,
+    MessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index 0ff5909b..ae49b954 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -17,19 +17,6 @@
     "QueryLogsByGroupQueryLogContext",
     "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
     "QueryLogsByGroupQueryLogMessage",
-    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
-    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam",
-    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
-    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
@@ -40,7 +27,20 @@
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction",
     "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
     "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
+    "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
 ]
 
 
@@ -93,32 +93,82 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
-    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
 
-    role: Literal["developer"]
+    type: Literal["refusal"]
+
+
+QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[
+        str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None
+    ] = None
+
+    function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
 
     name: Optional[str] = None
 
+    refusal: Optional[str] = None
 
-class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel):
-    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
+class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
 
-    role: Literal["system"]
+    role: Literal["tool"]
 
-    name: Optional[str] = None
+    tool_call_id: str
 
 
 class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
@@ -191,99 +241,49 @@ class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput(BaseMo
     name: Optional[str] = None
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
-    id: str
-
-
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
-    BaseModel
-):
+class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    BaseModel
-):
-    refusal: str
-
-    type: Literal["refusal"]
-
-
-QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
-    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
-    QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
 
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
-    arguments: str
+    role: Literal["system"]
 
-    name: str
+    name: Optional[str] = None
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
-    arguments: str
+class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
 
     name: str
 
-
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
-    id: str
-
-    function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
-
-    type: Literal["function"]
-
-
-class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
-    role: Literal["assistant"]
-
-    audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
-
-    content: Union[
-        str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None
-    ] = None
-
-    function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
-
-    name: Optional[str] = None
-
-    refusal: Optional[str] = None
-
-    tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+    role: Literal["function"]
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel):
-    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
-
-    role: Literal["tool"]
-
-    tool_call_id: str
-
-
-class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
-    content: Optional[str] = None
+class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
 
-    name: str
+    role: Literal["developer"]
 
-    role: Literal["function"]
+    name: Optional[str] = None
 
 
 QueryLogsByGroupQueryLogMessage: TypeAlias = Union[
-    QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam,
-    QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam,
-    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput,
     QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput,
     QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput,
+    QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam,
     QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam,
+    QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 495fc563..cc3b208b 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -15,19 +15,6 @@
     "Context",
     "DeterministicGuardrailsResults",
     "Message",
-    "MessageChatCompletionDeveloperMessageParam",
-    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "MessageChatCompletionSystemMessageParam",
-    "MessageChatCompletionSystemMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutput",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
     "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
@@ -38,7 +25,20 @@
     "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
     "MessageChatCompletionToolMessageParam",
     "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
     "MessageChatCompletionFunctionMessageParam",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
 ]
 
 
@@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionDeveloperMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
 
-    role: Literal["developer"]
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
 
     name: Optional[str] = None
 
+    refusal: Optional[str] = None
 
-class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionSystemMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
 
-    role: Literal["system"]
+    role: Literal["tool"]
 
-    name: Optional[str] = None
+    tool_call_id: str
 
 
 class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
@@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel):
     name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
-    id: str
-
-
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    BaseModel
-):
-    refusal: str
-
-    type: Literal["refusal"]
-
-
-MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
 
-class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
-    arguments: str
+    role: Literal["system"]
 
-    name: str
+    name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
-    arguments: str
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
 
     name: str
 
-
-class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
-    id: str
-
-    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
-
-    type: Literal["function"]
-
-
-class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
-    role: Literal["assistant"]
-
-    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
-
-    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
-
-    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
-
-    name: Optional[str] = None
-
-    refusal: Optional[str] = None
-
-    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+    role: Literal["function"]
 
 
-class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionToolMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
-
-    role: Literal["tool"]
-
-    tool_call_id: str
-
-
-class MessageChatCompletionFunctionMessageParam(BaseModel):
-    content: Optional[str] = None
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
 
-    name: str
+    role: Literal["developer"]
 
-    role: Literal["function"]
+    name: Optional[str] = None
 
 
 Message: TypeAlias = Union[
-    MessageChatCompletionDeveloperMessageParam,
-    MessageChatCompletionSystemMessageParam,
-    MessageChatCompletionUserMessageParamOutput,
     MessageChatCompletionAssistantMessageParamOutput,
     MessageChatCompletionToolMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionSystemMessageParam,
     MessageChatCompletionFunctionMessageParam,
+    MessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index 72a8cab6..0778898f 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -15,19 +15,6 @@
     "Context",
     "DeterministicGuardrailsResults",
     "Message",
-    "MessageChatCompletionDeveloperMessageParam",
-    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "MessageChatCompletionSystemMessageParam",
-    "MessageChatCompletionSystemMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutput",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
     "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
@@ -38,7 +25,20 @@
     "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
     "MessageChatCompletionToolMessageParam",
     "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
     "MessageChatCompletionFunctionMessageParam",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
 ]
 
 
@@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionDeveloperMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
 
-    role: Literal["developer"]
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
 
     name: Optional[str] = None
 
+    refusal: Optional[str] = None
 
-class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionSystemMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
 
-    role: Literal["system"]
+    role: Literal["tool"]
 
-    name: Optional[str] = None
+    tool_call_id: str
 
 
 class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
@@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel):
     name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
-    id: str
-
-
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    BaseModel
-):
-    refusal: str
-
-    type: Literal["refusal"]
-
-
-MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
 
-class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
-    arguments: str
+    role: Literal["system"]
 
-    name: str
+    name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
-    arguments: str
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
 
     name: str
 
-
-class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
-    id: str
-
-    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
-
-    type: Literal["function"]
-
-
-class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
-    role: Literal["assistant"]
-
-    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
-
-    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
-
-    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
-
-    name: Optional[str] = None
-
-    refusal: Optional[str] = None
-
-    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+    role: Literal["function"]
 
 
-class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionToolMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
-
-    role: Literal["tool"]
-
-    tool_call_id: str
-
-
-class MessageChatCompletionFunctionMessageParam(BaseModel):
-    content: Optional[str] = None
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
 
-    name: str
+    role: Literal["developer"]
 
-    role: Literal["function"]
+    name: Optional[str] = None
 
 
 Message: TypeAlias = Union[
-    MessageChatCompletionDeveloperMessageParam,
-    MessageChatCompletionSystemMessageParam,
-    MessageChatCompletionUserMessageParamOutput,
     MessageChatCompletionAssistantMessageParamOutput,
     MessageChatCompletionToolMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionSystemMessageParam,
     MessageChatCompletionFunctionMessageParam,
+    MessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 43242699..2751ef21 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -15,19 +15,6 @@
     "Context",
     "DeterministicGuardrailsResults",
     "Message",
-    "MessageChatCompletionDeveloperMessageParam",
-    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "MessageChatCompletionSystemMessageParam",
-    "MessageChatCompletionSystemMessageParamContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutput",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
-    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
     "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
@@ -38,7 +25,20 @@
     "MessageChatCompletionAssistantMessageParamOutputToolCallFunction",
     "MessageChatCompletionToolMessageParam",
     "MessageChatCompletionToolMessageParamContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutput",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "MessageChatCompletionSystemMessageParam",
+    "MessageChatCompletionSystemMessageParamContentUnionMember1",
     "MessageChatCompletionFunctionMessageParam",
+    "MessageChatCompletionDeveloperMessageParam",
+    "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
 ]
 
 
@@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionDeveloperMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
 
-    role: Literal["developer"]
+    type: Literal["refusal"]
+
+
+MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
 
     name: Optional[str] = None
 
+    refusal: Optional[str] = None
 
-class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionSystemMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
+class MessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
 
-    role: Literal["system"]
+    role: Literal["tool"]
 
-    name: Optional[str] = None
+    tool_call_id: str
 
 
 class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
@@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel):
     name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
-    id: str
-
-
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel):
+class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    BaseModel
-):
-    refusal: str
-
-    type: Literal["refusal"]
-
-
-MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
-    MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class MessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]]
 
-class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
-    arguments: str
+    role: Literal["system"]
 
-    name: str
+    name: Optional[str] = None
 
 
-class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
-    arguments: str
+class MessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
 
     name: str
 
-
-class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
-    id: str
-
-    function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction
-
-    type: Literal["function"]
-
-
-class MessageChatCompletionAssistantMessageParamOutput(BaseModel):
-    role: Literal["assistant"]
-
-    audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None
-
-    content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
-
-    function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
-
-    name: Optional[str] = None
-
-    refusal: Optional[str] = None
-
-    tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+    role: Literal["function"]
 
 
-class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class MessageChatCompletionToolMessageParam(BaseModel):
-    content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]]
-
-    role: Literal["tool"]
-
-    tool_call_id: str
-
-
-class MessageChatCompletionFunctionMessageParam(BaseModel):
-    content: Optional[str] = None
+class MessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]
 
-    name: str
+    role: Literal["developer"]
 
-    role: Literal["function"]
+    name: Optional[str] = None
 
 
 Message: TypeAlias = Union[
-    MessageChatCompletionDeveloperMessageParam,
-    MessageChatCompletionSystemMessageParam,
-    MessageChatCompletionUserMessageParamOutput,
     MessageChatCompletionAssistantMessageParamOutput,
     MessageChatCompletionToolMessageParam,
+    MessageChatCompletionUserMessageParamOutput,
+    MessageChatCompletionSystemMessageParam,
     MessageChatCompletionFunctionMessageParam,
+    MessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index cebfaf49..d56f9a41 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -16,19 +16,6 @@
     "QueryLogContext",
     "QueryLogDeterministicGuardrailsResults",
     "QueryLogMessage",
-    "QueryLogMessageChatCompletionDeveloperMessageParam",
-    "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "QueryLogMessageChatCompletionSystemMessageParam",
-    "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
-    "QueryLogMessageChatCompletionUserMessageParamOutput",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
-    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
     "QueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
     "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1",
@@ -39,7 +26,20 @@
     "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction",
     "QueryLogMessageChatCompletionToolMessageParam",
     "QueryLogMessageChatCompletionToolMessageParamContentUnionMember1",
+    "QueryLogMessageChatCompletionUserMessageParamOutput",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File",
+    "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile",
+    "QueryLogMessageChatCompletionSystemMessageParam",
+    "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1",
     "QueryLogMessageChatCompletionFunctionMessageParam",
+    "QueryLogMessageChatCompletionDeveloperMessageParam",
+    "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
 ]
 
 
@@ -92,32 +92,80 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
+class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
+    id: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
+    BaseModel
+):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
-    content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
+class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
+    BaseModel
+):
+    refusal: str
+
+    type: Literal["refusal"]
 
-    role: Literal["developer"]
+
+QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
+    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
+    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
+]
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
+    id: str
+
+    function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
+
+    type: Literal["function"]
+
+
+class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
+    role: Literal["assistant"]
+
+    audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
+
+    content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
+
+    function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
 
     name: Optional[str] = None
 
+    refusal: Optional[str] = None
 
-class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
+    tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+
+
+class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogMessageChatCompletionSystemMessageParam(BaseModel):
-    content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
+class QueryLogMessageChatCompletionToolMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
 
-    role: Literal["system"]
+    role: Literal["tool"]
 
-    name: Optional[str] = None
+    tool_call_id: str
 
 
 class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
@@ -190,97 +238,49 @@ class QueryLogMessageChatCompletionUserMessageParamOutput(BaseModel):
     name: Optional[str] = None
 
 
-class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
-    id: str
-
-
-class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(
-    BaseModel
-):
+class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam(
-    BaseModel
-):
-    refusal: str
-
-    type: Literal["refusal"]
-
-
-QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[
-    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam,
-    QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam,
-]
-
+class QueryLogMessageChatCompletionSystemMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]]
 
-class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel):
-    arguments: str
+    role: Literal["system"]
 
-    name: str
+    name: Optional[str] = None
 
 
-class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel):
-    arguments: str
+class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
+    content: Optional[str] = None
 
     name: str
 
-
-class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel):
-    id: str
-
-    function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction
-
-    type: Literal["function"]
-
-
-class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel):
-    role: Literal["assistant"]
-
-    audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None
-
-    content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None
-
-    function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None
-
-    name: Optional[str] = None
-
-    refusal: Optional[str] = None
-
-    tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None
+    role: Literal["function"]
 
 
-class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel):
+class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel):
     text: str
 
     type: Literal["text"]
 
 
-class QueryLogMessageChatCompletionToolMessageParam(BaseModel):
-    content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]]
-
-    role: Literal["tool"]
-
-    tool_call_id: str
-
-
-class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel):
-    content: Optional[str] = None
+class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
+    content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]]
 
-    name: str
+    role: Literal["developer"]
 
-    role: Literal["function"]
+    name: Optional[str] = None
 
 
 QueryLogMessage: TypeAlias = Union[
-    QueryLogMessageChatCompletionDeveloperMessageParam,
-    QueryLogMessageChatCompletionSystemMessageParam,
-    QueryLogMessageChatCompletionUserMessageParamOutput,
     QueryLogMessageChatCompletionAssistantMessageParamOutput,
     QueryLogMessageChatCompletionToolMessageParam,
+    QueryLogMessageChatCompletionUserMessageParamOutput,
+    QueryLogMessageChatCompletionSystemMessageParam,
     QueryLogMessageChatCompletionFunctionMessageParam,
+    QueryLogMessageChatCompletionDeveloperMessageParam,
 ]
 
 
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 9312ca0a..ae3f4f05 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -594,9 +594,25 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             eval_scores={"foo": 0},
             messages=[
                 {
+                    "role": "assistant",
+                    "audio": {"id": "id"},
                     "content": "string",
-                    "role": "developer",
+                    "function_call": {
+                        "arguments": "arguments",
+                        "name": "name",
+                    },
                     "name": "name",
+                    "refusal": "refusal",
+                    "tool_calls": [
+                        {
+                            "id": "id",
+                            "function": {
+                                "arguments": "arguments",
+                                "name": "name",
+                            },
+                            "type": "function",
+                        }
+                    ],
                 }
             ],
             options={
@@ -1240,9 +1256,25 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             eval_scores={"foo": 0},
             messages=[
                 {
+                    "role": "assistant",
+                    "audio": {"id": "id"},
                     "content": "string",
-                    "role": "developer",
+                    "function_call": {
+                        "arguments": "arguments",
+                        "name": "name",
+                    },
                     "name": "name",
+                    "refusal": "refusal",
+                    "tool_calls": [
+                        {
+                            "id": "id",
+                            "function": {
+                                "arguments": "arguments",
+                                "name": "name",
+                            },
+                            "type": "function",
+                        }
+                    ],
                 }
             ],
             options={

From 0f0dd538be7aff82b4e7e27f2eb509e0f8f5dec6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Jul 2025 23:18:00 +0000
Subject: [PATCH 211/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 9fb51405..19c8465a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: f7b67b502828e6d0ca3944d40d00d89b
+openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56

From af96aa3c98951fecaa190001320ce2ebb513c268 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:03:15 +0000
Subject: [PATCH 212/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 src/codex/resources/projects/projects.py      |  74 +++++-----
 src/codex/resources/tlm.py                    | 128 +++++++++---------
 src/codex/types/project_validate_params.py    |  58 +++++---
 .../query_log_list_by_group_response.py       |  46 +++++++
 .../query_log_list_groups_response.py         |  46 +++++++
 .../types/projects/query_log_list_response.py |  46 +++++++
 .../projects/query_log_retrieve_response.py   |  46 +++++++
 ...remediation_list_resolved_logs_response.py |  46 +++++++
 src/codex/types/tlm_prompt_params.py          |  34 ++---
 src/codex/types/tlm_score_params.py           |  34 ++---
 tests/api_resources/test_projects.py          |  24 ++++
 tests/api_resources/test_tlm.py               |   4 +
 13 files changed, 443 insertions(+), 145 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 19c8465a..3fdd5d0e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75
+openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d
 config_hash: 8f6e5c3b064cbb77569a6bf654954a56
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 3a109edf..f82bcd03 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -460,6 +460,7 @@ def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
+        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -504,17 +505,16 @@ def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -550,12 +550,11 @@ def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -574,6 +573,8 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -582,6 +583,9 @@ def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
+          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
+              provided to the LLM.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -620,6 +624,7 @@ def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
+                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
@@ -1028,6 +1033,7 @@ async def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
+        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -1072,17 +1078,16 @@ async def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -1118,12 +1123,11 @@ async def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -1142,6 +1146,8 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -1150,6 +1156,9 @@ async def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
+          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
+              provided to the LLM.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1188,6 +1197,7 @@ async def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
+                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index 12ff6c0d..c6064ed6 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -79,17 +79,16 @@ def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -125,12 +124,11 @@ def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -149,6 +147,8 @@ def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -217,17 +217,16 @@ def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -263,12 +262,11 @@ def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -287,6 +285,8 @@ def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -371,17 +371,16 @@ async def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -417,12 +416,11 @@ async def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -441,6 +439,8 @@ async def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -509,17 +509,16 @@ async def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -555,12 +554,11 @@ async def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -579,6 +577,8 @@ async def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 081dd2a5..62313671 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -54,6 +54,8 @@
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
     "Options",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -106,17 +108,16 @@ class ProjectValidateParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -152,12 +153,11 @@ class ProjectValidateParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -175,6 +175,8 @@ class ProjectValidateParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     prompt: Optional[str]
@@ -194,6 +196,12 @@ class ProjectValidateParams(TypedDict, total=False):
 
     task: Optional[str]
 
+    tools: Optional[Iterable[Tool]]
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
@@ -649,8 +657,26 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
 
     use_self_reflection: bool
+
+
+class ToolFunction(TypedDict, total=False):
+    name: Required[str]
+
+    description: str
+
+    parameters: object
+
+    strict: Optional[bool]
+
+
+class Tool(TypedDict, total=False):
+    function: Required[ToolFunction]
+
+    type: Required[Literal["function"]]
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index ae49b954..b3c774ba 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -16,6 +16,8 @@
     "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
     "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
+    "QueryLogsByGroupQueryLogEvaluatedResponseToolCall",
+    "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction",
     "QueryLogsByGroupQueryLogMessage",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -41,6 +43,8 @@
     "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogTool",
+    "QueryLogsByGroupQueryLogToolFunction",
 ]
 
 
@@ -93,6 +97,20 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -287,6 +305,22 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod
 ]
 
 
+class QueryLogsByGroupQueryLogToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class QueryLogsByGroupQueryLogTool(BaseModel):
+    function: QueryLogsByGroupQueryLogToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogsByGroupQueryLog(BaseModel):
     id: str
 
@@ -357,6 +391,12 @@ class QueryLogsByGroupQueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -383,6 +423,12 @@ class QueryLogsByGroupQueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
+    tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
 
 class QueryLogsByGroup(BaseModel):
     query_logs: List[QueryLogsByGroupQueryLog]
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index cc3b208b..6ed4d146 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogListGroupsResponse(BaseModel):
     id: str
 
@@ -347,6 +381,12 @@ class QueryLogListGroupsResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -372,3 +412,9 @@ class QueryLogListGroupsResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index 0778898f..c6737b2f 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogListResponse(BaseModel):
     id: str
 
@@ -341,6 +375,12 @@ class QueryLogListResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -366,3 +406,9 @@ class QueryLogListResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 2751ef21..8fd8662e 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogRetrieveResponse(BaseModel):
     id: str
 
@@ -345,6 +379,12 @@ class QueryLogRetrieveResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -370,3 +410,9 @@ class QueryLogRetrieveResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index d56f9a41..567a0869 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -15,6 +15,8 @@
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
     "QueryLogDeterministicGuardrailsResults",
+    "QueryLogEvaluatedResponseToolCall",
+    "QueryLogEvaluatedResponseToolCallFunction",
     "QueryLogMessage",
     "QueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -40,6 +42,8 @@
     "QueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogTool",
+    "QueryLogToolFunction",
 ]
 
 
@@ -92,6 +96,20 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogEvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogEvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: QueryLogEvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -284,6 +302,22 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class QueryLogToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class QueryLogTool(BaseModel):
+    function: QueryLogToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLog(BaseModel):
     id: str
 
@@ -348,6 +382,12 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -374,6 +414,12 @@ class QueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
+    tools: Optional[List[QueryLogTool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
 
 class RemediationListResolvedLogsResponse(BaseModel):
     query_logs: List[QueryLog]
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 3c04bfc4..8749c5ac 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -30,17 +30,16 @@ class TlmPromptParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -76,12 +75,11 @@ class TlmPromptParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -99,6 +97,8 @@ class TlmPromptParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -120,6 +120,8 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index 95bcc4c4..4a0a32ad 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -32,17 +32,16 @@ class TlmScoreParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -78,12 +77,11 @@ class TlmScoreParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -101,6 +99,8 @@ class TlmScoreParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -122,6 +122,8 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index ae3f4f05..7884db0f 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -622,6 +622,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -630,6 +631,17 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
+            tools=[
+                {
+                    "function": {
+                        "name": "name",
+                        "description": "description",
+                        "parameters": {},
+                        "strict": True,
+                    },
+                    "type": "function",
+                }
+            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -1284,6 +1296,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -1292,6 +1305,17 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
+            tools=[
+                {
+                    "function": {
+                        "name": "name",
+                        "description": "description",
+                        "parameters": {},
+                        "strict": True,
+                    },
+                    "type": "function",
+                }
+            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index 41376a46..da0a9ad1 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -38,6 +38,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -96,6 +97,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -160,6 +162,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) ->
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -218,6 +221,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,

From 35dad32d24fd1a7c4815d8e92933cf925b3bcb69 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:04:05 +0000
Subject: [PATCH 213/227] feat(api): remove deprecated endpoint
 increment_queries

---
 .stats.yml                                    |   4 +-
 src/codex/resources/projects/projects.py      |  74 +++++-----
 src/codex/resources/tlm.py                    | 128 +++++++++---------
 src/codex/types/project_validate_params.py    |  58 +++-----
 .../query_log_list_by_group_response.py       |  46 -------
 .../query_log_list_groups_response.py         |  46 -------
 .../types/projects/query_log_list_response.py |  46 -------
 .../projects/query_log_retrieve_response.py   |  46 -------
 ...remediation_list_resolved_logs_response.py |  46 -------
 src/codex/types/tlm_prompt_params.py          |  34 +++--
 src/codex/types/tlm_score_params.py           |  34 +++--
 tests/api_resources/test_projects.py          |  24 ----
 tests/api_resources/test_tlm.py               |   4 -
 13 files changed, 146 insertions(+), 444 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 3fdd5d0e..031dedf7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d
-config_hash: 8f6e5c3b064cbb77569a6bf654954a56
+openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75
+config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index f82bcd03..3a109edf 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -460,7 +460,6 @@ def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
-        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -505,16 +504,17 @@ def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -550,11 +550,12 @@ def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -573,8 +574,6 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -583,9 +582,6 @@ def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
-          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
-              provided to the LLM.
-
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -624,7 +620,6 @@ def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
-                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
@@ -1033,7 +1028,6 @@ async def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
-        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -1078,16 +1072,17 @@ async def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -1123,11 +1118,12 @@ async def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -1146,8 +1142,6 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -1156,9 +1150,6 @@ async def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
-          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
-              provided to the LLM.
-
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1197,7 +1188,6 @@ async def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
-                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index c6064ed6..12ff6c0d 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -79,16 +79,17 @@ def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -124,11 +125,12 @@ def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -147,8 +149,6 @@ def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -217,16 +217,17 @@ def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -262,11 +263,12 @@ def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -285,8 +287,6 @@ def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -371,16 +371,17 @@ async def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -416,11 +417,12 @@ async def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -439,8 +441,6 @@ async def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -509,16 +509,17 @@ async def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"high"`.
-              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-                `reasoning_effort` = `"none"`.
-              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-                `reasoning_effort` = `"none"`.
+              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+                `use_self_reflection` = True. This preset improves LLM responses.
+              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+                `use_self_reflection` = True.
+              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+                `use_self_reflection` = True.
+              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+                "base" preset, a faster self-reflection is employed.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -554,11 +555,12 @@ async def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+                  and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -577,8 +579,6 @@ async def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
-
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 62313671..081dd2a5 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -54,8 +54,6 @@
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
     "Options",
-    "Tool",
-    "ToolFunction",
 ]
 
 
@@ -108,16 +106,17 @@ class ProjectValidateParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"none"`.
-    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-      `reasoning_effort` = `"none"`.
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a faster self-reflection is employed.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -153,11 +152,12 @@ class ProjectValidateParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -175,8 +175,6 @@ class ProjectValidateParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
-
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     prompt: Optional[str]
@@ -196,12 +194,6 @@ class ProjectValidateParams(TypedDict, total=False):
 
     task: Optional[str]
 
-    tools: Optional[Iterable[Tool]]
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
-
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
@@ -657,26 +649,8 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
-    num_self_reflections: int
-
     reasoning_effort: str
 
     similarity_measure: str
 
     use_self_reflection: bool
-
-
-class ToolFunction(TypedDict, total=False):
-    name: Required[str]
-
-    description: str
-
-    parameters: object
-
-    strict: Optional[bool]
-
-
-class Tool(TypedDict, total=False):
-    function: Required[ToolFunction]
-
-    type: Required[Literal["function"]]
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index b3c774ba..ae49b954 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -16,8 +16,6 @@
     "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
     "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
-    "QueryLogsByGroupQueryLogEvaluatedResponseToolCall",
-    "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction",
     "QueryLogsByGroupQueryLogMessage",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -43,8 +41,6 @@
     "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "QueryLogsByGroupQueryLogTool",
-    "QueryLogsByGroupQueryLogToolFunction",
 ]
 
 
@@ -97,20 +93,6 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel):
-    arguments: str
-
-    name: str
-
-
-class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel):
-    id: str
-
-    function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction
-
-    type: Literal["function"]
-
-
 class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -305,22 +287,6 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod
 ]
 
 
-class QueryLogsByGroupQueryLogToolFunction(BaseModel):
-    name: str
-
-    description: Optional[str] = None
-
-    parameters: Optional[object] = None
-
-    strict: Optional[bool] = None
-
-
-class QueryLogsByGroupQueryLogTool(BaseModel):
-    function: QueryLogsByGroupQueryLogToolFunction
-
-    type: Literal["function"]
-
-
 class QueryLogsByGroupQueryLog(BaseModel):
     id: str
 
@@ -391,12 +357,6 @@ class QueryLogsByGroupQueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
-    evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None
-    """Tool calls from the evaluated response, if any.
-
-    Used to log tool calls in the query log.
-    """
-
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -423,12 +383,6 @@ class QueryLogsByGroupQueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
-    tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
-
 
 class QueryLogsByGroup(BaseModel):
     query_logs: List[QueryLogsByGroupQueryLog]
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 6ed4d146..cc3b208b 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -14,8 +14,6 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
-    "EvaluatedResponseToolCall",
-    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -41,8 +39,6 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "Tool",
-    "ToolFunction",
 ]
 
 
@@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class EvaluatedResponseToolCallFunction(BaseModel):
-    arguments: str
-
-    name: str
-
-
-class EvaluatedResponseToolCall(BaseModel):
-    id: str
-
-    function: EvaluatedResponseToolCallFunction
-
-    type: Literal["function"]
-
-
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
-class ToolFunction(BaseModel):
-    name: str
-
-    description: Optional[str] = None
-
-    parameters: Optional[object] = None
-
-    strict: Optional[bool] = None
-
-
-class Tool(BaseModel):
-    function: ToolFunction
-
-    type: Literal["function"]
-
-
 class QueryLogListGroupsResponse(BaseModel):
     id: str
 
@@ -381,12 +347,6 @@ class QueryLogListGroupsResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
-    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
-    """Tool calls from the evaluated response, if any.
-
-    Used to log tool calls in the query log.
-    """
-
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -412,9 +372,3 @@ class QueryLogListGroupsResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
-
-    tools: Optional[List[Tool]] = None
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index c6737b2f..0778898f 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -14,8 +14,6 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
-    "EvaluatedResponseToolCall",
-    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -41,8 +39,6 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "Tool",
-    "ToolFunction",
 ]
 
 
@@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class EvaluatedResponseToolCallFunction(BaseModel):
-    arguments: str
-
-    name: str
-
-
-class EvaluatedResponseToolCall(BaseModel):
-    id: str
-
-    function: EvaluatedResponseToolCallFunction
-
-    type: Literal["function"]
-
-
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
-class ToolFunction(BaseModel):
-    name: str
-
-    description: Optional[str] = None
-
-    parameters: Optional[object] = None
-
-    strict: Optional[bool] = None
-
-
-class Tool(BaseModel):
-    function: ToolFunction
-
-    type: Literal["function"]
-
-
 class QueryLogListResponse(BaseModel):
     id: str
 
@@ -375,12 +341,6 @@ class QueryLogListResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
-    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
-    """Tool calls from the evaluated response, if any.
-
-    Used to log tool calls in the query log.
-    """
-
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -406,9 +366,3 @@ class QueryLogListResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
-
-    tools: Optional[List[Tool]] = None
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 8fd8662e..2751ef21 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -14,8 +14,6 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
-    "EvaluatedResponseToolCall",
-    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -41,8 +39,6 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "Tool",
-    "ToolFunction",
 ]
 
 
@@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class EvaluatedResponseToolCallFunction(BaseModel):
-    arguments: str
-
-    name: str
-
-
-class EvaluatedResponseToolCall(BaseModel):
-    id: str
-
-    function: EvaluatedResponseToolCallFunction
-
-    type: Literal["function"]
-
-
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
-class ToolFunction(BaseModel):
-    name: str
-
-    description: Optional[str] = None
-
-    parameters: Optional[object] = None
-
-    strict: Optional[bool] = None
-
-
-class Tool(BaseModel):
-    function: ToolFunction
-
-    type: Literal["function"]
-
-
 class QueryLogRetrieveResponse(BaseModel):
     id: str
 
@@ -379,12 +345,6 @@ class QueryLogRetrieveResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
-    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
-    """Tool calls from the evaluated response, if any.
-
-    Used to log tool calls in the query log.
-    """
-
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -410,9 +370,3 @@ class QueryLogRetrieveResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
-
-    tools: Optional[List[Tool]] = None
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 567a0869..d56f9a41 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -15,8 +15,6 @@
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
     "QueryLogDeterministicGuardrailsResults",
-    "QueryLogEvaluatedResponseToolCall",
-    "QueryLogEvaluatedResponseToolCallFunction",
     "QueryLogMessage",
     "QueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -42,8 +40,6 @@
     "QueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
-    "QueryLogTool",
-    "QueryLogToolFunction",
 ]
 
 
@@ -96,20 +92,6 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
-class QueryLogEvaluatedResponseToolCallFunction(BaseModel):
-    arguments: str
-
-    name: str
-
-
-class QueryLogEvaluatedResponseToolCall(BaseModel):
-    id: str
-
-    function: QueryLogEvaluatedResponseToolCallFunction
-
-    type: Literal["function"]
-
-
 class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -302,22 +284,6 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
-class QueryLogToolFunction(BaseModel):
-    name: str
-
-    description: Optional[str] = None
-
-    parameters: Optional[object] = None
-
-    strict: Optional[bool] = None
-
-
-class QueryLogTool(BaseModel):
-    function: QueryLogToolFunction
-
-    type: Literal["function"]
-
-
 class QueryLog(BaseModel):
     id: str
 
@@ -382,12 +348,6 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
-    evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None
-    """Tool calls from the evaluated response, if any.
-
-    Used to log tool calls in the query log.
-    """
-
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -414,12 +374,6 @@ class QueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
-    tools: Optional[List[QueryLogTool]] = None
-    """Tools to use for the LLM call.
-
-    If not provided, it is assumed no tools were provided to the LLM.
-    """
-
 
 class RemediationListResolvedLogsResponse(BaseModel):
     query_logs: List[QueryLog]
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 8749c5ac..3c04bfc4 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -30,16 +30,17 @@ class TlmPromptParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"none"`.
-    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-      `reasoning_effort` = `"none"`.
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a faster self-reflection is employed.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -75,11 +76,12 @@ class TlmPromptParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -97,8 +99,6 @@ class TlmPromptParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
-
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -120,8 +120,6 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
-    num_self_reflections: int
-
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index 4a0a32ad..95bcc4c4 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -32,16 +32,17 @@ class TlmScoreParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"high"`.
-    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
-      `reasoning_effort` = `"none"`.
-    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
-      `reasoning_effort` = `"none"`.
+    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
+      `use_self_reflection` = True. This preset improves LLM responses.
+    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
+      `use_self_reflection` = True.
+    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
+      `use_self_reflection` = True.
+    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
+      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
+      "base" preset, a faster self-reflection is employed.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -77,11 +78,12 @@ class TlmScoreParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
+        and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -99,8 +101,6 @@ class TlmScoreParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
-
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -122,8 +122,6 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
-    num_self_reflections: int
-
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 7884db0f..ae3f4f05 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -622,7 +622,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -631,17 +630,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
-            tools=[
-                {
-                    "function": {
-                        "name": "name",
-                        "description": "description",
-                        "parameters": {},
-                        "strict": True,
-                    },
-                    "type": "function",
-                }
-            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -1296,7 +1284,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -1305,17 +1292,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
-            tools=[
-                {
-                    "function": {
-                        "name": "name",
-                        "description": "description",
-                        "parameters": {},
-                        "strict": True,
-                    },
-                    "type": "function",
-                }
-            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index da0a9ad1..41376a46 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -38,7 +38,6 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -97,7 +96,6 @@ def test_method_score_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -162,7 +160,6 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) ->
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -221,7 +218,6 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
-                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,

From 63ecb44534f3e4ae94ac9ee99ab9f89cc88295ce Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:18:00 +0000
Subject: [PATCH 214/227] feat(api): api update

---
 .stats.yml                                    |   2 +-
 src/codex/resources/projects/projects.py      |  74 +++++-----
 src/codex/resources/tlm.py                    | 128 +++++++++---------
 src/codex/types/project_validate_params.py    |  58 +++++---
 .../query_log_list_by_group_response.py       |  46 +++++++
 .../query_log_list_groups_response.py         |  46 +++++++
 .../types/projects/query_log_list_response.py |  46 +++++++
 .../projects/query_log_retrieve_response.py   |  46 +++++++
 ...remediation_list_resolved_logs_response.py |  46 +++++++
 src/codex/types/tlm_prompt_params.py          |  34 ++---
 src/codex/types/tlm_score_params.py           |  34 ++---
 tests/api_resources/test_projects.py          |  24 ++++
 tests/api_resources/test_tlm.py               |   4 +
 13 files changed, 443 insertions(+), 145 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 031dedf7..4f2aa488 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75
+openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 3a109edf..f82bcd03 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -460,6 +460,7 @@ def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
+        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -504,17 +505,16 @@ def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -550,12 +550,11 @@ def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -574,6 +573,8 @@ def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -582,6 +583,9 @@ def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
+          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
+              provided to the LLM.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -620,6 +624,7 @@ def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
+                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
@@ -1028,6 +1033,7 @@ async def validate(
         quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN,
         rewritten_question: Optional[str] | NotGiven = NOT_GIVEN,
         task: Optional[str] | NotGiven = NOT_GIVEN,
+        tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN,
         x_client_library_version: str | NotGiven = NOT_GIVEN,
         x_integration_type: str | NotGiven = NOT_GIVEN,
         x_source: str | NotGiven = NOT_GIVEN,
@@ -1072,17 +1078,16 @@ async def validate(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -1118,12 +1123,11 @@ async def validate(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -1142,6 +1146,8 @@ async def validate(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
 
@@ -1150,6 +1156,9 @@ async def validate(
           rewritten_question: The re-written query if it was provided by the client to Codex from a user to be
               used instead of the original query.
 
+          tools: Tools to use for the LLM call. If not provided, it is assumed no tools were
+              provided to the LLM.
+
           extra_headers: Send extra headers
 
           extra_query: Add additional query parameters to the request
@@ -1188,6 +1197,7 @@ async def validate(
                     "quality_preset": quality_preset,
                     "rewritten_question": rewritten_question,
                     "task": task,
+                    "tools": tools,
                 },
                 project_validate_params.ProjectValidateParams,
             ),
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index 12ff6c0d..c6064ed6 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -79,17 +79,16 @@ def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -125,12 +124,11 @@ def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -149,6 +147,8 @@ def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -217,17 +217,16 @@ def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -263,12 +262,11 @@ def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -287,6 +285,8 @@ def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -371,17 +371,16 @@ async def prompt(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -417,12 +416,11 @@ async def prompt(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -441,6 +439,8 @@ async def prompt(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
@@ -509,17 +509,16 @@ async def score(
 
               The default values corresponding to each quality preset are:
 
-              - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-                `use_self_reflection` = True. This preset improves LLM responses.
-              - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-                `use_self_reflection` = True.
-              - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-                `use_self_reflection` = True.
-              - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-                `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-                "base" preset, a faster self-reflection is employed.
+              - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"high"`.
+              - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+                `reasoning_effort` = `"none"`.
+              - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+                `reasoning_effort` = `"none"`.
 
               By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
               `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -555,12 +554,11 @@ async def score(
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-                  use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-                  and catches responses that are noticeably incorrect/bad upon further analysis.
+                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+                  similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -579,6 +577,8 @@ async def score(
                   - name: Name of the evaluation criteria.
                   - criteria: Instructions specifying the evaluation criteria.
 
+                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
           extra_headers: Send extra headers
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 081dd2a5..62313671 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -54,6 +54,8 @@
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
     "Options",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -106,17 +108,16 @@ class ProjectValidateParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -152,12 +153,11 @@ class ProjectValidateParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -175,6 +175,8 @@ class ProjectValidateParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     prompt: Optional[str]
@@ -194,6 +196,12 @@ class ProjectValidateParams(TypedDict, total=False):
 
     task: Optional[str]
 
+    tools: Optional[Iterable[Tool]]
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
     x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
 
     x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")]
@@ -649,8 +657,26 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
 
     use_self_reflection: bool
+
+
+class ToolFunction(TypedDict, total=False):
+    name: Required[str]
+
+    description: str
+
+    parameters: object
+
+    strict: Optional[bool]
+
+
+class Tool(TypedDict, total=False):
+    function: Required[ToolFunction]
+
+    type: Required[Literal["function"]]
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index ae49b954..b3c774ba 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -16,6 +16,8 @@
     "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores",
     "QueryLogsByGroupQueryLogContext",
     "QueryLogsByGroupQueryLogDeterministicGuardrailsResults",
+    "QueryLogsByGroupQueryLogEvaluatedResponseToolCall",
+    "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction",
     "QueryLogsByGroupQueryLogMessage",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -41,6 +43,8 @@
     "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogsByGroupQueryLogTool",
+    "QueryLogsByGroupQueryLogToolFunction",
 ]
 
 
@@ -93,6 +97,20 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -287,6 +305,22 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod
 ]
 
 
+class QueryLogsByGroupQueryLogToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class QueryLogsByGroupQueryLogTool(BaseModel):
+    function: QueryLogsByGroupQueryLogToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogsByGroupQueryLog(BaseModel):
     id: str
 
@@ -357,6 +391,12 @@ class QueryLogsByGroupQueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -383,6 +423,12 @@ class QueryLogsByGroupQueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
+    tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
 
 class QueryLogsByGroup(BaseModel):
     query_logs: List[QueryLogsByGroupQueryLog]
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index cc3b208b..6ed4d146 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogListGroupsResponse(BaseModel):
     id: str
 
@@ -347,6 +381,12 @@ class QueryLogListGroupsResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -372,3 +412,9 @@ class QueryLogListGroupsResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index 0778898f..c6737b2f 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogListResponse(BaseModel):
     id: str
 
@@ -341,6 +375,12 @@ class QueryLogListResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -366,3 +406,9 @@ class QueryLogListResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 2751ef21..8fd8662e 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -14,6 +14,8 @@
     "FormattedNonGuardrailEvalScores",
     "Context",
     "DeterministicGuardrailsResults",
+    "EvaluatedResponseToolCall",
+    "EvaluatedResponseToolCallFunction",
     "Message",
     "MessageChatCompletionAssistantMessageParamOutput",
     "MessageChatCompletionAssistantMessageParamOutputAudio",
@@ -39,6 +41,8 @@
     "MessageChatCompletionFunctionMessageParam",
     "MessageChatCompletionDeveloperMessageParam",
     "MessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "Tool",
+    "ToolFunction",
 ]
 
 
@@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class EvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class EvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: EvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class ToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class Tool(BaseModel):
+    function: ToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLogRetrieveResponse(BaseModel):
     id: str
 
@@ -345,6 +379,12 @@ class QueryLogRetrieveResponse(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -370,3 +410,9 @@ class QueryLogRetrieveResponse(BaseModel):
 
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
+
+    tools: Optional[List[Tool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index d56f9a41..567a0869 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -15,6 +15,8 @@
     "QueryLogFormattedNonGuardrailEvalScores",
     "QueryLogContext",
     "QueryLogDeterministicGuardrailsResults",
+    "QueryLogEvaluatedResponseToolCall",
+    "QueryLogEvaluatedResponseToolCallFunction",
     "QueryLogMessage",
     "QueryLogMessageChatCompletionAssistantMessageParamOutput",
     "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio",
@@ -40,6 +42,8 @@
     "QueryLogMessageChatCompletionFunctionMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParam",
     "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1",
+    "QueryLogTool",
+    "QueryLogToolFunction",
 ]
 
 
@@ -92,6 +96,20 @@ class QueryLogDeterministicGuardrailsResults(BaseModel):
     matches: Optional[List[str]] = None
 
 
+class QueryLogEvaluatedResponseToolCallFunction(BaseModel):
+    arguments: str
+
+    name: str
+
+
+class QueryLogEvaluatedResponseToolCall(BaseModel):
+    id: str
+
+    function: QueryLogEvaluatedResponseToolCallFunction
+
+    type: Literal["function"]
+
+
 class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel):
     id: str
 
@@ -284,6 +302,22 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel):
 ]
 
 
+class QueryLogToolFunction(BaseModel):
+    name: str
+
+    description: Optional[str] = None
+
+    parameters: Optional[object] = None
+
+    strict: Optional[bool] = None
+
+
+class QueryLogTool(BaseModel):
+    function: QueryLogToolFunction
+
+    type: Literal["function"]
+
+
 class QueryLog(BaseModel):
     id: str
 
@@ -348,6 +382,12 @@ class QueryLog(BaseModel):
     evaluated_response: Optional[str] = None
     """The response being evaluated from the RAG system (before any remediation)"""
 
+    evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None
+    """Tool calls from the evaluated response, if any.
+
+    Used to log tool calls in the query log.
+    """
+
     guardrail_evals: Optional[List[str]] = None
     """Evals that should trigger guardrail"""
 
@@ -374,6 +414,12 @@ class QueryLog(BaseModel):
     primary_eval_issue_score: Optional[float] = None
     """Score of the primary eval issue"""
 
+    tools: Optional[List[QueryLogTool]] = None
+    """Tools to use for the LLM call.
+
+    If not provided, it is assumed no tools were provided to the LLM.
+    """
+
 
 class RemediationListResolvedLogsResponse(BaseModel):
     query_logs: List[QueryLog]
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 3c04bfc4..8749c5ac 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -30,17 +30,16 @@ class TlmPromptParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -76,12 +75,11 @@ class TlmPromptParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -99,6 +97,8 @@ class TlmPromptParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -120,6 +120,8 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index 95bcc4c4..4a0a32ad 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -32,17 +32,16 @@ class TlmScoreParams(TypedDict, total=False):
 
     The default values corresponding to each quality preset are:
 
-    - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8,
-      `use_self_reflection` = True. This preset improves LLM responses.
-    - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8,
-      `use_self_reflection` = True.
-    - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4,
-      `use_self_reflection` = True.
-    - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0,
-      `use_self_reflection` = False. When using `get_trustworthiness_score()` on
-      "base" preset, a faster self-reflection is employed.
+    - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"high"`.
+    - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3,
+      `reasoning_effort` = `"none"`.
+    - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1,
+      `reasoning_effort` = `"none"`.
 
     By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base
     `model`, and `max_tokens` is set to 512. You can set custom values for these
@@ -78,12 +77,11 @@ class TlmScoreParams(TypedDict, total=False):
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
 
-        use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts
-        and catches responses that are noticeably incorrect/bad upon further analysis.
+        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
+        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
 
-        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the
+        similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
@@ -101,6 +99,8 @@ class TlmScoreParams(TypedDict, total=False):
         The expected input format is a list of dictionaries, where each dictionary has the following keys:
         - name: Name of the evaluation criteria.
         - criteria: Instructions specifying the evaluation criteria.
+
+        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -122,6 +122,8 @@ class Options(TypedDict, total=False):
 
     num_consistency_samples: int
 
+    num_self_reflections: int
+
     reasoning_effort: str
 
     similarity_measure: str
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index ae3f4f05..7884db0f 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -622,6 +622,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -630,6 +631,17 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
+            tools=[
+                {
+                    "function": {
+                        "name": "name",
+                        "description": "description",
+                        "parameters": {},
+                        "strict": True,
+                    },
+                    "type": "function",
+                }
+            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
@@ -1284,6 +1296,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -1292,6 +1305,17 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             quality_preset="best",
             rewritten_question="rewritten_question",
             task="task",
+            tools=[
+                {
+                    "function": {
+                        "name": "name",
+                        "description": "description",
+                        "parameters": {},
+                        "strict": True,
+                    },
+                    "type": "function",
+                }
+            ],
             x_client_library_version="x-client-library-version",
             x_integration_type="x-integration-type",
             x_source="x-source",
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index 41376a46..da0a9ad1 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -38,6 +38,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -96,6 +97,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None:
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -160,6 +162,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) ->
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,
@@ -218,6 +221,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N
                 "model": "model",
                 "num_candidate_responses": 0,
                 "num_consistency_samples": 0,
+                "num_self_reflections": 0,
                 "reasoning_effort": "reasoning_effort",
                 "similarity_measure": "similarity_measure",
                 "use_self_reflection": True,

From e132c148f2f60fde5e152b544ca62f62856df95a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:21:09 +0000
Subject: [PATCH 215/227] chore(internal): version bump

---
 .release-please-manifest.json | 2 +-
 pyproject.toml                | 2 +-
 src/codex/_version.py         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 1c0bb885..380b6f91 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0-alpha.23"
+  ".": "0.1.0-alpha.24"
 }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 0e315f1b..6a941cf3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "codex-sdk"
-version = "0.1.0-alpha.23"
+version = "0.1.0-alpha.24"
 description = "The official Python library for the Codex API"
 dynamic = ["readme"]
 license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 18f2dcb7..e020cb91 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
 # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
 
 __title__ = "codex"
-__version__ = "0.1.0-alpha.23"  # x-release-please-version
+__version__ = "0.1.0-alpha.24"  # x-release-please-version

From 8bdd5d3687dbc60f6193d409ffac8524505b8480 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 29 Jul 2025 23:18:01 +0000
Subject: [PATCH 216/227] feat(api): api update

---
 .stats.yml                                   | 2 +-
 src/codex/resources/projects/projects.py     | 4 ++--
 src/codex/types/project_invite_sme_params.py | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 4f2aa488..9c3265d2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d
+openapi_spec_hash: c37472afd4b354d9a4e358b9dc6fd1a8
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index f82bcd03..83ff4421 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -352,7 +352,7 @@ def invite_sme(
         project_id: str,
         *,
         email: str,
-        page_type: Literal["query_log", "remediation"],
+        page_type: Literal["query_log", "remediation", "prioritized_issue"],
         url_query_string: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -925,7 +925,7 @@ async def invite_sme(
         project_id: str,
         *,
         email: str,
-        page_type: Literal["query_log", "remediation"],
+        page_type: Literal["query_log", "remediation", "prioritized_issue"],
         url_query_string: str,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
diff --git a/src/codex/types/project_invite_sme_params.py b/src/codex/types/project_invite_sme_params.py
index f2694632..974ef7c3 100644
--- a/src/codex/types/project_invite_sme_params.py
+++ b/src/codex/types/project_invite_sme_params.py
@@ -10,6 +10,6 @@
 class ProjectInviteSmeParams(TypedDict, total=False):
     email: Required[str]
 
-    page_type: Required[Literal["query_log", "remediation"]]
+    page_type: Required[Literal["query_log", "remediation", "prioritized_issue"]]
 
     url_query_string: Required[str]

From 47c55ecf735ba66b6e69351413b66e3cbdf569ef Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 30 Jul 2025 18:17:53 +0000
Subject: [PATCH 217/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/query_logs.py    | 24 +++++++++++++++++++
 .../query_log_list_by_group_params.py         |  3 +++
 .../query_log_list_by_group_response.py       |  6 +++++
 .../projects/query_log_list_groups_params.py  |  3 +++
 .../query_log_list_groups_response.py         |  2 ++
 .../types/projects/query_log_list_params.py   |  3 +++
 .../types/projects/query_log_list_response.py |  2 ++
 .../projects/query_log_retrieve_response.py   |  2 ++
 ...remediation_list_resolved_logs_response.py |  2 ++
 .../api_resources/projects/test_query_logs.py |  6 +++++
 11 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 9c3265d2..6adad987 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: c37472afd4b354d9a4e358b9dc6fd1a8
+openapi_spec_hash: 6b4e0d5289d21e9bcda264caea7f03d8
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 6fa490e8..268d3ac1 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -109,6 +109,7 @@ def list(
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -135,6 +136,8 @@ def list(
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -168,6 +171,7 @@ def list(
                         "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_params.QueryLogListParams,
@@ -196,6 +200,7 @@ def list_by_group(
         | NotGiven = NOT_GIVEN,
         remediation_ids: List[str] | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -226,6 +231,8 @@ def list_by_group(
 
           remediation_ids: List of groups to list child logs for
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -260,6 +267,7 @@ def list_by_group(
                         "primary_eval_issue": primary_eval_issue,
                         "remediation_ids": remediation_ids,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_by_group_params.QueryLogListByGroupParams,
@@ -288,6 +296,7 @@ def list_groups(
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
         | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -316,6 +325,8 @@ def list_groups(
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -350,6 +361,7 @@ def list_groups(
                         "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_groups_params.QueryLogListGroupsParams,
@@ -469,6 +481,7 @@ def list(
         ]
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -495,6 +508,8 @@ def list(
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -528,6 +543,7 @@ def list(
                         "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_params.QueryLogListParams,
@@ -556,6 +572,7 @@ async def list_by_group(
         | NotGiven = NOT_GIVEN,
         remediation_ids: List[str] | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -586,6 +603,8 @@ async def list_by_group(
 
           remediation_ids: List of groups to list child logs for
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -620,6 +639,7 @@ async def list_by_group(
                         "primary_eval_issue": primary_eval_issue,
                         "remediation_ids": remediation_ids,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_by_group_params.QueryLogListByGroupParams,
@@ -648,6 +668,7 @@ def list_groups(
         | NotGiven = NOT_GIVEN,
         sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
         | NotGiven = NOT_GIVEN,
+        tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN,
         was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN,
         # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
         # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -676,6 +697,8 @@ def list_groups(
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
 
+          tool_call_names: Filter by names of tools called in the assistant response
+
           was_cache_hit: Filter by cache hit status
 
           extra_headers: Send extra headers
@@ -710,6 +733,7 @@ def list_groups(
                         "passed_evals": passed_evals,
                         "primary_eval_issue": primary_eval_issue,
                         "sort": sort,
+                        "tool_call_names": tool_call_names,
                         "was_cache_hit": was_cache_hit,
                     },
                     query_log_list_groups_params.QueryLogListGroupsParams,
diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py
index 90bd3867..74edd4b2 100644
--- a/src/codex/types/projects/query_log_list_by_group_params.py
+++ b/src/codex/types/projects/query_log_list_by_group_params.py
@@ -49,5 +49,8 @@ class QueryLogListByGroupParams(TypedDict, total=False):
 
     sort: Optional[Literal["created_at", "primary_eval_issue_score"]]
 
+    tool_call_names: Optional[List[str]]
+    """Filter by names of tools called in the assistant response"""
+
     was_cache_hit: Optional[bool]
     """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py
index b3c774ba..fc33cdeb 100644
--- a/src/codex/types/projects/query_log_list_by_group_response.py
+++ b/src/codex/types/projects/query_log_list_by_group_response.py
@@ -358,6 +358,8 @@ class QueryLogsByGroupQueryLog(BaseModel):
 
     remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
+    tool_call_names: Optional[List[str]] = None
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
@@ -438,5 +440,9 @@ class QueryLogsByGroup(BaseModel):
 
 class QueryLogListByGroupResponse(BaseModel):
     custom_metadata_columns: List[str]
+    """Columns of the custom metadata"""
 
     query_logs_by_group: Dict[str, QueryLogsByGroup]
+
+    tool_names: Optional[List[str]] = None
+    """Names of the tools available in queries"""
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
index f75ee299..5461b541 100644
--- a/src/codex/types/projects/query_log_list_groups_params.py
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -46,5 +46,8 @@ class QueryLogListGroupsParams(TypedDict, total=False):
 
     sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]]
 
+    tool_call_names: Optional[List[str]]
+    """Filter by names of tools called in the assistant response"""
+
     was_cache_hit: Optional[bool]
     """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 6ed4d146..60545aaa 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -346,6 +346,8 @@ class QueryLogListGroupsResponse(BaseModel):
 
     remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
+    tool_call_names: Optional[List[str]] = None
+
     total_count: int
 
     was_cache_hit: Optional[bool] = None
diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py
index 5892d3c9..2f198acb 100644
--- a/src/codex/types/projects/query_log_list_params.py
+++ b/src/codex/types/projects/query_log_list_params.py
@@ -43,5 +43,8 @@ class QueryLogListParams(TypedDict, total=False):
 
     sort: Optional[Literal["created_at", "primary_eval_issue_score"]]
 
+    tool_call_names: Optional[List[str]]
+    """Filter by names of tools called in the assistant response"""
+
     was_cache_hit: Optional[bool]
     """Filter by cache hit status"""
diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py
index c6737b2f..b56d43d3 100644
--- a/src/codex/types/projects/query_log_list_response.py
+++ b/src/codex/types/projects/query_log_list_response.py
@@ -342,6 +342,8 @@ class QueryLogListResponse(BaseModel):
 
     remediation_id: str
 
+    tool_call_names: Optional[List[str]] = None
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py
index 8fd8662e..b9be8d6d 100644
--- a/src/codex/types/projects/query_log_retrieve_response.py
+++ b/src/codex/types/projects/query_log_retrieve_response.py
@@ -346,6 +346,8 @@ class QueryLogRetrieveResponse(BaseModel):
 
     remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"]
 
+    tool_call_names: Optional[List[str]] = None
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py
index 567a0869..ed764766 100644
--- a/src/codex/types/projects/remediation_list_resolved_logs_response.py
+++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py
@@ -349,6 +349,8 @@ class QueryLog(BaseModel):
 
     remediation_id: str
 
+    tool_call_names: Optional[List[str]] = None
+
     was_cache_hit: Optional[bool] = None
     """If similar query already answered, or None if cache was not checked"""
 
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index cd4cd7d2..d98cdc9c 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -106,6 +106,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
@@ -170,6 +171,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
             primary_eval_issue=["hallucination"],
             remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
@@ -233,6 +235,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None:
             passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
@@ -405,6 +408,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
@@ -469,6 +473,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod
             primary_eval_issue=["hallucination"],
             remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
@@ -532,6 +537,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex
             passed_evals=["string"],
             primary_eval_issue=["hallucination"],
             sort="created_at",
+            tool_call_names=["string"],
             was_cache_hit=True,
         )
         assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])

From b57b54b1b29e7bf6e4c82208527f65950c17a114 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 03:41:41 +0000
Subject: [PATCH 218/227] feat(client): support file upload requests

---
 src/codex/_base_client.py | 5 ++++-
 src/codex/_files.py       | 8 ++++----
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 6da89f6c..870a4729 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -532,7 +532,10 @@ def _build_request(
         is_body_allowed = options.method.lower() != "get"
 
         if is_body_allowed:
-            kwargs["json"] = json_data if is_given(json_data) else None
+            if isinstance(json_data, bytes):
+                kwargs["content"] = json_data
+            else:
+                kwargs["json"] = json_data if is_given(json_data) else None
             kwargs["files"] = files
         else:
             headers.pop("Content-Type", None)
diff --git a/src/codex/_files.py b/src/codex/_files.py
index 715cc207..cc14c14f 100644
--- a/src/codex/_files.py
+++ b/src/codex/_files.py
@@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes:
         return file
 
     if is_tuple_t(file):
-        return (file[0], _read_file_content(file[1]), *file[2:])
+        return (file[0], read_file_content(file[1]), *file[2:])
 
     raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
 
 
-def _read_file_content(file: FileContent) -> HttpxFileContent:
+def read_file_content(file: FileContent) -> HttpxFileContent:
     if isinstance(file, os.PathLike):
         return pathlib.Path(file).read_bytes()
     return file
@@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
         return file
 
     if is_tuple_t(file):
-        return (file[0], await _async_read_file_content(file[1]), *file[2:])
+        return (file[0], await async_read_file_content(file[1]), *file[2:])
 
     raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
 
 
-async def _async_read_file_content(file: FileContent) -> HttpxFileContent:
+async def async_read_file_content(file: FileContent) -> HttpxFileContent:
     if isinstance(file, os.PathLike):
         return await anyio.Path(file).read_bytes()
 

From 4bee8fb085790a6dcaf4e9cfde9e09f77493b739 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 17:17:52 +0000
Subject: [PATCH 219/227] feat(api): api update

---
 .stats.yml                                    |  2 +-
 src/codex/resources/projects/query_logs.py    | 24 +++++++++++++++++++
 .../query_log_list_by_group_params.py         |  3 +++
 .../projects/query_log_list_groups_params.py  |  3 +++
 .../query_log_list_groups_response.py         |  3 +++
 .../types/projects/query_log_list_params.py   |  3 +++
 .../api_resources/projects/test_query_logs.py |  6 +++++
 7 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 6adad987..761389b9 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 6b4e0d5289d21e9bcda264caea7f03d8
+openapi_spec_hash: 928298b295109c995a0b3a8679b441b7
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py
index 268d3ac1..45277433 100644
--- a/src/codex/resources/projects/query_logs.py
+++ b/src/codex/resources/projects/query_logs.py
@@ -100,6 +100,7 @@ def list(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
@@ -132,6 +133,8 @@ def list(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           passed_evals: Filter by evals that passed
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
@@ -165,6 +168,7 @@ def list(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
@@ -189,6 +193,7 @@ def list_by_group(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
@@ -223,6 +228,8 @@ def list_by_group(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           needs_review: Filter logs that need review
 
           passed_evals: Filter by evals that passed
@@ -259,6 +266,7 @@ def list_by_group(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "needs_review": needs_review,
                         "offset": offset,
@@ -285,6 +293,7 @@ def list_groups(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
@@ -319,6 +328,8 @@ def list_groups(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           needs_review: Filter log groups that need review
 
           passed_evals: Filter by evals that passed
@@ -354,6 +365,7 @@ def list_groups(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "needs_review": needs_review,
                         "offset": offset,
@@ -472,6 +484,7 @@ def list(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
         order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
@@ -504,6 +517,8 @@ def list(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           passed_evals: Filter by evals that passed
 
           primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation)
@@ -537,6 +552,7 @@ def list(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "offset": offset,
                         "order": order,
@@ -561,6 +577,7 @@ async def list_by_group(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
@@ -595,6 +612,8 @@ async def list_by_group(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           needs_review: Filter logs that need review
 
           passed_evals: Filter by evals that passed
@@ -631,6 +650,7 @@ async def list_by_group(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "needs_review": needs_review,
                         "offset": offset,
@@ -657,6 +677,7 @@ def list_groups(
         custom_metadata: Optional[str] | NotGiven = NOT_GIVEN,
         failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN,
         guardrailed: Optional[bool] | NotGiven = NOT_GIVEN,
+        has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
         limit: int | NotGiven = NOT_GIVEN,
         needs_review: Optional[bool] | NotGiven = NOT_GIVEN,
         offset: int | NotGiven = NOT_GIVEN,
@@ -691,6 +712,8 @@ def list_groups(
 
           guardrailed: Filter by guardrailed status
 
+          has_tool_calls: Filter by whether the query log has tool calls
+
           needs_review: Filter log groups that need review
 
           passed_evals: Filter by evals that passed
@@ -726,6 +749,7 @@ def list_groups(
                         "custom_metadata": custom_metadata,
                         "failed_evals": failed_evals,
                         "guardrailed": guardrailed,
+                        "has_tool_calls": has_tool_calls,
                         "limit": limit,
                         "needs_review": needs_review,
                         "offset": offset,
diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py
index 74edd4b2..0fbb2804 100644
--- a/src/codex/types/projects/query_log_list_by_group_params.py
+++ b/src/codex/types/projects/query_log_list_by_group_params.py
@@ -27,6 +27,9 @@ class QueryLogListByGroupParams(TypedDict, total=False):
     guardrailed: Optional[bool]
     """Filter by guardrailed status"""
 
+    has_tool_calls: Optional[bool]
+    """Filter by whether the query log has tool calls"""
+
     limit: int
 
     needs_review: Optional[bool]
diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py
index 5461b541..6adefdf5 100644
--- a/src/codex/types/projects/query_log_list_groups_params.py
+++ b/src/codex/types/projects/query_log_list_groups_params.py
@@ -27,6 +27,9 @@ class QueryLogListGroupsParams(TypedDict, total=False):
     guardrailed: Optional[bool]
     """Filter by guardrailed status"""
 
+    has_tool_calls: Optional[bool]
+    """Filter by whether the query log has tool calls"""
+
     limit: int
 
     needs_review: Optional[bool]
diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py
index 60545aaa..7b2d44c9 100644
--- a/src/codex/types/projects/query_log_list_groups_response.py
+++ b/src/codex/types/projects/query_log_list_groups_response.py
@@ -314,6 +314,9 @@ class Tool(BaseModel):
 class QueryLogListGroupsResponse(BaseModel):
     id: str
 
+    any_escalated: bool
+    """Whether any query log in the group was escalated"""
+
     created_at: datetime
 
     formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None
diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py
index 2f198acb..02c1707b 100644
--- a/src/codex/types/projects/query_log_list_params.py
+++ b/src/codex/types/projects/query_log_list_params.py
@@ -27,6 +27,9 @@ class QueryLogListParams(TypedDict, total=False):
     guardrailed: Optional[bool]
     """Filter by guardrailed status"""
 
+    has_tool_calls: Optional[bool]
+    """Filter by whether the query log has tool calls"""
+
     limit: int
 
     offset: int
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index d98cdc9c..3e0a96fa 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -100,6 +100,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             offset=0,
             order="asc",
@@ -163,6 +164,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             needs_review=True,
             offset=0,
@@ -228,6 +230,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None:
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             needs_review=True,
             offset=0,
@@ -402,6 +405,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             offset=0,
             order="asc",
@@ -465,6 +469,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             needs_review=True,
             offset=0,
@@ -530,6 +535,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex
             custom_metadata="custom_metadata",
             failed_evals=["string"],
             guardrailed=True,
+            has_tool_calls=True,
             limit=1,
             needs_review=True,
             offset=0,

From 43885ef093390608810614df222c61ac9fabfc04 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 6 Aug 2025 04:30:12 +0000
Subject: [PATCH 220/227] chore(internal): fix ruff target version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6a941cf3..cc023e90 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -159,7 +159,7 @@ reportPrivateUsage = false
 [tool.ruff]
 line-length = 120
 output-format = "grouped"
-target-version = "py37"
+target-version = "py38"
 
 [tool.ruff.format]
 docstring-code-format = true

From 09d3441a8e1eae47a5a39b6c4ef4440ea0ca4ff1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 7 Aug 2025 22:17:54 +0000
Subject: [PATCH 221/227] feat(api): api update

---
 .stats.yml                                 | 2 +-
 src/codex/resources/projects/projects.py   | 4 ++--
 src/codex/types/project_validate_params.py | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 761389b9..3aa68e69 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 928298b295109c995a0b3a8679b441b7
+openapi_spec_hash: d01153406f196329a5d5d1efd8f3e50e
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 83ff4421..d21f7937 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -449,7 +449,7 @@ def validate(
         context: str,
         query: str,
         response: project_validate_params.Response,
-        use_llm_matching: bool | NotGiven = NOT_GIVEN,
+        use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
@@ -1022,7 +1022,7 @@ async def validate(
         context: str,
         query: str,
         response: project_validate_params.Response,
-        use_llm_matching: bool | NotGiven = NOT_GIVEN,
+        use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN,
         constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN,
         custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN,
         custom_metadata: Optional[object] | NotGiven = NOT_GIVEN,
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 62313671..86c10495 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -66,7 +66,7 @@ class ProjectValidateParams(TypedDict, total=False):
 
     response: Required[Response]
 
-    use_llm_matching: bool
+    use_llm_matching: Optional[bool]
 
     constrain_outputs: Optional[List[str]]
 

From 705ef5ab013b45b2ef0749b8a9d3bb0e13013ce9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 9 Aug 2025 03:09:37 +0000
Subject: [PATCH 222/227] chore: update @stainless-api/prism-cli to v5.15.0

---
 scripts/mock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/mock b/scripts/mock
index d2814ae6..0b28f6ea 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}"
 
 # Run prism mock on the given spec
 if [ "$1" == "--daemon" ]; then
-  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log &
+  npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log &
 
   # Wait for server to come online
   echo -n "Waiting for server"
@@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then
 
   echo
 else
-  npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL"
+  npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL"
 fi

From 58e840d518d63c1ae838b1e8781aef4590204e6f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 9 Aug 2025 03:12:54 +0000
Subject: [PATCH 223/227] chore(internal): update comment in script

---
 scripts/test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/test b/scripts/test
index 2b878456..dbeda2d2 100755
--- a/scripts/test
+++ b/scripts/test
@@ -43,7 +43,7 @@ elif ! prism_is_running ; then
   echo -e "To run the server, pass in the path or url of your OpenAPI"
   echo -e "spec to the prism command:"
   echo
-  echo -e "  \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
+  echo -e "  \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}"
   echo
 
   exit 1

From 81619adf7a2fe293f8bc83cc2b25ce57b79cb893 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 12 Aug 2025 02:08:43 +0000
Subject: [PATCH 224/227] chore(internal): codegen related update

---
 .../billing/test_card_details.py              |  16 +-
 .../billing/test_plan_details.py              |  16 +-
 .../billing/test_setup_intent.py              |  16 +-
 .../organizations/test_billing.py             |  32 +--
 .../projects/test_access_keys.py              | 116 +++++------
 tests/api_resources/projects/test_evals.py    |  96 ++++-----
 .../api_resources/projects/test_query_logs.py |  92 ++++-----
 .../projects/test_remediations.py             | 184 +++++++++---------
 tests/api_resources/test_health.py            |  24 +--
 tests/api_resources/test_organizations.py     |  48 ++---
 tests/api_resources/test_projects.py          | 156 +++++++--------
 tests/api_resources/test_tlm.py               |  32 +--
 tests/api_resources/test_users.py             |  16 +-
 .../users/myself/test_api_key.py              |  24 +--
 .../users/myself/test_organizations.py        |  12 +-
 tests/api_resources/users/test_myself.py      |  12 +-
 .../api_resources/users/test_verification.py  |  12 +-
 17 files changed, 452 insertions(+), 452 deletions(-)

diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py
index 3a034833..e4468456 100644
--- a/tests/api_resources/organizations/billing/test_card_details.py
+++ b/tests/api_resources/organizations/billing/test_card_details.py
@@ -17,7 +17,7 @@
 class TestCardDetails:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         card_detail = client.organizations.billing.card_details.retrieve(
@@ -25,7 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.organizations.billing.card_details.with_raw_response.retrieve(
@@ -37,7 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         card_detail = response.parse()
         assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.organizations.billing.card_details.with_streaming_response.retrieve(
@@ -51,7 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -65,7 +65,7 @@ class TestAsyncCardDetails:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         card_detail = await async_client.organizations.billing.card_details.retrieve(
@@ -73,7 +73,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.card_details.with_raw_response.retrieve(
@@ -85,7 +85,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         card_detail = await response.parse()
         assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.card_details.with_streaming_response.retrieve(
@@ -99,7 +99,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py
index 76d9732e..1e3c36fb 100644
--- a/tests/api_resources/organizations/billing/test_plan_details.py
+++ b/tests/api_resources/organizations/billing/test_plan_details.py
@@ -17,7 +17,7 @@
 class TestPlanDetails:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         plan_detail = client.organizations.billing.plan_details.retrieve(
@@ -25,7 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.organizations.billing.plan_details.with_raw_response.retrieve(
@@ -37,7 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         plan_detail = response.parse()
         assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.organizations.billing.plan_details.with_streaming_response.retrieve(
@@ -51,7 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -65,7 +65,7 @@ class TestAsyncPlanDetails:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         plan_detail = await async_client.organizations.billing.plan_details.retrieve(
@@ -73,7 +73,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.plan_details.with_raw_response.retrieve(
@@ -85,7 +85,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         plan_detail = await response.parse()
         assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.plan_details.with_streaming_response.retrieve(
@@ -99,7 +99,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py
index 49d80b0d..edc3372b 100644
--- a/tests/api_resources/organizations/billing/test_setup_intent.py
+++ b/tests/api_resources/organizations/billing/test_setup_intent.py
@@ -17,7 +17,7 @@
 class TestSetupIntent:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         setup_intent = client.organizations.billing.setup_intent.create(
@@ -25,7 +25,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.organizations.billing.setup_intent.with_raw_response.create(
@@ -37,7 +37,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         setup_intent = response.parse()
         assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.organizations.billing.setup_intent.with_streaming_response.create(
@@ -51,7 +51,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_create(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -65,7 +65,7 @@ class TestAsyncSetupIntent:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         setup_intent = await async_client.organizations.billing.setup_intent.create(
@@ -73,7 +73,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.setup_intent.with_raw_response.create(
@@ -85,7 +85,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         setup_intent = await response.parse()
         assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.setup_intent.with_streaming_response.create(
@@ -99,7 +99,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_create(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py
index 237562b5..f13fa304 100644
--- a/tests/api_resources/organizations/test_billing.py
+++ b/tests/api_resources/organizations/test_billing.py
@@ -17,7 +17,7 @@
 class TestBilling:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_invoices(self, client: Codex) -> None:
         billing = client.organizations.billing.invoices(
@@ -25,7 +25,7 @@ def test_method_invoices(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_invoices(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.invoices(
@@ -37,7 +37,7 @@ def test_raw_response_invoices(self, client: Codex) -> None:
         billing = response.parse()
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_invoices(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.invoices(
@@ -51,7 +51,7 @@ def test_streaming_response_invoices(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_invoices(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -59,7 +59,7 @@ def test_path_params_invoices(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_usage(self, client: Codex) -> None:
         billing = client.organizations.billing.usage(
@@ -67,7 +67,7 @@ def test_method_usage(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_usage(self, client: Codex) -> None:
         response = client.organizations.billing.with_raw_response.usage(
@@ -79,7 +79,7 @@ def test_raw_response_usage(self, client: Codex) -> None:
         billing = response.parse()
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_usage(self, client: Codex) -> None:
         with client.organizations.billing.with_streaming_response.usage(
@@ -93,7 +93,7 @@ def test_streaming_response_usage(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_usage(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -107,7 +107,7 @@ class TestAsyncBilling:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_invoices(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.invoices(
@@ -115,7 +115,7 @@ async def test_method_invoices(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.invoices(
@@ -127,7 +127,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None:
         billing = await response.parse()
         assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.invoices(
@@ -141,7 +141,7 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -149,7 +149,7 @@ async def test_path_params_invoices(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_usage(self, async_client: AsyncCodex) -> None:
         billing = await async_client.organizations.billing.usage(
@@ -157,7 +157,7 @@ async def test_method_usage(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.billing.with_raw_response.usage(
@@ -169,7 +169,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None:
         billing = await response.parse()
         assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.billing.with_streaming_response.usage(
@@ -183,7 +183,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_usage(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py
index c3bc1785..13fc60ca 100644
--- a/tests/api_resources/projects/test_access_keys.py
+++ b/tests/api_resources/projects/test_access_keys.py
@@ -22,7 +22,7 @@
 class TestAccessKeys:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
@@ -31,7 +31,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.create(
@@ -46,7 +46,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.create(
@@ -59,7 +59,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.create(
@@ -74,7 +74,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_create(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -83,7 +83,7 @@ def test_path_params_create(self, client: Codex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve(
@@ -92,7 +92,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve(
@@ -105,7 +105,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve(
@@ -120,7 +120,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -135,7 +135,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
@@ -145,7 +145,7 @@ def test_method_update(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         access_key = client.projects.access_keys.update(
@@ -157,7 +157,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.update(
@@ -171,7 +171,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.update(
@@ -187,7 +187,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_update(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -204,7 +204,7 @@ def test_path_params_update(self, client: Codex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         access_key = client.projects.access_keys.list(
@@ -212,7 +212,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.list(
@@ -224,7 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.list(
@@ -238,7 +238,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -246,7 +246,7 @@ def test_path_params_list(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         access_key = client.projects.access_keys.delete(
@@ -255,7 +255,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.delete(
@@ -268,7 +268,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         access_key = response.parse()
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.delete(
@@ -283,7 +283,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -298,13 +298,13 @@ def test_path_params_delete(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve_project_id(self, client: Codex) -> None:
         access_key = client.projects.access_keys.retrieve_project_id()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.retrieve_project_id()
@@ -314,7 +314,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None:
         access_key = response.parse()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
@@ -326,7 +326,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_revoke(self, client: Codex) -> None:
         access_key = client.projects.access_keys.revoke(
@@ -335,7 +335,7 @@ def test_method_revoke(self, client: Codex) -> None:
         )
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_revoke(self, client: Codex) -> None:
         response = client.projects.access_keys.with_raw_response.revoke(
@@ -348,7 +348,7 @@ def test_raw_response_revoke(self, client: Codex) -> None:
         access_key = response.parse()
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_revoke(self, client: Codex) -> None:
         with client.projects.access_keys.with_streaming_response.revoke(
@@ -363,7 +363,7 @@ def test_streaming_response_revoke(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_revoke(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -384,7 +384,7 @@ class TestAsyncAccessKeys:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
@@ -393,7 +393,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.create(
@@ -408,7 +408,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.create(
@@ -421,7 +421,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.create(
@@ -436,7 +436,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_create(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -445,7 +445,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve(
@@ -454,7 +454,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve(
@@ -467,7 +467,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve(
@@ -482,7 +482,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -497,7 +497,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
@@ -507,7 +507,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.update(
@@ -519,7 +519,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.update(
@@ -533,7 +533,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeySchema, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.update(
@@ -549,7 +549,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_update(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -566,7 +566,7 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.list(
@@ -574,7 +574,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.list(
@@ -586,7 +586,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert_matches_type(AccessKeyListResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.list(
@@ -600,7 +600,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -608,7 +608,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.delete(
@@ -617,7 +617,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.delete(
@@ -630,7 +630,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.delete(
@@ -645,7 +645,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -660,13 +660,13 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.retrieve_project_id()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id()
@@ -676,7 +676,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex)
         access_key = await response.parse()
         assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response:
@@ -688,7 +688,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await async_client.projects.access_keys.revoke(
@@ -697,7 +697,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None:
         )
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.access_keys.with_raw_response.revoke(
@@ -710,7 +710,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None:
         access_key = await response.parse()
         assert access_key is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.access_keys.with_streaming_response.revoke(
@@ -725,7 +725,7 @@ async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_revoke(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py
index f36de276..7266751d 100644
--- a/tests/api_resources/projects/test_evals.py
+++ b/tests/api_resources/projects/test_evals.py
@@ -18,7 +18,7 @@
 class TestEvals:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         eval = client.projects.evals.create(
@@ -29,7 +29,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         eval = client.projects.evals.create(
@@ -50,7 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.create(
@@ -65,7 +65,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         eval = response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.create(
@@ -82,7 +82,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_create(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -93,7 +93,7 @@ def test_path_params_create(self, client: Codex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_overload_1(self, client: Codex) -> None:
         eval = client.projects.evals.update(
@@ -105,7 +105,7 @@ def test_method_update_overload_1(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_with_all_params_overload_1(self, client: Codex) -> None:
         eval = client.projects.evals.update(
@@ -127,7 +127,7 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_update_overload_1(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.update(
@@ -143,7 +143,7 @@ def test_raw_response_update_overload_1(self, client: Codex) -> None:
         eval = response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_update_overload_1(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.update(
@@ -161,7 +161,7 @@ def test_streaming_response_update_overload_1(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_update_overload_1(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -182,7 +182,7 @@ def test_path_params_update_overload_1(self, client: Codex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_overload_2(self, client: Codex) -> None:
         eval = client.projects.evals.update(
@@ -192,7 +192,7 @@ def test_method_update_overload_2(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_with_all_params_overload_2(self, client: Codex) -> None:
         eval = client.projects.evals.update(
@@ -208,7 +208,7 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_update_overload_2(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.update(
@@ -222,7 +222,7 @@ def test_raw_response_update_overload_2(self, client: Codex) -> None:
         eval = response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_update_overload_2(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.update(
@@ -238,7 +238,7 @@ def test_streaming_response_update_overload_2(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_update_overload_2(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -255,7 +255,7 @@ def test_path_params_update_overload_2(self, client: Codex) -> None:
                 body_eval_key="eval_key",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         eval = client.projects.evals.list(
@@ -263,7 +263,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         eval = client.projects.evals.list(
@@ -274,7 +274,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.list(
@@ -286,7 +286,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         eval = response.parse()
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.list(
@@ -300,7 +300,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -308,7 +308,7 @@ def test_path_params_list(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         eval = client.projects.evals.delete(
@@ -317,7 +317,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.evals.with_raw_response.delete(
@@ -330,7 +330,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         eval = response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.evals.with_streaming_response.delete(
@@ -345,7 +345,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -366,7 +366,7 @@ class TestAsyncEvals:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.create(
@@ -377,7 +377,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.create(
@@ -398,7 +398,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.create(
@@ -413,7 +413,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         eval = await response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.create(
@@ -430,7 +430,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_create(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -441,7 +441,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None:
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.update(
@@ -453,7 +453,7 @@ async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_with_all_params_overload_1(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.update(
@@ -475,7 +475,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.update(
@@ -491,7 +491,7 @@ async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) ->
         eval = await response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_update_overload_1(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.update(
@@ -509,7 +509,7 @@ async def test_streaming_response_update_overload_1(self, async_client: AsyncCod
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_update_overload_1(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -530,7 +530,7 @@ async def test_path_params_update_overload_1(self, async_client: AsyncCodex) ->
                 name="name",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.update(
@@ -540,7 +540,7 @@ async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_with_all_params_overload_2(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.update(
@@ -556,7 +556,7 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.update(
@@ -570,7 +570,7 @@ async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) ->
         eval = await response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_update_overload_2(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.update(
@@ -586,7 +586,7 @@ async def test_streaming_response_update_overload_2(self, async_client: AsyncCod
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -603,7 +603,7 @@ async def test_path_params_update_overload_2(self, async_client: AsyncCodex) ->
                 body_eval_key="eval_key",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.list(
@@ -611,7 +611,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.list(
@@ -622,7 +622,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.list(
@@ -634,7 +634,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         eval = await response.parse()
         assert_matches_type(EvalListResponse, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.list(
@@ -648,7 +648,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -656,7 +656,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         eval = await async_client.projects.evals.delete(
@@ -665,7 +665,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.evals.with_raw_response.delete(
@@ -678,7 +678,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         eval = await response.parse()
         assert_matches_type(ProjectReturnSchema, eval, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.evals.with_streaming_response.delete(
@@ -693,7 +693,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py
index 3e0a96fa..5f7e02cd 100644
--- a/tests/api_resources/projects/test_query_logs.py
+++ b/tests/api_resources/projects/test_query_logs.py
@@ -30,7 +30,7 @@
 class TestQueryLogs:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         query_log = client.projects.query_logs.retrieve(
@@ -39,7 +39,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.query_logs.with_raw_response.retrieve(
@@ -52,7 +52,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         query_log = response.parse()
         assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.query_logs.with_streaming_response.retrieve(
@@ -67,7 +67,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -82,7 +82,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list(
@@ -90,7 +90,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list(
@@ -112,7 +112,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.query_logs.with_raw_response.list(
@@ -124,7 +124,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         query_log = response.parse()
         assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.query_logs.with_streaming_response.list(
@@ -138,7 +138,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -146,7 +146,7 @@ def test_path_params_list(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_by_group(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list_by_group(
@@ -154,7 +154,7 @@ def test_method_list_by_group(self, client: Codex) -> None:
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list_by_group(
@@ -178,7 +178,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list_by_group(self, client: Codex) -> None:
         response = client.projects.query_logs.with_raw_response.list_by_group(
@@ -190,7 +190,7 @@ def test_raw_response_list_by_group(self, client: Codex) -> None:
         query_log = response.parse()
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list_by_group(self, client: Codex) -> None:
         with client.projects.query_logs.with_streaming_response.list_by_group(
@@ -204,7 +204,7 @@ def test_streaming_response_list_by_group(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list_by_group(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -212,7 +212,7 @@ def test_path_params_list_by_group(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_groups(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list_groups(
@@ -220,7 +220,7 @@ def test_method_list_groups(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_groups_with_all_params(self, client: Codex) -> None:
         query_log = client.projects.query_logs.list_groups(
@@ -243,7 +243,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list_groups(self, client: Codex) -> None:
         response = client.projects.query_logs.with_raw_response.list_groups(
@@ -255,7 +255,7 @@ def test_raw_response_list_groups(self, client: Codex) -> None:
         query_log = response.parse()
         assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list_groups(self, client: Codex) -> None:
         with client.projects.query_logs.with_streaming_response.list_groups(
@@ -269,7 +269,7 @@ def test_streaming_response_list_groups(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list_groups(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -277,7 +277,7 @@ def test_path_params_list_groups(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_start_remediation(self, client: Codex) -> None:
         query_log = client.projects.query_logs.start_remediation(
@@ -286,7 +286,7 @@ def test_method_start_remediation(self, client: Codex) -> None:
         )
         assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_start_remediation(self, client: Codex) -> None:
         response = client.projects.query_logs.with_raw_response.start_remediation(
@@ -299,7 +299,7 @@ def test_raw_response_start_remediation(self, client: Codex) -> None:
         query_log = response.parse()
         assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_start_remediation(self, client: Codex) -> None:
         with client.projects.query_logs.with_streaming_response.start_remediation(
@@ -314,7 +314,7 @@ def test_streaming_response_start_remediation(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_start_remediation(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -335,7 +335,7 @@ class TestAsyncQueryLogs:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.retrieve(
@@ -344,7 +344,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.query_logs.with_raw_response.retrieve(
@@ -357,7 +357,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         query_log = await response.parse()
         assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.query_logs.with_streaming_response.retrieve(
@@ -372,7 +372,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -387,7 +387,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list(
@@ -395,7 +395,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list(
@@ -417,7 +417,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.query_logs.with_raw_response.list(
@@ -429,7 +429,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         query_log = await response.parse()
         assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.query_logs.with_streaming_response.list(
@@ -443,7 +443,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -451,7 +451,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_by_group(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list_by_group(
@@ -459,7 +459,7 @@ async def test_method_list_by_group(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_by_group_with_all_params(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list_by_group(
@@ -483,7 +483,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod
         )
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.query_logs.with_raw_response.list_by_group(
@@ -495,7 +495,7 @@ async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> Non
         query_log = await response.parse()
         assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.query_logs.with_streaming_response.list_by_group(
@@ -509,7 +509,7 @@ async def test_streaming_response_list_by_group(self, async_client: AsyncCodex)
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -517,7 +517,7 @@ async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_groups(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list_groups(
@@ -525,7 +525,7 @@ async def test_method_list_groups(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.list_groups(
@@ -548,7 +548,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex
         )
         assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.query_logs.with_raw_response.list_groups(
@@ -560,7 +560,7 @@ async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None:
         query_log = await response.parse()
         assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.query_logs.with_streaming_response.list_groups(
@@ -574,7 +574,7 @@ async def test_streaming_response_list_groups(self, async_client: AsyncCodex) ->
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -582,7 +582,7 @@ async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_start_remediation(self, async_client: AsyncCodex) -> None:
         query_log = await async_client.projects.query_logs.start_remediation(
@@ -591,7 +591,7 @@ async def test_method_start_remediation(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.query_logs.with_raw_response.start_remediation(
@@ -604,7 +604,7 @@ async def test_raw_response_start_remediation(self, async_client: AsyncCodex) ->
         query_log = await response.parse()
         assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_start_remediation(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.query_logs.with_streaming_response.start_remediation(
@@ -619,7 +619,7 @@ async def test_streaming_response_start_remediation(self, async_client: AsyncCod
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py
index 5866dbe7..d547ac85 100644
--- a/tests/api_resources/projects/test_remediations.py
+++ b/tests/api_resources/projects/test_remediations.py
@@ -30,7 +30,7 @@
 class TestRemediations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         remediation = client.projects.remediations.create(
@@ -39,7 +39,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         remediation = client.projects.remediations.create(
@@ -50,7 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.create(
@@ -63,7 +63,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.create(
@@ -78,7 +78,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_create(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -87,7 +87,7 @@ def test_path_params_create(self, client: Codex) -> None:
                 question="x",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         remediation = client.projects.remediations.retrieve(
@@ -96,7 +96,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.retrieve(
@@ -109,7 +109,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.retrieve(
@@ -124,7 +124,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -139,7 +139,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         remediation = client.projects.remediations.list(
@@ -147,7 +147,7 @@ def test_method_list(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         remediation = client.projects.remediations.list(
@@ -165,7 +165,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.list(
@@ -177,7 +177,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.list(
@@ -191,7 +191,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -199,7 +199,7 @@ def test_path_params_list(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         remediation = client.projects.remediations.delete(
@@ -208,7 +208,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert remediation is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.delete(
@@ -221,7 +221,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         remediation = response.parse()
         assert remediation is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.delete(
@@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -251,7 +251,7 @@ def test_path_params_delete(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_edit_answer(self, client: Codex) -> None:
         remediation = client.projects.remediations.edit_answer(
@@ -261,7 +261,7 @@ def test_method_edit_answer(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_edit_answer(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.edit_answer(
@@ -275,7 +275,7 @@ def test_raw_response_edit_answer(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_edit_answer(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.edit_answer(
@@ -291,7 +291,7 @@ def test_streaming_response_edit_answer(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_edit_answer(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -308,7 +308,7 @@ def test_path_params_edit_answer(self, client: Codex) -> None:
                 answer="answer",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_edit_draft_answer(self, client: Codex) -> None:
         remediation = client.projects.remediations.edit_draft_answer(
@@ -318,7 +318,7 @@ def test_method_edit_draft_answer(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_edit_draft_answer(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.edit_draft_answer(
@@ -332,7 +332,7 @@ def test_raw_response_edit_draft_answer(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_edit_draft_answer(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.edit_draft_answer(
@@ -348,7 +348,7 @@ def test_streaming_response_edit_draft_answer(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_edit_draft_answer(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -365,7 +365,7 @@ def test_path_params_edit_draft_answer(self, client: Codex) -> None:
                 draft_answer="draft_answer",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_get_resolved_logs_count(self, client: Codex) -> None:
         remediation = client.projects.remediations.get_resolved_logs_count(
@@ -374,7 +374,7 @@ def test_method_get_resolved_logs_count(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.get_resolved_logs_count(
@@ -387,7 +387,7 @@ def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.get_resolved_logs_count(
@@ -402,7 +402,7 @@ def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_get_resolved_logs_count(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -417,7 +417,7 @@ def test_path_params_get_resolved_logs_count(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_resolved_logs(self, client: Codex) -> None:
         remediation = client.projects.remediations.list_resolved_logs(
@@ -426,7 +426,7 @@ def test_method_list_resolved_logs(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list_resolved_logs(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.list_resolved_logs(
@@ -439,7 +439,7 @@ def test_raw_response_list_resolved_logs(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list_resolved_logs(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.list_resolved_logs(
@@ -454,7 +454,7 @@ def test_streaming_response_list_resolved_logs(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list_resolved_logs(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -469,7 +469,7 @@ def test_path_params_list_resolved_logs(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_pause(self, client: Codex) -> None:
         remediation = client.projects.remediations.pause(
@@ -478,7 +478,7 @@ def test_method_pause(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_pause(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.pause(
@@ -491,7 +491,7 @@ def test_raw_response_pause(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_pause(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.pause(
@@ -506,7 +506,7 @@ def test_streaming_response_pause(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_pause(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -521,7 +521,7 @@ def test_path_params_pause(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_publish(self, client: Codex) -> None:
         remediation = client.projects.remediations.publish(
@@ -530,7 +530,7 @@ def test_method_publish(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_publish(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.publish(
@@ -543,7 +543,7 @@ def test_raw_response_publish(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_publish(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.publish(
@@ -558,7 +558,7 @@ def test_streaming_response_publish(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_publish(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -573,7 +573,7 @@ def test_path_params_publish(self, client: Codex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_unpause(self, client: Codex) -> None:
         remediation = client.projects.remediations.unpause(
@@ -582,7 +582,7 @@ def test_method_unpause(self, client: Codex) -> None:
         )
         assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_unpause(self, client: Codex) -> None:
         response = client.projects.remediations.with_raw_response.unpause(
@@ -595,7 +595,7 @@ def test_raw_response_unpause(self, client: Codex) -> None:
         remediation = response.parse()
         assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_unpause(self, client: Codex) -> None:
         with client.projects.remediations.with_streaming_response.unpause(
@@ -610,7 +610,7 @@ def test_streaming_response_unpause(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_unpause(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -631,7 +631,7 @@ class TestAsyncRemediations:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.create(
@@ -640,7 +640,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.create(
@@ -651,7 +651,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.create(
@@ -664,7 +664,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationCreateResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.create(
@@ -679,7 +679,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_create(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -688,7 +688,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None:
                 question="x",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.retrieve(
@@ -697,7 +697,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.retrieve(
@@ -710,7 +710,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.retrieve(
@@ -725,7 +725,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -740,7 +740,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.list(
@@ -748,7 +748,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.list(
@@ -766,7 +766,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.list(
@@ -778,7 +778,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.list(
@@ -792,7 +792,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -800,7 +800,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.delete(
@@ -809,7 +809,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert remediation is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.delete(
@@ -822,7 +822,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert remediation is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.delete(
@@ -837,7 +837,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -852,7 +852,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_edit_answer(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.edit_answer(
@@ -862,7 +862,7 @@ async def test_method_edit_answer(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.edit_answer(
@@ -876,7 +876,7 @@ async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.edit_answer(
@@ -892,7 +892,7 @@ async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) ->
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -909,7 +909,7 @@ async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None:
                 answer="answer",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.edit_draft_answer(
@@ -919,7 +919,7 @@ async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.edit_draft_answer(
@@ -933,7 +933,7 @@ async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) ->
         remediation = await response.parse()
         assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.edit_draft_answer(
@@ -949,7 +949,7 @@ async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCod
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -966,7 +966,7 @@ async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) ->
                 draft_answer="draft_answer",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.get_resolved_logs_count(
@@ -975,7 +975,7 @@ async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count(
@@ -988,7 +988,7 @@ async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCod
         remediation = await response.parse()
         assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count(
@@ -1003,7 +1003,7 @@ async def test_streaming_response_get_resolved_logs_count(self, async_client: As
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1018,7 +1018,7 @@ async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCode
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.list_resolved_logs(
@@ -1027,7 +1027,7 @@ async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None
         )
         assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.list_resolved_logs(
@@ -1040,7 +1040,7 @@ async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -
         remediation = await response.parse()
         assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.list_resolved_logs(
@@ -1055,7 +1055,7 @@ async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCo
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1070,7 +1070,7 @@ async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) ->
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_pause(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.pause(
@@ -1079,7 +1079,7 @@ async def test_method_pause(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_pause(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.pause(
@@ -1092,7 +1092,7 @@ async def test_raw_response_pause(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationPauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.pause(
@@ -1107,7 +1107,7 @@ async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_pause(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1122,7 +1122,7 @@ async def test_path_params_pause(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_publish(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.publish(
@@ -1131,7 +1131,7 @@ async def test_method_publish(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_publish(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.publish(
@@ -1144,7 +1144,7 @@ async def test_raw_response_publish(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationPublishResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.publish(
@@ -1159,7 +1159,7 @@ async def test_streaming_response_publish(self, async_client: AsyncCodex) -> Non
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_publish(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1174,7 +1174,7 @@ async def test_path_params_publish(self, async_client: AsyncCodex) -> None:
                 project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_unpause(self, async_client: AsyncCodex) -> None:
         remediation = await async_client.projects.remediations.unpause(
@@ -1183,7 +1183,7 @@ async def test_method_unpause(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.remediations.with_raw_response.unpause(
@@ -1196,7 +1196,7 @@ async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None:
         remediation = await response.parse()
         assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.remediations.with_streaming_response.unpause(
@@ -1211,7 +1211,7 @@ async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> Non
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_unpause(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py
index 92db3a81..0baadf50 100644
--- a/tests/api_resources/test_health.py
+++ b/tests/api_resources/test_health.py
@@ -17,13 +17,13 @@
 class TestHealth:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_check(self, client: Codex) -> None:
         health = client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_check(self, client: Codex) -> None:
         response = client.health.with_raw_response.check()
@@ -33,7 +33,7 @@ def test_raw_response_check(self, client: Codex) -> None:
         health = response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_check(self, client: Codex) -> None:
         with client.health.with_streaming_response.check() as response:
@@ -45,13 +45,13 @@ def test_streaming_response_check(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_db(self, client: Codex) -> None:
         health = client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_db(self, client: Codex) -> None:
         response = client.health.with_raw_response.db()
@@ -61,7 +61,7 @@ def test_raw_response_db(self, client: Codex) -> None:
         health = response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_db(self, client: Codex) -> None:
         with client.health.with_streaming_response.db() as response:
@@ -79,13 +79,13 @@ class TestAsyncHealth:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_check(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.check()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.check()
@@ -95,7 +95,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None:
         health = await response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.check() as response:
@@ -107,13 +107,13 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_db(self, async_client: AsyncCodex) -> None:
         health = await async_client.health.db()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         response = await async_client.health.with_raw_response.db()
@@ -123,7 +123,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None:
         health = await response.parse()
         assert_matches_type(HealthCheckResponse, health, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_db(self, async_client: AsyncCodex) -> None:
         async with async_client.health.with_streaming_response.db() as response:
diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py
index eecdf3f7..9245bd89 100644
--- a/tests/api_resources/test_organizations.py
+++ b/tests/api_resources/test_organizations.py
@@ -21,7 +21,7 @@
 class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         organization = client.organizations.retrieve(
@@ -29,7 +29,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.organizations.with_raw_response.retrieve(
@@ -41,7 +41,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.organizations.with_streaming_response.retrieve(
@@ -55,7 +55,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -63,7 +63,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_members(self, client: Codex) -> None:
         organization = client.organizations.list_members(
@@ -71,7 +71,7 @@ def test_method_list_members(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list_members(self, client: Codex) -> None:
         response = client.organizations.with_raw_response.list_members(
@@ -83,7 +83,7 @@ def test_raw_response_list_members(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list_members(self, client: Codex) -> None:
         with client.organizations.with_streaming_response.list_members(
@@ -97,7 +97,7 @@ def test_streaming_response_list_members(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_list_members(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -105,7 +105,7 @@ def test_path_params_list_members(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve_permissions(self, client: Codex) -> None:
         organization = client.organizations.retrieve_permissions(
@@ -113,7 +113,7 @@ def test_method_retrieve_permissions(self, client: Codex) -> None:
         )
         assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve_permissions(self, client: Codex) -> None:
         response = client.organizations.with_raw_response.retrieve_permissions(
@@ -125,7 +125,7 @@ def test_raw_response_retrieve_permissions(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve_permissions(self, client: Codex) -> None:
         with client.organizations.with_streaming_response.retrieve_permissions(
@@ -139,7 +139,7 @@ def test_streaming_response_retrieve_permissions(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve_permissions(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -153,7 +153,7 @@ class TestAsyncOrganizations:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         organization = await async_client.organizations.retrieve(
@@ -161,7 +161,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.with_raw_response.retrieve(
@@ -173,7 +173,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         organization = await response.parse()
         assert_matches_type(OrganizationSchemaPublic, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.with_streaming_response.retrieve(
@@ -187,7 +187,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -195,7 +195,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_members(self, async_client: AsyncCodex) -> None:
         organization = await async_client.organizations.list_members(
@@ -203,7 +203,7 @@ async def test_method_list_members(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.with_raw_response.list_members(
@@ -215,7 +215,7 @@ async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None
         organization = await response.parse()
         assert_matches_type(OrganizationListMembersResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list_members(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.with_streaming_response.list_members(
@@ -229,7 +229,7 @@ async def test_streaming_response_list_members(self, async_client: AsyncCodex) -
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_list_members(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
@@ -237,7 +237,7 @@ async def test_path_params_list_members(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> None:
         organization = await async_client.organizations.retrieve_permissions(
@@ -245,7 +245,7 @@ async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) -> None:
         response = await async_client.organizations.with_raw_response.retrieve_permissions(
@@ -257,7 +257,7 @@ async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex)
         organization = await response.parse()
         assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve_permissions(self, async_client: AsyncCodex) -> None:
         async with async_client.organizations.with_streaming_response.retrieve_permissions(
@@ -271,7 +271,7 @@ async def test_streaming_response_retrieve_permissions(self, async_client: Async
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve_permissions(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"):
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 7884db0f..2159a1f0 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -24,7 +24,7 @@
 class TestProjects:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create(self, client: Codex) -> None:
         project = client.projects.create(
@@ -34,7 +34,7 @@ def test_method_create(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_create_with_all_params(self, client: Codex) -> None:
         project = client.projects.create(
@@ -128,7 +128,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_create(self, client: Codex) -> None:
         response = client.projects.with_raw_response.create(
@@ -142,7 +142,7 @@ def test_raw_response_create(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_create(self, client: Codex) -> None:
         with client.projects.with_streaming_response.create(
@@ -158,7 +158,7 @@ def test_streaming_response_create(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         project = client.projects.retrieve(
@@ -166,7 +166,7 @@ def test_method_retrieve(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.projects.with_raw_response.retrieve(
@@ -178,7 +178,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.projects.with_streaming_response.retrieve(
@@ -192,7 +192,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -200,7 +200,7 @@ def test_path_params_retrieve(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update(self, client: Codex) -> None:
         project = client.projects.update(
@@ -208,7 +208,7 @@ def test_method_update(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_update_with_all_params(self, client: Codex) -> None:
         project = client.projects.update(
@@ -302,7 +302,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_update(self, client: Codex) -> None:
         response = client.projects.with_raw_response.update(
@@ -314,7 +314,7 @@ def test_raw_response_update(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_update(self, client: Codex) -> None:
         with client.projects.with_streaming_response.update(
@@ -328,7 +328,7 @@ def test_streaming_response_update(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_update(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -336,13 +336,13 @@ def test_path_params_update(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         project = client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list_with_all_params(self, client: Codex) -> None:
         project = client.projects.list(
@@ -356,7 +356,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.projects.with_raw_response.list()
@@ -366,7 +366,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.projects.with_streaming_response.list() as response:
@@ -378,7 +378,7 @@ def test_streaming_response_list(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_delete(self, client: Codex) -> None:
         project = client.projects.delete(
@@ -386,7 +386,7 @@ def test_method_delete(self, client: Codex) -> None:
         )
         assert project is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_delete(self, client: Codex) -> None:
         response = client.projects.with_raw_response.delete(
@@ -398,7 +398,7 @@ def test_raw_response_delete(self, client: Codex) -> None:
         project = response.parse()
         assert project is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_delete(self, client: Codex) -> None:
         with client.projects.with_streaming_response.delete(
@@ -412,7 +412,7 @@ def test_streaming_response_delete(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_delete(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -420,7 +420,7 @@ def test_path_params_delete(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_export(self, client: Codex) -> None:
         project = client.projects.export(
@@ -428,7 +428,7 @@ def test_method_export(self, client: Codex) -> None:
         )
         assert_matches_type(object, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_export(self, client: Codex) -> None:
         response = client.projects.with_raw_response.export(
@@ -440,7 +440,7 @@ def test_raw_response_export(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(object, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_export(self, client: Codex) -> None:
         with client.projects.with_streaming_response.export(
@@ -454,7 +454,7 @@ def test_streaming_response_export(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_export(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -462,7 +462,7 @@ def test_path_params_export(self, client: Codex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_invite_sme(self, client: Codex) -> None:
         project = client.projects.invite_sme(
@@ -473,7 +473,7 @@ def test_method_invite_sme(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_invite_sme(self, client: Codex) -> None:
         response = client.projects.with_raw_response.invite_sme(
@@ -488,7 +488,7 @@ def test_raw_response_invite_sme(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_invite_sme(self, client: Codex) -> None:
         with client.projects.with_streaming_response.invite_sme(
@@ -505,7 +505,7 @@ def test_streaming_response_invite_sme(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_invite_sme(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -516,7 +516,7 @@ def test_path_params_invite_sme(self, client: Codex) -> None:
                 url_query_string="url_query_string",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve_analytics(self, client: Codex) -> None:
         project = client.projects.retrieve_analytics(
@@ -524,7 +524,7 @@ def test_method_retrieve_analytics(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None:
         project = client.projects.retrieve_analytics(
@@ -534,7 +534,7 @@ def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve_analytics(self, client: Codex) -> None:
         response = client.projects.with_raw_response.retrieve_analytics(
@@ -546,7 +546,7 @@ def test_raw_response_retrieve_analytics(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve_analytics(self, client: Codex) -> None:
         with client.projects.with_streaming_response.retrieve_analytics(
@@ -560,7 +560,7 @@ def test_streaming_response_retrieve_analytics(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_retrieve_analytics(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -568,7 +568,7 @@ def test_path_params_retrieve_analytics(self, client: Codex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_validate(self, client: Codex) -> None:
         project = client.projects.validate(
@@ -579,7 +579,7 @@ def test_method_validate(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_validate_with_all_params(self, client: Codex) -> None:
         project = client.projects.validate(
@@ -649,7 +649,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_validate(self, client: Codex) -> None:
         response = client.projects.with_raw_response.validate(
@@ -664,7 +664,7 @@ def test_raw_response_validate(self, client: Codex) -> None:
         project = response.parse()
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_validate(self, client: Codex) -> None:
         with client.projects.with_streaming_response.validate(
@@ -681,7 +681,7 @@ def test_streaming_response_validate(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_path_params_validate(self, client: Codex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -698,7 +698,7 @@ class TestAsyncProjects:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
@@ -708,7 +708,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.create(
@@ -802,7 +802,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.create(
@@ -816,7 +816,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_create(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.create(
@@ -832,7 +832,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve(
@@ -840,7 +840,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve(
@@ -852,7 +852,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve(
@@ -866,7 +866,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -874,7 +874,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
@@ -882,7 +882,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.update(
@@ -976,7 +976,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.update(
@@ -988,7 +988,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectReturnSchema, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_update(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.update(
@@ -1002,7 +1002,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_update(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1010,13 +1010,13 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None:
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.list(
@@ -1030,7 +1030,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No
         )
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.list()
@@ -1040,7 +1040,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectListResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.list() as response:
@@ -1052,7 +1052,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_delete(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.delete(
@@ -1060,7 +1060,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None:
         )
         assert project is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.delete(
@@ -1072,7 +1072,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert project is None
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.delete(
@@ -1086,7 +1086,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1094,7 +1094,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_export(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.export(
@@ -1102,7 +1102,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(object, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.export(
@@ -1114,7 +1114,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(object, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_export(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.export(
@@ -1128,7 +1128,7 @@ async def test_streaming_response_export(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_export(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1136,7 +1136,7 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None:
                 "",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_invite_sme(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.invite_sme(
@@ -1147,7 +1147,7 @@ async def test_method_invite_sme(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.invite_sme(
@@ -1162,7 +1162,7 @@ async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectInviteSmeResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.invite_sme(
@@ -1179,7 +1179,7 @@ async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) ->
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1190,7 +1190,7 @@ async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None:
                 url_query_string="url_query_string",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve_analytics(
@@ -1198,7 +1198,7 @@ async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve_analytics_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.retrieve_analytics(
@@ -1208,7 +1208,7 @@ async def test_method_retrieve_analytics_with_all_params(self, async_client: Asy
         )
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.retrieve_analytics(
@@ -1220,7 +1220,7 @@ async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -
         project = await response.parse()
         assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.retrieve_analytics(
@@ -1234,7 +1234,7 @@ async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCo
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
@@ -1242,7 +1242,7 @@ async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) ->
                 project_id="",
             )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_validate(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
@@ -1253,7 +1253,7 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -> None:
         project = await async_client.projects.validate(
@@ -1323,7 +1323,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
         )
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         response = await async_client.projects.with_raw_response.validate(
@@ -1338,7 +1338,7 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None:
         project = await response.parse()
         assert_matches_type(ProjectValidateResponse, project, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_validate(self, async_client: AsyncCodex) -> None:
         async with async_client.projects.with_streaming_response.validate(
@@ -1355,7 +1355,7 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_path_params_validate(self, async_client: AsyncCodex) -> None:
         with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index da0a9ad1..fd78e60c 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -17,7 +17,7 @@
 class TestTlm:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_prompt(self, client: Codex) -> None:
         tlm = client.tlm.prompt(
@@ -25,7 +25,7 @@ def test_method_prompt(self, client: Codex) -> None:
         )
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_prompt_with_all_params(self, client: Codex) -> None:
         tlm = client.tlm.prompt(
@@ -48,7 +48,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_prompt(self, client: Codex) -> None:
         response = client.tlm.with_raw_response.prompt(
@@ -60,7 +60,7 @@ def test_raw_response_prompt(self, client: Codex) -> None:
         tlm = response.parse()
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_prompt(self, client: Codex) -> None:
         with client.tlm.with_streaming_response.prompt(
@@ -74,7 +74,7 @@ def test_streaming_response_prompt(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_score(self, client: Codex) -> None:
         tlm = client.tlm.score(
@@ -83,7 +83,7 @@ def test_method_score(self, client: Codex) -> None:
         )
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_score_with_all_params(self, client: Codex) -> None:
         tlm = client.tlm.score(
@@ -107,7 +107,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_score(self, client: Codex) -> None:
         response = client.tlm.with_raw_response.score(
@@ -120,7 +120,7 @@ def test_raw_response_score(self, client: Codex) -> None:
         tlm = response.parse()
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_score(self, client: Codex) -> None:
         with client.tlm.with_streaming_response.score(
@@ -141,7 +141,7 @@ class TestAsyncTlm:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_prompt(self, async_client: AsyncCodex) -> None:
         tlm = await async_client.tlm.prompt(
@@ -149,7 +149,7 @@ async def test_method_prompt(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> None:
         tlm = await async_client.tlm.prompt(
@@ -172,7 +172,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) ->
         )
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None:
         response = await async_client.tlm.with_raw_response.prompt(
@@ -184,7 +184,7 @@ async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None:
         tlm = await response.parse()
         assert_matches_type(TlmPromptResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None:
         async with async_client.tlm.with_streaming_response.prompt(
@@ -198,7 +198,7 @@ async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_score(self, async_client: AsyncCodex) -> None:
         tlm = await async_client.tlm.score(
@@ -207,7 +207,7 @@ async def test_method_score(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> None:
         tlm = await async_client.tlm.score(
@@ -231,7 +231,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N
         )
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_score(self, async_client: AsyncCodex) -> None:
         response = await async_client.tlm.with_raw_response.score(
@@ -244,7 +244,7 @@ async def test_raw_response_score(self, async_client: AsyncCodex) -> None:
         tlm = await response.parse()
         assert_matches_type(TlmScoreResponse, tlm, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_score(self, async_client: AsyncCodex) -> None:
         async with async_client.tlm.with_streaming_response.score(
diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py
index 661ee559..4526254d 100644
--- a/tests/api_resources/test_users.py
+++ b/tests/api_resources/test_users.py
@@ -18,7 +18,7 @@
 class TestUsers:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_activate_account(self, client: Codex) -> None:
         user = client.users.activate_account(
@@ -27,7 +27,7 @@ def test_method_activate_account(self, client: Codex) -> None:
         )
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_activate_account_with_all_params(self, client: Codex) -> None:
         user = client.users.activate_account(
@@ -41,7 +41,7 @@ def test_method_activate_account_with_all_params(self, client: Codex) -> None:
         )
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_activate_account(self, client: Codex) -> None:
         response = client.users.with_raw_response.activate_account(
@@ -54,7 +54,7 @@ def test_raw_response_activate_account(self, client: Codex) -> None:
         user = response.parse()
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_activate_account(self, client: Codex) -> None:
         with client.users.with_streaming_response.activate_account(
@@ -75,7 +75,7 @@ class TestAsyncUsers:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_activate_account(self, async_client: AsyncCodex) -> None:
         user = await async_client.users.activate_account(
@@ -84,7 +84,7 @@ async def test_method_activate_account(self, async_client: AsyncCodex) -> None:
         )
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_activate_account_with_all_params(self, async_client: AsyncCodex) -> None:
         user = await async_client.users.activate_account(
@@ -98,7 +98,7 @@ async def test_method_activate_account_with_all_params(self, async_client: Async
         )
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.with_raw_response.activate_account(
@@ -111,7 +111,7 @@ async def test_raw_response_activate_account(self, async_client: AsyncCodex) ->
         user = await response.parse()
         assert_matches_type(UserSchemaPublic, user, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_activate_account(self, async_client: AsyncCodex) -> None:
         async with async_client.users.with_streaming_response.activate_account(
diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py
index f0a7ccf7..65cff9ad 100644
--- a/tests/api_resources/users/myself/test_api_key.py
+++ b/tests/api_resources/users/myself/test_api_key.py
@@ -17,13 +17,13 @@
 class TestAPIKey:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         api_key = client.users.myself.api_key.retrieve()
         assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.users.myself.api_key.with_raw_response.retrieve()
@@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         api_key = response.parse()
         assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.users.myself.api_key.with_streaming_response.retrieve() as response:
@@ -45,13 +45,13 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_refresh(self, client: Codex) -> None:
         api_key = client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_refresh(self, client: Codex) -> None:
         response = client.users.myself.api_key.with_raw_response.refresh()
@@ -61,7 +61,7 @@ def test_raw_response_refresh(self, client: Codex) -> None:
         api_key = response.parse()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_refresh(self, client: Codex) -> None:
         with client.users.myself.api_key.with_streaming_response.refresh() as response:
@@ -79,13 +79,13 @@ class TestAsyncAPIKey:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         api_key = await async_client.users.myself.api_key.retrieve()
         assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.retrieve()
@@ -95,7 +95,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         api_key = await response.parse()
         assert_matches_type(UserSchemaPublic, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.retrieve() as response:
@@ -107,13 +107,13 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
 
         assert cast(Any, response.is_closed) is True
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_refresh(self, async_client: AsyncCodex) -> None:
         api_key = await async_client.users.myself.api_key.refresh()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.api_key.with_raw_response.refresh()
@@ -123,7 +123,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None:
         api_key = await response.parse()
         assert_matches_type(UserSchema, api_key, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.api_key.with_streaming_response.refresh() as response:
diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py
index fd377ea0..d30c0b60 100644
--- a/tests/api_resources/users/myself/test_organizations.py
+++ b/tests/api_resources/users/myself/test_organizations.py
@@ -17,13 +17,13 @@
 class TestOrganizations:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_list(self, client: Codex) -> None:
         organization = client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_list(self, client: Codex) -> None:
         response = client.users.myself.organizations.with_raw_response.list()
@@ -33,7 +33,7 @@ def test_raw_response_list(self, client: Codex) -> None:
         organization = response.parse()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_list(self, client: Codex) -> None:
         with client.users.myself.organizations.with_streaming_response.list() as response:
@@ -51,13 +51,13 @@ class TestAsyncOrganizations:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_list(self, async_client: AsyncCodex) -> None:
         organization = await async_client.users.myself.organizations.list()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.organizations.with_raw_response.list()
@@ -67,7 +67,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None:
         organization = await response.parse()
         assert_matches_type(UserOrganizationsSchema, organization, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_list(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.organizations.with_streaming_response.list() as response:
diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py
index 1c56b0be..f86d54cb 100644
--- a/tests/api_resources/users/test_myself.py
+++ b/tests/api_resources/users/test_myself.py
@@ -17,13 +17,13 @@
 class TestMyself:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_retrieve(self, client: Codex) -> None:
         myself = client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_retrieve(self, client: Codex) -> None:
         response = client.users.myself.with_raw_response.retrieve()
@@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
         myself = response.parse()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_retrieve(self, client: Codex) -> None:
         with client.users.myself.with_streaming_response.retrieve() as response:
@@ -51,13 +51,13 @@ class TestAsyncMyself:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await async_client.users.myself.retrieve()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.myself.with_raw_response.retrieve()
@@ -67,7 +67,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
         myself = await response.parse()
         assert_matches_type(UserSchemaPublic, myself, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None:
         async with async_client.users.myself.with_streaming_response.retrieve() as response:
diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py
index fbf6b667..3fca3582 100644
--- a/tests/api_resources/users/test_verification.py
+++ b/tests/api_resources/users/test_verification.py
@@ -17,13 +17,13 @@
 class TestVerification:
     parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_method_resend(self, client: Codex) -> None:
         verification = client.users.verification.resend()
         assert_matches_type(VerificationResendResponse, verification, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_raw_response_resend(self, client: Codex) -> None:
         response = client.users.verification.with_raw_response.resend()
@@ -33,7 +33,7 @@ def test_raw_response_resend(self, client: Codex) -> None:
         verification = response.parse()
         assert_matches_type(VerificationResendResponse, verification, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     def test_streaming_response_resend(self, client: Codex) -> None:
         with client.users.verification.with_streaming_response.resend() as response:
@@ -51,13 +51,13 @@ class TestAsyncVerification:
         "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
     )
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_method_resend(self, async_client: AsyncCodex) -> None:
         verification = await async_client.users.verification.resend()
         assert_matches_type(VerificationResendResponse, verification, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_raw_response_resend(self, async_client: AsyncCodex) -> None:
         response = await async_client.users.verification.with_raw_response.resend()
@@ -67,7 +67,7 @@ async def test_raw_response_resend(self, async_client: AsyncCodex) -> None:
         verification = await response.parse()
         assert_matches_type(VerificationResendResponse, verification, path=["response"])
 
-    @pytest.mark.skip()
+    @pytest.mark.skip(reason="Prism tests are disabled")
     @parametrize
     async def test_streaming_response_resend(self, async_client: AsyncCodex) -> None:
         async with async_client.users.verification.with_streaming_response.resend() as response:

From 95b308a34464c1830399711e897029bf6ce59ad9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 12 Aug 2025 18:17:49 +0000
Subject: [PATCH 225/227] feat(api): api update

---
 .stats.yml                                 |   2 +-
 src/codex/resources/projects/projects.py   | 172 ++++++-----
 src/codex/resources/tlm.py                 | 344 +++++++++++----------
 src/codex/types/project_validate_params.py |  88 +++---
 src/codex/types/tlm_prompt_params.py       |  88 +++---
 src/codex/types/tlm_score_params.py        |  88 +++---
 tests/api_resources/test_projects.py       |   2 +
 tests/api_resources/test_tlm.py            |   4 +
 8 files changed, 427 insertions(+), 361 deletions(-)

diff --git a/.stats.yml b/.stats.yml
index 3aa68e69..5e0a177c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: d01153406f196329a5d5d1efd8f3e50e
+openapi_spec_hash: 924a89b5f031d9215a5a701f834b132f
 config_hash: 930284cfa37f835d949c8a1b124f4807
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index d21f7937..c4d55973 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -520,60 +520,66 @@ def validate(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
@@ -1093,60 +1099,66 @@ async def validate(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           prompt: The prompt to use for the TLM call. If not provided, the prompt will be
               generated from the messages.
diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py
index c6064ed6..2483e669 100644
--- a/src/codex/resources/tlm.py
+++ b/src/codex/resources/tlm.py
@@ -94,60 +94,66 @@ def prompt(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
@@ -232,60 +238,66 @@ def score(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
@@ -386,60 +398,66 @@ async def prompt(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
@@ -524,60 +542,66 @@ async def score(
               `model`, and `max_tokens` is set to 512. You can set custom values for these
               arguments regardless of the quality preset specified.
 
-              Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-              "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-              "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-              "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-              "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-              default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-              better results, faster models yield faster results). - Models still in beta:
-              "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-              "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-              "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-              "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-              latency/costs: "gpt-4.1-nano", "nova-micro".
-
-                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-                  Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-                  If you experience token/rate limit errors while using TLM, try lowering this number.
+              Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+              "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+              "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+              "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+              "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+              (better models yield better results, faster models yield faster results). -
+              Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+              "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+              "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+              models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+              "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+              "nova-micro".
+
+                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
+                  - name: Name of the evaluation criteria.
+                  - criteria: Instructions specifying the evaluation criteria.
+
+                  max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+                  If you experience token/rate-limit errors, try lowering this number.
                   For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-                  Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-                  When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+                  Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+                  num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+                  The maximum number currently supported is 3. Lower values can reduce runtimes.
+                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-                  Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+                  num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+                  Must be between 0 and 20. Lower values can reduce runtimes.
                   Measuring consistency helps quantify the epistemic uncertainty associated with
                   strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
                   TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-                  num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-                  The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-                  Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
                   similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
                   trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
                   Supported similarity measures include - "semantic" (based on natural language inference),
                   "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
                   "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-                  reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-                  when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-                  Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-                  log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-                  For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+                  and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+                  This parameter has no effect when `num_consistency_samples = 0`.
 
-                  custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-                  The expected input format is a list of dictionaries, where each dictionary has the following keys:
-                  - name: Name of the evaluation criteria.
-                  - criteria: Instructions specifying the evaluation criteria.
+                  num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+                  `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+                  You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+                  This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+                  When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+                  This parameter has no effect when `disable_trustworthiness` is True.
 
-                  use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+                  disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+                  This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+                  The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
 
           quality_preset: The quality preset to use for the TLM or Trustworthy RAG API.
 
diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py
index 86c10495..48074632 100644
--- a/src/codex/types/project_validate_params.py
+++ b/src/codex/types/project_validate_params.py
@@ -123,60 +123,66 @@ class ProjectValidateParams(TypedDict, total=False):
     `model`, and `max_tokens` is set to 512. You can set custom values for these
     arguments regardless of the quality preset specified.
 
-    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-    better results, faster models yield faster results). - Models still in beta:
-    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-    latency/costs: "gpt-4.1-nano", "nova-micro".
-
-        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-        If you experience token/rate limit errors while using TLM, try lowering this number.
+    Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+    "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+    "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+    (better models yield better results, faster models yield faster results). -
+    Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+    models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+    "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+    "nova-micro".
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+        If you experience token/rate-limit errors, try lowering this number.
         For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+        num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+        The maximum number currently supported is 3. Lower values can reduce runtimes.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+        Must be between 0 and 20. Lower values can reduce runtimes.
         Measuring consistency helps quantify the epistemic uncertainty associated with
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+        This parameter has no effect when `num_consistency_samples = 0`.
 
-        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-        The expected input format is a list of dictionaries, where each dictionary has the following keys:
-        - name: Name of the evaluation criteria.
-        - criteria: Instructions specifying the evaluation criteria.
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+        disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+        This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+        The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
     """
 
     prompt: Optional[str]
@@ -647,6 +653,8 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False):
 class Options(TypedDict, total=False):
     custom_eval_criteria: Iterable[object]
 
+    disable_trustworthiness: bool
+
     log: List[str]
 
     max_tokens: int
diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py
index 8749c5ac..aaa8b321 100644
--- a/src/codex/types/tlm_prompt_params.py
+++ b/src/codex/types/tlm_prompt_params.py
@@ -45,60 +45,66 @@ class TlmPromptParams(TypedDict, total=False):
     `model`, and `max_tokens` is set to 512. You can set custom values for these
     arguments regardless of the quality preset specified.
 
-    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-    better results, faster models yield faster results). - Models still in beta:
-    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-    latency/costs: "gpt-4.1-nano", "nova-micro".
-
-        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-        If you experience token/rate limit errors while using TLM, try lowering this number.
+    Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+    "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+    "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+    (better models yield better results, faster models yield faster results). -
+    Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+    models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+    "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+    "nova-micro".
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+        If you experience token/rate-limit errors, try lowering this number.
         For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+        num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+        The maximum number currently supported is 3. Lower values can reduce runtimes.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+        Must be between 0 and 20. Lower values can reduce runtimes.
         Measuring consistency helps quantify the epistemic uncertainty associated with
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+        This parameter has no effect when `num_consistency_samples = 0`.
 
-        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-        The expected input format is a list of dictionaries, where each dictionary has the following keys:
-        - name: Name of the evaluation criteria.
-        - criteria: Instructions specifying the evaluation criteria.
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+        disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+        This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+        The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -110,6 +116,8 @@ class TlmPromptParams(TypedDict, total=False):
 class Options(TypedDict, total=False):
     custom_eval_criteria: Iterable[object]
 
+    disable_trustworthiness: bool
+
     log: List[str]
 
     max_tokens: int
diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py
index 4a0a32ad..a5a75c6d 100644
--- a/src/codex/types/tlm_score_params.py
+++ b/src/codex/types/tlm_score_params.py
@@ -47,60 +47,66 @@ class TlmScoreParams(TypedDict, total=False):
     `model`, and `max_tokens` is set to 512. You can set custom values for these
     arguments regardless of the quality preset specified.
 
-    Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3",
-    "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4",
-    "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0",
-    "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet",
-    "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"},
-    default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield
-    better results, faster models yield faster results). - Models still in beta:
-    "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview",
-    "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet",
-    "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini",
-    "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low
-    latency/costs: "gpt-4.1-nano", "nova-micro".
-
-        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring).
-        Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs.
-        If you experience token/rate limit errors while using TLM, try lowering this number.
+    Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini",
+    "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o",
+    "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2",
+    "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro",
+    "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use
+    (better models yield better results, faster models yield faster results). -
+    Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1",
+    "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0",
+    "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended
+    models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0",
+    "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano",
+    "nova-micro".
+
+        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
+        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+
+        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
+        The expected input format is a list of dictionaries, where each dictionary has the following keys:
+        - name: Name of the evaluation criteria.
+        - criteria: Instructions specifying the evaluation criteria.
+
+        max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring.
+        If you experience token/rate-limit errors, try lowering this number.
         For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512.
 
-        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
-        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
-        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
-        Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs.
-        When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
+        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
+        Reduce this value to reduce runtimes. Higher values may improve trust scoring.
+
+        num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring.
+        The maximum number currently supported is 3. Lower values can reduce runtimes.
+        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring.
-        Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs.
+        num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring.
+        Must be between 0 and 20. Lower values can reduce runtimes.
         Measuring consistency helps quantify the epistemic uncertainty associated with
         strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response.
         TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible.
-
-        num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence.
-        The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores.
-        Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
         similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the
         trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model.
         Supported similarity measures include - "semantic" (based on natural language inference),
         "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model),
         "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies),
-        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs.
-
-        reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens)
-        when generating alternative possible responses and reflecting on responses during trustworthiness scoring.
-        Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs.
-
-        log (list[str], default = []): optionally specify additional logs or metadata that TLM should return.
-        For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness.
+        and "string" (based on character/word overlap). Set this to "string" for minimal runtimes.
+        This parameter has no effect when `num_consistency_samples = 0`.
 
-        custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring.
-        The expected input format is a list of dictionaries, where each dictionary has the following keys:
-        - name: Name of the evaluation criteria.
-        - criteria: Instructions specifying the evaluation criteria.
+        num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`.
+        `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one.
+        You can auto-improve responses by increasing this parameter, but at higher runtimes/costs.
+        This parameter must be between 1 and 20. It has no effect on `TLM.score()`.
+        When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it.
+        This parameter has no effect when `disable_trustworthiness` is True.
 
-        use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead.
+        disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses.
+        This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response.
+        The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`.
     """
 
     quality_preset: Literal["best", "high", "medium", "low", "base"]
@@ -112,6 +118,8 @@ class TlmScoreParams(TypedDict, total=False):
 class Options(TypedDict, total=False):
     custom_eval_criteria: Iterable[object]
 
+    disable_trustworthiness: bool
+
     log: List[str]
 
     max_tokens: int
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 2159a1f0..586b0fa9 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -617,6 +617,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None:
             ],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",
@@ -1291,6 +1292,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -
             ],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",
diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py
index fd78e60c..fd977f6a 100644
--- a/tests/api_resources/test_tlm.py
+++ b/tests/api_resources/test_tlm.py
@@ -33,6 +33,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None:
             constrain_outputs=["string"],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",
@@ -92,6 +93,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None:
             constrain_outputs=["string"],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",
@@ -157,6 +159,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) ->
             constrain_outputs=["string"],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",
@@ -216,6 +219,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N
             constrain_outputs=["string"],
             options={
                 "custom_eval_criteria": [{}],
+                "disable_trustworthiness": True,
                 "log": ["string"],
                 "max_tokens": 0,
                 "model": "model",

From 9159de606715e8232442712d6f9145bd1de4cc1c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 13 Aug 2025 02:08:32 +0000
Subject: [PATCH 226/227] chore(internal): codegen related update

---
 tests/conftest.py    |   8 +-
 tests/test_client.py | 181 ++++++++++++++++++++++++++++++++-----------
 2 files changed, 141 insertions(+), 48 deletions(-)

diff --git a/tests/conftest.py b/tests/conftest.py
index 3472c36d..ed898e38 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -45,6 +45,8 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
 
+auth_token = "My Auth Token"
+
 
 @pytest.fixture(scope="session")
 def client(request: FixtureRequest) -> Iterator[Codex]:
@@ -52,7 +54,7 @@ def client(request: FixtureRequest) -> Iterator[Codex]:
     if not isinstance(strict, bool):
         raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
 
-    with Codex(base_url=base_url, _strict_response_validation=strict) as client:
+    with Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=strict) as client:
         yield client
 
 
@@ -76,5 +78,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]:
     else:
         raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
 
-    async with AsyncCodex(base_url=base_url, _strict_response_validation=strict, http_client=http_client) as client:
+    async with AsyncCodex(
+        base_url=base_url, auth_token=auth_token, _strict_response_validation=strict, http_client=http_client
+    ) as client:
         yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index 24749152..cdc717fd 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -37,6 +37,7 @@
 from .utils import update_env
 
 base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+auth_token = "My Auth Token"
 
 
 def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
@@ -58,7 +59,7 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int:
 
 
 class TestCodex:
-    client = Codex(base_url=base_url, _strict_response_validation=True)
+    client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     def test_raw_response(self, respx_mock: MockRouter) -> None:
@@ -84,6 +85,10 @@ def test_copy(self) -> None:
         copied = self.client.copy()
         assert id(copied) != id(self.client)
 
+        copied = self.client.copy(auth_token="another My Auth Token")
+        assert copied.auth_token == "another My Auth Token"
+        assert self.client.auth_token == "My Auth Token"
+
     def test_copy_default_options(self) -> None:
         # options that have a default are overridden correctly
         copied = self.client.copy(max_retries=7)
@@ -101,7 +106,9 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Codex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -133,7 +140,9 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = Codex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_query={"foo": "bar"}
+        )
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -257,7 +266,9 @@ def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     def test_client_timeout_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = Codex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
+        )
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -266,7 +277,9 @@ def test_client_timeout_option(self) -> None:
     def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         with httpx.Client(timeout=None) as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -274,7 +287,9 @@ def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         with httpx.Client() as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -282,7 +297,9 @@ def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = Codex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -291,16 +308,24 @@ def test_http_client_timeout_option(self) -> None:
     async def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             async with httpx.AsyncClient() as http_client:
-                Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                Codex(
+                    base_url=base_url,
+                    auth_token=auth_token,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
 
     def test_default_headers_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = Codex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
         client2 = Codex(
             base_url=base_url,
+            auth_token=auth_token,
             _strict_response_validation=True,
             default_headers={
                 "X-Foo": "stainless",
@@ -312,7 +337,12 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
+        client = Codex(
+            base_url=base_url,
+            auth_token=auth_token,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -511,7 +541,9 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = Codex(
+            base_url="https://example.com/from_init", auth_token=auth_token, _strict_response_validation=True
+        )
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -520,23 +552,28 @@ def test_base_url_setter(self) -> None:
 
     def test_base_url_env(self) -> None:
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = Codex(_strict_response_validation=True)
+            client = Codex(auth_token=auth_token, _strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                Codex(_strict_response_validation=True, environment="production")
+                Codex(auth_token=auth_token, _strict_response_validation=True, environment="production")
 
-            client = Codex(base_url=None, _strict_response_validation=True, environment="production")
+            client = Codex(
+                base_url=None, auth_token=auth_token, _strict_response_validation=True, environment="production"
+            )
             assert str(client.base_url).startswith("https://api-codex.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -556,9 +593,12 @@ def test_base_url_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -578,9 +618,12 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            Codex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             Codex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.Client(),
             ),
@@ -598,7 +641,7 @@ def test_absolute_request_url(self, client: Codex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     def test_copied_client_does_not_close_http(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -609,7 +652,7 @@ def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     def test_client_context_manager(self) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
         with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -630,7 +673,9 @@ class Model(BaseModel):
 
     def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            Codex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, max_retries=cast(Any, None)
+            )
 
     @pytest.mark.respx(base_url=base_url)
     def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -639,12 +684,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = Codex(base_url=base_url, _strict_response_validation=True)
+        strict_client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             strict_client.get("/foo", cast_to=Model)
 
-        client = Codex(base_url=base_url, _strict_response_validation=False)
+        client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False)
 
         response = client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -672,7 +717,7 @@ class Model(BaseModel):
     )
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = Codex(base_url=base_url, _strict_response_validation=True)
+        client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
@@ -840,7 +885,7 @@ def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
 
 
 class TestAsyncCodex:
-    client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+    client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -868,6 +913,10 @@ def test_copy(self) -> None:
         copied = self.client.copy()
         assert id(copied) != id(self.client)
 
+        copied = self.client.copy(auth_token="another My Auth Token")
+        assert copied.auth_token == "another My Auth Token"
+        assert self.client.auth_token == "My Auth Token"
+
     def test_copy_default_options(self) -> None:
         # options that have a default are overridden correctly
         copied = self.client.copy(max_retries=7)
@@ -885,7 +934,9 @@ def test_copy_default_options(self) -> None:
         assert isinstance(self.client.timeout, httpx.Timeout)
 
     def test_copy_default_headers(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCodex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         assert client.default_headers["X-Foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -917,7 +968,9 @@ def test_copy_default_headers(self) -> None:
             client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
 
     def test_copy_default_query(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"})
+        client = AsyncCodex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_query={"foo": "bar"}
+        )
         assert _get_params(client)["foo"] == "bar"
 
         # does not override the already given value when not specified
@@ -1041,7 +1094,9 @@ async def test_request_timeout(self) -> None:
         assert timeout == httpx.Timeout(100.0)
 
     async def test_client_timeout_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0))
+        client = AsyncCodex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
+        )
 
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1050,7 +1105,9 @@ async def test_client_timeout_option(self) -> None:
     async def test_http_client_timeout_option(self) -> None:
         # custom timeout given to the httpx client should be used
         async with httpx.AsyncClient(timeout=None) as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1058,7 +1115,9 @@ async def test_http_client_timeout_option(self) -> None:
 
         # no timeout given to the httpx client should not use the httpx default
         async with httpx.AsyncClient() as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1066,7 +1125,9 @@ async def test_http_client_timeout_option(self) -> None:
 
         # explicitly passing the default timeout currently results in it being ignored
         async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
-            client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client)
+            client = AsyncCodex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client
+            )
 
             request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
             timeout = httpx.Timeout(**request.extensions["timeout"])  # type: ignore
@@ -1075,16 +1136,24 @@ async def test_http_client_timeout_option(self) -> None:
     def test_invalid_http_client(self) -> None:
         with pytest.raises(TypeError, match="Invalid `http_client` arg"):
             with httpx.Client() as http_client:
-                AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client))
+                AsyncCodex(
+                    base_url=base_url,
+                    auth_token=auth_token,
+                    _strict_response_validation=True,
+                    http_client=cast(Any, http_client),
+                )
 
     def test_default_headers_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"})
+        client = AsyncCodex(
+            base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         assert request.headers.get("x-foo") == "bar"
         assert request.headers.get("x-stainless-lang") == "python"
 
         client2 = AsyncCodex(
             base_url=base_url,
+            auth_token=auth_token,
             _strict_response_validation=True,
             default_headers={
                 "X-Foo": "stainless",
@@ -1096,7 +1165,12 @@ def test_default_headers_option(self) -> None:
         assert request.headers.get("x-stainless-lang") == "my-overriding-header"
 
     def test_default_query_option(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"})
+        client = AsyncCodex(
+            base_url=base_url,
+            auth_token=auth_token,
+            _strict_response_validation=True,
+            default_query={"query_param": "bar"},
+        )
         request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
         url = httpx.URL(request.url)
         assert dict(url.params) == {"query_param": "bar"}
@@ -1295,7 +1369,9 @@ class Model(BaseModel):
         assert response.foo == 2
 
     def test_base_url_setter(self) -> None:
-        client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True)
+        client = AsyncCodex(
+            base_url="https://example.com/from_init", auth_token=auth_token, _strict_response_validation=True
+        )
         assert client.base_url == "https://example.com/from_init/"
 
         client.base_url = "https://example.com/from_setter"  # type: ignore[assignment]
@@ -1304,23 +1380,28 @@ def test_base_url_setter(self) -> None:
 
     def test_base_url_env(self) -> None:
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
-            client = AsyncCodex(_strict_response_validation=True)
+            client = AsyncCodex(auth_token=auth_token, _strict_response_validation=True)
             assert client.base_url == "http://localhost:5000/from/env/"
 
         # explicit environment arg requires explicitness
         with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"):
             with pytest.raises(ValueError, match=r"you must pass base_url=None"):
-                AsyncCodex(_strict_response_validation=True, environment="production")
+                AsyncCodex(auth_token=auth_token, _strict_response_validation=True, environment="production")
 
-            client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production")
+            client = AsyncCodex(
+                base_url=None, auth_token=auth_token, _strict_response_validation=True, environment="production"
+            )
             assert str(client.base_url).startswith("https://api-codex.cleanlab.ai")
 
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1340,9 +1421,12 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1362,9 +1446,12 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None:
     @pytest.mark.parametrize(
         "client",
         [
-            AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True),
+            AsyncCodex(
+                base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True
+            ),
             AsyncCodex(
                 base_url="http://localhost:5000/custom/path/",
+                auth_token=auth_token,
                 _strict_response_validation=True,
                 http_client=httpx.AsyncClient(),
             ),
@@ -1382,7 +1469,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None:
         assert request.url == "https://myapi.com/foo"
 
     async def test_copied_client_does_not_close_http(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
         assert not client.is_closed()
 
         copied = client.copy()
@@ -1394,7 +1481,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
         assert not client.is_closed()
 
     async def test_client_context_manager(self) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
         async with client as c2:
             assert c2 is client
             assert not c2.is_closed()
@@ -1416,7 +1503,9 @@ class Model(BaseModel):
 
     async def test_client_max_retries_validation(self) -> None:
         with pytest.raises(TypeError, match=r"max_retries cannot be None"):
-            AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None))
+            AsyncCodex(
+                base_url=base_url, auth_token=auth_token, _strict_response_validation=True, max_retries=cast(Any, None)
+            )
 
     @pytest.mark.respx(base_url=base_url)
     @pytest.mark.asyncio
@@ -1426,12 +1515,12 @@ class Model(BaseModel):
 
         respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
 
-        strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        strict_client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
         with pytest.raises(APIResponseValidationError):
             await strict_client.get("/foo", cast_to=Model)
 
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=False)
+        client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False)
 
         response = await client.get("/foo", cast_to=Model)
         assert isinstance(response, str)  # type: ignore[unreachable]
@@ -1460,7 +1549,7 @@ class Model(BaseModel):
     @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
     @pytest.mark.asyncio
     async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
-        client = AsyncCodex(base_url=base_url, _strict_response_validation=True)
+        client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True)
 
         headers = httpx.Headers({"retry-after": retry_after})
         options = FinalRequestOptions(method="get", url="/foo", max_retries=3)

From aae872824f8c06556f051d14254a5c7ccda1dc0e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
 <142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 13 Aug 2025 16:33:40 +0000
Subject: [PATCH 227/227] codegen metadata

---
 .stats.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.stats.yml b/.stats.yml
index 5e0a177c..b509115f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
 configured_endpoints: 54
-openapi_spec_hash: 924a89b5f031d9215a5a701f834b132f
+openapi_spec_hash: 04e1b7aefbeff10daab249b153de147f
 config_hash: 930284cfa37f835d949c8a1b124f4807
