Guidance for agentic coding tools working in this repository. Scope: entire repo.
CLAUDE.md is a symlink to this file. Always edit AGENTS.md directly; never modify CLAUDE.md.
- Project:
otari(PyPI package nameotari), the Python client SDK for the otari gateway / platform.OtariClient(sync) andAsyncOtariClient(async) talk to a running gateway over HTTP. - Language/runtime: Python 3.11+ (CI matrix: 3.11, 3.12, 3.13).
- Package manager + task runner:
uv. - Source root:
src/otari(imported asotari). - Tests:
tests/unit(mocked, offline) andtests/integration(real gateway).
This SDK is a thin, hand-written shell over an OpenAPI-generated typed core. Read these together before changing request behavior.
- Generated core (
src/otari/_client/): produced by OpenAPI Generator from the gateway's OpenAPI spec. It is generated, not hand-edited. Regeneration happens upstream in the gateway repo (.github/workflows/gateway-sdk-codegen.yml), which opens asdk-codegen/client-corePR here. The core is excluded from ruff and mypy (pyproject.toml:extend-exclude/ mypyexclude). Do not edit it to fix a lint error; fix the shell or the upstream spec/generator instead. - Hand-written shell (everything else under
src/otari/):client.py/async_client.py: ergonomicOtariClient/AsyncOtariClientwithcompletion,response,message,embedding,moderation,rerank,list_models, batch operations, and acontrol_planeaccessor._base.py: shared logic: auth-mode resolution, default headers, URL normalization, and the single seam where generatedApiExceptionis caught and mapped to typed errors._streaming.py: hand-written SSE shim. The generated core buffers and cannot stream, so streaming endpoints use rawhttpx+ a line/event parser. Chat streaming yields typedChatCompletionChunk; responses/messages streaming yields raw eventdicts (no chunk model exists for those).errors.py: typed error hierarchy (OtariErrorbase + subclasses).types.py: re-exports of generated models plus hand-written TypedDicts (batch/options).control_plane.py: wrapper over the management endpoints (keys/users/budgets/pricing/usage).
Resolved in _base.py from constructor args, then environment:
- Platform (
OTARI_AI_TOKEN/platform_token):Authorization: Bearer <token>, base URL defaults tohttps://api.otari.ai. - Self-hosted (
api_key+api_base, envGATEWAY_API_KEY/GATEWAY_API_BASE):Otari-Keyheader;api_baseis required in this mode. Error mapping applies in both modes; do not regress one when changing the other.
sdk-endpoints.txt records which gateway endpoints this SDK surfaces ([covered]) and which it
deliberately does not ([excluded], with a reason). It is a generated artifact. The gateway's
codegen workflow pushes it here from the canonical copy at scripts/sdk_codegen/sdk-endpoints.txt
in mozilla-ai/otari, so an edit made in this repo is overwritten on the next regeneration. To
change coverage classification, edit the canonical copy in the gateway.
tests/unit/test_endpoint_coverage.py only checks the manifest's structure, offline. The drift gate that compares it
against the OpenAPI spec runs in the gateway, against the spec from the same commit. It used to run
here over the network, which made the result depend on when CI ran rather than on the commit; see
mozilla-ai/otari#438.
- Install (dev):
uv sync --extra dev
- Full suite:
uv run pytest - Unit only:
uv run pytest tests/unit - Single test:
uv run pytest tests/unit/test_client.py::TestOtariClient::test_completion -v - Manifest checks:
uv run pytest tests/unit/test_endpoint_coverage.py -v - Integration tests under
tests/integration/spawn / require a real gateway and are skipped when one is not available.
- Lint:
uv run ruff check . - Typecheck (mypy strict):
uv run mypy src/ - Build:
uv build
from __future__ import annotationsat the top of modules;TYPE_CHECKINGfor type-only imports; importCallable/Iteratorfromcollections.abc.- mypy is
strict; the generatedotari._clientis excluded. New/changed shell code must be fully typed. Use@overloadfor streaming polymorphism (stream=Truevs not), asclient.pyalready does. - Public API is exported from
src/otari/__init__.py(clients, errors, types); don't remove or rename exports without auditing callers. - Unit tests mock at the transport seam (the generated core's REST client) and use
respxfor the raw-httpxstreaming path. Test classes areTest<Feature>.
- Touched request handling, auth, or errors → run
tests/unitand confirm both auth modes still map errors correctly. - Touched streaming → run the streaming tests; verify chat yields
ChatCompletionChunkand responses/messages yield raw dicts. - Added/removed an endpoint wrapper → update the canonical
sdk-endpoints.txtinmozilla-ai/otari(scripts/sdk_codegen/); the copy here is regenerated. - Always run
uv run ruff check .anduv run mypy src/before opening a PR.
- Avoid em dashes and double hyphens (
--) used as separators in prose (README, docs, doc comments, commit messages, PR descriptions). Use commas, semicolons, colons, parentheses, or periods, or rephrase. This does not apply to code (for example CLI flags like--all) or en-dash numeric ranges like3–4.
- Never hand-edit
src/otari/_client/; it is regenerated from the gateway spec. - Prefer minimal, targeted edits; match existing typing and import style in touched files.
- Preserve security-relevant behavior (header/auth handling, error-detail boundaries).