Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Anthropic lanes: send the model as written; the prefix picks the route by rejojer · Pull Request #443 · VectifyAI/PageIndex · GitHub
Skip to content

Anthropic lanes: send the model as written; the prefix picks the route - #443

Open
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model
Open

Anthropic lanes: send the model as written; the prefix picks the route#443
rejojer wants to merge 23 commits into
mainfrom
feat/claude-agent-config-model

Conversation

@rejojer

@rejojerrejojer commented Aug 28, 2026

Copy link
Copy Markdown
Member

The Anthropic-native lanes (messages(), anthropic_runner_config(), claude_agent_config()) ignored chat_model; under the two-switch design (api_key= where the documents are, chat= who answers) they were the lanes where chat= did not answer.

Now, on all three:

  • A chat_model you set carries over, and any model name is sent as written — no gate, no model list, no LiteLLM lookup; the destination judges the id. Only the routing prefix is read: bedrock/, vertex_ai/, and azure_ai/ select that channel, litellm/ and anthropic/ drop, and anything else (bare ids, aliases like sonnet, gateway names) ships verbatim on the direct route.
  • messages() constructs the transport the prefix declaresAnthropic, AnthropicBedrock, AnthropicVertex, or AnthropicFoundry (Azure / Microsoft Foundry) — with chat_backend passed to that constructor (aws_region, project_id, resource, …; unset keys fall to each SDK's own env defaults). The anthropic extra floor moves to >=0.122.0, where those clients gain the tool runner. anthropic_runner_config() stays transport-agnostic (the caller builds the client and its kwargs now work on all of them); claude_agent_config() hands the stripped id to the Claude Code CLI and carries the matching switch (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) in the config's env, so the prefix picks the channel there too.
  • The stock default never impersonates a choice. Whether chat_model was ever set is recorded at construction and on assignment, not inferred by comparing values. With the untouched stock default, claude_agent_config() omits the model key (the SDK's own default runs, as before) and the two wire-bound surfaces raise needs a model — pass a Claude model=..., or configure chat_model instead of sending a name the user never wrote. Explicitly writing the stock value is a choice like any other and carries.
  • LiteLLM leaves these lanes entirely: no import (2.5–2.7 s and an offline-hang risk on first touch, measured), no provider table, no "claude" substring. The rules are vendor-free — nothing changes if the stock default ever changes vendors.

Compared to the previous revision of this branch, the refusals are gone: Bedrock/Vertex/Azure Claude ids work on every lane, an explicit non-Claude choice goes to the wire and gets the provider's own 404, and error messages no longer claim a name "is not Claude" or leak None.

One deliberate behavior flip from the previous revision: explicitly constructing with the stock value (chat_model="gpt-5.6-sol") now carries it like any chosen model instead of being silently treated as no choice.

Tests: 452 green; every new or flipped test red-verified against the previous revision; the no-frameworks CI leg simulated (agents/anthropic/claude-agent-sdk/boto3/google blocked: 303 passed, 149 skipped); pyright on the two touched modules 36 → 33.

README: main's extraction of the usage guide to docs.pageindex.ai landed mid-PR and is merged in; this branch's earlier README snippet fixes lived in the extracted sections and are superseded by that move. The agent-integration contract now lives at docs.pageindex.ai/sdk/agents, which needs updating for these semantics.

https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U

The Claude Agent SDK lane ignored chat_model: the agent ran on the SDK's
default no matter what the client was told. A chosen Claude chat_model is
now forwarded (LiteLLM's anthropic/ prefix stripped, provider detected via
litellm.get_llm_provider so routed names are refused, not prefix-guessed);
a chosen non-Claude model raises with a pointer to model= and
openai_agent_config(); the never-chosen default keeps the SDK's own model
so existing clients are untouched. New model= takes the SDK's own name
verbatim and wins.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
messages(), anthropic_runner_config(), and claude_agent_config(model=) all
hand the name to Anthropic directly, where the client's LiteLLM spelling
(anthropic/claude-x) is a 404 — verified live. A method on the PageIndex
client takes the client's spelling: the prefix is dropped, by string strip
so SDK aliases like "sonnet" still pass. max_tokens defaults now resolve on
the stripped id as well.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer
rejojerforce-pushed the feat/claude-agent-config-model branch from 83f3422 to f7266faCompareAugust 28, 2026 13:15
Same rule as claude_agent_config(): model= left unset takes the chosen
Claude chat_model with LiteLLM's anthropic/ prefix stripped; any other
provider raises with a pointer to model=. These two have no SDK default to
fall back on, so the never-chosen gpt default raises as well — as it did
when model= was missing, now readably. model= relaxes to Optional on both;
positional callers are unaffected. One resolver serves all three surfaces.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
LiteLLM resolves bare names through its model map, so a Claude release
newer than the installed LiteLLM (or a -latest alias) raised BadRequest and
was refused as "not Claude". LiteLLM stays the authority; a name it cannot
resolve falls back to Anthropic's own rule: every Claude id starts with
"claude". Prefixed anthropic/ names were never affected.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
Folded in from the docs branch (#442): a new "MCP server" item under (b)
with the hosted config verbatim from docs.pageindex.ai/mcp; the Claude
Agent SDK item names ANTHROPIC_API_KEY, the chat_model rule, and model= in
the explicit form; messages() and the tool runner examples drop model=
and say they assume a Claude chat_model; the OpenAI Agents explicit form
notes the litellm/ prefix and cache settings the helper adds; wording
touches on the (b) intro and the Anthropic SDK item title.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
@rejojer

rejojer commented Aug 28, 2026

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 11c74b7, which folded the two model-name paths into one resolver: the litellm/ spelling now resolves on chat_model and explicit model= alike, and max_tokens resolves on the stripped id.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Explicit model= went through a prefix strip of its own while chat_model
went through LiteLLM; the two disagreed on litellm/ (dropped everywhere
else in the client, refused here) and were two code paths for one job.
Both now read the name the client's way: litellm/ dropped, LiteLLM names
the provider, anthropic/ goes. The fallback for a name LiteLLM cannot
place is this lane's rule — no provider prefix means Anthropic's own — so
Claude Agent SDK aliases and unreleased ids reach the destination instead
of being guessed at by a "claude" prefix.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The OpenAI Agents explicit form said "local clients only" where the
condition is own-model chat (cloud with chat= included; None on managed
chat); the tool-runner explicit form dropped the cache_control the helper
sets, so a copy lost prompt caching silently; ANTHROPIC_API_KEY is named
on every Anthropic lane, not just the Claude Agent SDK; one phrasing for
the chat_model rule across the three.
Claude-Session: https://claude.ai/code/session_01F84VYrC858n9cr5eRjV4Cy
The docstring ended on "anything else is refused, like every other
provider", but the fallback it had just described does the opposite: a
slash-free name LiteLLM cannot place is passed through for the
destination to judge, which is the whole point of the fallback. Only a
name LiteLLM places with another provider, or an unplaceable prefixed
one, is refused. All three branches now read as the code runs.
Claude-Session: https://claude.ai/code/session_01W6ZhgbVFJxjfzRWya7rPbg
_claude_model_name() leaves litellm alone when there is no name to
place. On a managed-chat cloud client, anthropic_runner_config() without
model= imported litellm synchronously before _preload_litellm had
stamped LITELLM_LOCAL_MODEL_COST_MAP: a multi-second network fetch of
the model map, paid for an error the call raises anyway. Now it raises
in a millisecond.
litellm prints a red "Provider List" banner to stdout before raising on
a name it cannot place, which is every alias and every id newer than its
map, the exact names the fallback exists for; suppress_debug_info is its
switch for that.
claude_agent_config() resolves the model before building the config,
the order anthropic_runner_config() already has, so a non-Claude
chat_model no longer pays the MCP initialize round trip before raising.
The DEFAULT_CHAT_MODEL import moves into the branch that uses it: a
managed-chat cloud client no longer imports utils, and with it
load_dotenv() into os.environ, from a config getter.
README: the two examples that lean on a Claude chat_model say so inline,
since the Step 1 client's chat_model is not one. Tests match the gate's
own message; "model=" also matched the messages() own-chat guard.
Claude-Session: https://claude.ai/code/session_01TkZw9WFCEZKbBy7HNaX8oF
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Re-checked at 08ac1dc, which resolves the model before building the config and drops the resolver's side effects. Verified by execution: the no-name path raises in ~18 ms without importing litellm, and the "Provider List" banner is gone from every branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

The litellm gate is gone: messages(), anthropic_runner_config() and
claude_agent_config() now read only the routing prefix (bedrock/,
vertex_ai/, anthropic/, litellm/) and ship the id verbatim — the
destination judges it. messages() constructs the transport class the
prefix declares (AnthropicBedrock / AnthropicVertex; anthropic
>= 0.122.0 has their tool runner). The stock chat_model default is
tracked as a construction-time flag instead of a value compare, and it
never impersonates a choice: the claude lane omits the model key, the
two wire-bound surfaces ask for one.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
LiteLLM already names the route (azure_ai/, Claude entries in its map),
AnthropicFoundry ships at the 0.122.0 floor, and Claude Code serves the
channel via CLAUDE_CODE_USE_FOUNDRY — every roster condition holds, so
the row costs one tuple element and one class mapping.
Claude-Session: https://claude.ai/code/session_014Zs7gzhCtUgnybaDfASW9U
@rejojerrejojer changed the title claude_agent_config(): forward a Claude chat_model, add model=Anthropic lanes: send the model as written; the prefix picks the routeAug 31, 2026
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- Construction failures wrap as "backend is not configured" on every
route: Vertex and Foundry refuse a missing region or credential at
construction, each with its own exception type; only the direct
route's TypeError was caught.
- A tool-runner probe where the routes converge: an anthropic build
predating a route's runner passed _require_anthropic and died in an
AttributeError; it now names the gap and the upgrade.
- A chat_model set in config.yaml counts as chosen, exactly like the
constructor spellings (blank values mean absent, also like them); the
Anthropic surfaces no longer refuse a model the chat lanes honor.
- claude_agent_config() carries a bedrock/vertex_ai/azure_ai prefix as
the matching CLAUDE_CODE_USE_* switch. Foundry ids double as valid
direct ids, so without the switch that channel ran the wrong
transport with no signal; now the prefix picks the channel here too.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
Version numbers in messages drift — this branch moved the floor and
left two copies stale. Messages and docstrings now say what is
missing; pyproject's constraint is the single source. _claude_wire's
docstring also gains the azure_ai row it routes.
Claude-Session: https://claude.ai/code/session_01L3kPdmRDLFP2iqJtuwdRgD
# config.yaml is the third way to name a chat model; a key set there
# must read as chosen, exactly like the constructor spellings.
pytest.importorskip("anthropic")
import pageindex.utils
# surface has no tool runner: name the gap, not an AttributeError.
class _Runnerless:
class beta:
class messages: ...
- The route tests' importorskip guards asked for boto3/google-auth that
the tests never use (explicit-credential construction, no network), so
every CI leg skipped them and _ROUTE_CLIENTS had no executed coverage.
Deleted; verified green with those imports blocked.
- gate ran under always(), which also fires on a cancelled run, so
cancel-in-progress turned every superseded push into a red required
check. !cancelled() keeps fail-on-red without the false red.
- The stretch between transport construction and the runner handoff (the
ceiling probe, the tool_runner build) could raise past the close the
branches below own; one close-on-failure now covers the whole window,
and the version-gap probe folds into it.
Claude-Session: https://claude.ai/code/session_01EhcWJpnngbgHAbx5MxQuu5
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Bedrock resolves credentials per request and fails with a bare
RuntimeError (anthropic/lib/bedrock/_auth.py); Vertex with google.auth's
own types. Neither is AnthropicError nor the direct route's TypeError,
so a missing credential — first-run state on those channels — escaped
messages() as a raw third-party exception. 90c9835 widened the
construction-time catch for exactly this reason; this is the
request-time half, wrapped with the same message. The direct route's
handlers are untouched (route-gated), and non-credential exceptions
still propagate.
Three doc truths alongside: messages()'s summary no longer claims every
call drives /v1/messages under ANTHROPIC_API_KEY (the Args' three
routes say otherwise); claude_agent_config()'s bundle description names
the model/env keys a chosen model adds ("three slots" undersold it
since the route carry landed); the growth-rule comment stops claiming
Mantle waits on a LiteLLM prefix name — litellm 1.97.0 ships
bedrock_mantle and the SDK ships AnthropicBedrockMantle, so Mantle
clears both bars and waits only on someone asking.
Claude-Session: https://claude.ai/code/session_018QVVYEbeo639N97j3TgD1j
Seven round-8 review fixes, three of them this PR's own features
finishing incompletely:
- claude_agent_config: a written route prefix now blanks the other
CLAUDE_CODE_USE_* switches ("" is off to the CLI; "0" would read as
on) and anthropic/ blanks all three. ClaudeAgentOptions.env merges
OVER the inherited environment and the CLI reads any set switch by
its own fixed precedence, so an exported CLAUDE_CODE_USE_BEDROCK=1
silently beat an explicit vertex_ai/ prefix — and beat anthropic/
outright, which emitted no env at all. Bare names still leave env
out: they name a model, not a channel.
- _default_max_tokens takes the route and looks the ceiling up in the
route's own spelling via get_model_info (model_cost misses the
bedrock region-namespaced ids): the stripped wire id resolved
nowhere, so an enabled thinking budget sailed past the channel's
output ceiling into a guaranteed 400. anthropic_runner_config keeps
the route it used to discard, for the same lookup.
- _litellm_claude_marks: azure_ai joins the provider tuple — the one
route table the PR missed. Claude-on-Foundry was silently repaying
full prompt price every turn on the LiteLLM lanes.
- run_messages names the missing route extra: pageindex[anthropic]
does not carry boto3/google-auth, so a route's first request died as
a bare ModuleNotFoundError whack-a-mole instead of naming
anthropic[bedrock] / anthropic[vertex]. A tool's own missing module
stays raw.
- The "set ANTHROPIC_API_KEY" remediation is direct-route-only now
(_not_configured): AnthropicVertex has no api_key parameter at all,
so the advice could not work where it was being given.
- owns_transport snapshots _ANTHROPIC_CLIENTS.values() via list():
dict_values has no __contains__, so membership iterates the live
view and a concurrent setdefault raises RuntimeError mid-scan.
- gate: back to always(). GitHub counts a SKIPPED required check as
passing, so !cancelled() let a cancelled run clear the one required
check on main with zero legs completed. The cost is a cosmetic red
on superseded shas, which required checks never read.
_ROUTE_ENV now sources _CLAUDE_ROUTES (byte-identical, declared 21
lines apart) and a table-agreement test makes the third copy
(_ROUTE_CLIENTS) and the marks predicate loud on drift. Docstring
truth alongside: messages() backend keys are the selected route's own;
anthropic_runner_config names the client class a routed prefix pairs
with; the constructor points cross-surface Claude users at the
anthropic/ spelling.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
An anthropic build predating AnthropicFoundry died on the azure_ai
route with a bare AttributeError at the getattr, while the very next
step — the tool-runner probe — hands the same category of failure (an
old build missing a piece the route needs) a named gap and the
upgrade pointer. Resolve the class explicitly and give the missing
case that same message, one step earlier.
Claude-Session: https://claude.ai/code/session_01GRxEKSfeDL4frnfc58ZtMS
@rejojer

Copy link
Copy Markdown
MemberAuthor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rejojer