Coverage for src/keel/vocab.py: 100%
10 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 20:26 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 20:26 +0000
1"""The delegate vocabulary — the leaf both halves of keel read it from (#1050).
3Two facts have to be known in two places that cannot import each other:
5* which providers keel understands with no configuration at all
6 (:data:`BUILTIN_DELEGATE_VENDORS`), and
7* which reasoning efforts exist and which providers can honour one
8 (:data:`EFFORTS`, :func:`supports_effort`).
10:mod:`keel.agents` and :mod:`keel.delegate` *dispatch* on them; :mod:`keel.team` and
11:mod:`keel.config` *validate against* them. Dispatch imports :mod:`keel.config` for the
12config types, and :mod:`keel.config` imports :mod:`keel.team` for the policy type — so
13the validating half used to reach the vocabulary through a function-local import.
14Benign at run time, but a cyclic-import alert to a scanner (CodeQL 58–61) and a
15paragraph of explanation to every reader, every time (#1050).
17The fix is not a cleverer import: a vocabulary two sides share is owned by neither.
18This module is the leaf that owns it. It imports **nothing** from the rest of the
19package, and it has to stay that way — an import added here re-opens the cycle it
20exists to close. ``tests/test_vocab_leaf.py`` pins both halves: this module stays a
21leaf, and no module reaches these names function-locally again.
23Every name keeps its original spelling *and* its original home as a re-export
24(``keel.agents.BUILTIN_DELEGATE_VENDORS``, ``keel.delegate.EFFORTS``, …), so this is a
25move with no caller, no CLI surface and no ``config_hash`` input changed.
26"""
28from __future__ import annotations
30#: Agent-CLI delegate vendors keel drives as a subprocess. Hardcoded on purpose — not
31#: to be confused with the generic ``cli`` *profile* vendor (issue #659), which is the
32#: operator-configured escape hatch for every CLI that is not one of these three.
33CLI_VENDORS = ("claude", "codex", "agy")
35#: Local-model delegate vendors: no agent CLI, no hosted key, and no tools.
36LOCAL_VENDORS = ("ollama",)
38#: Hosted-API delegate vendors (#548, ``google-api`` added in #666): the vendor's
39#: real API keyed by an env token, no agent CLI installed. Same no-tools contract
40#: as ``ollama:`` — the
41#: orchestrator owns every git/PR step and delegates only code generation. The
42#: vendor names match ai-jury's hosted-adapter vocabulary so the value fits the
43#: existing first-colon ``vendor:model`` split unchanged.
44API_VENDORS = ("anthropic-api", "openai-api", "google-api")
46#: Every delegate name keel understands with no configuration at all. Name resolution
47#: is **fail-closed**: a ``knobs.delegate_profiles`` entry may not shadow one of these,
48#: and the attempt is a config error rather than a silent override (issue #659).
49BUILTIN_DELEGATE_VENDORS = CLI_VENDORS + LOCAL_VENDORS + API_VENDORS
51#: The one vendor whose endpoint and key-env name come from ``knobs.delegate_profiles``
52#: instead of :mod:`keel.api_delegate`'s hardcoded table (#666).
53OPENAI_COMPATIBLE = "openai-compatible"
55#: Reasoning-effort levels, lowest first. Mapped per vendor by
56#: :func:`keel.delegate.plan_run`.
57EFFORTS = ("low", "medium", "high")
59#: Vendors that have a spelling for reasoning effort. Everything else reaches
60#: :func:`keel.delegate._apply_effort`'s fallback, where an ``--effort`` request becomes
61#: a warning plus ``effort_applied: false``. That is the right answer for a *run* — a
62#: flag that did not take effect must be visible, not fatal — but the wrong one for
63#: **config**: a ``knobs.team`` seat that pairs ``claude`` with ``effort: high`` is a
64#: policy stating something keel can never do, so :func:`keel.team.team_issues` rejects
65#: it up front. The tuple is asserted against ``_apply_effort`` itself by
66#: ``tests/test_delegate.py`` so the two cannot drift.
67EFFORT_VENDORS = ("agy", "codex", "anthropic-api", "openai-api", "google-api", OPENAI_COMPATIBLE)
70def supports_effort(vendor: str) -> bool:
71 """True when ``vendor`` can express a reasoning-effort request in its own spelling."""
72 return vendor in EFFORT_VENDORS