Skip to content

feat(llm): run keyless on Claude Code login — claude-code provider + chat floor#186

Merged
pgsharma merged 1 commit into
mainfrom
feat/claude-code-provider
Jun 26, 2026
Merged

feat(llm): run keyless on Claude Code login — claude-code provider + chat floor#186
pgsharma merged 1 commit into
mainfrom
feat/claude-code-provider

Conversation

@pgsharma

Copy link
Copy Markdown
Contributor

What

You asked to use Claude Code as metalworks' model — no API key, as the default. This adds a claude-code provider (ClaudeCodeChatModel over the Claude Agent SDK) that runs completions through the bundled claude CLI using your existing Claude Code login, and makes it the keyless chat floor: when no key / ref / METALWORKS_MODEL is configured and the SDK + CLI are present, resolve_chat() falls back to it instead of raising MissingKeyError — the chat analogue of how embeddings already fall back to the local model. Any explicit key/ref still wins.

Verified live (keyless, no API key): complete_text → "Tokyo.", complete_structured{city, country} (native), and the floor via resolve_chat()ClaudeCodeChatModel → "Paris."

Why not MCP sampling

The cleaner "host does the inference" path (MCP sampling) is not supported by Claude Code — tracked in #1785 and closed "not planned" in #31893. The Agent SDK (subprocess) is the only viable keyless path.

Changes

  • llm/adapters/claude_code.py (new) — non-agentic completion (allowed_tools=[], max_turns=1, permission_mode="bypassPermissions"); native structured via the SDK's output_format json_schema → ResultMessage.structured_output, with the schema-in-prompt ladder as fallback; an async→sync bridge (one shared daemon event loop + run_coroutine_threadsafe) so the sync, thread-pooled ChatModel protocol drives the async-only SDK; SDK errors mapped to actionable MetalworksError.
  • config.pyclaude-code in _NATIVE_PROVIDERS; resolve_chat dispatch; _claude_code_available() (find_spec + shutil.which, no subprocess, no env read) gates the floor right before the final MissingKeyError, which now also names the metalworks[claude-code] option.
  • pyproject.tomlclaude-code = ["claude-agent-sdk>=0.2.110"] (+ in all).
  • tests (test_claude_code_adapter.py, new) — fully offline via a fake SDK module: text, native structured, prompt fallback, is_error, non-agentic options, alias strip, MissingExtraError, and the concurrent bridge (12 calls through a thread pool); plus floor resolution (engages / key-wins / unavailable-raises / claude-code/opus ref).
  • docs + CHANGELOG — with the honest trade-offs (spawns the CLI ~5–7s/call; uses your individual subscription; ToS note on distributed use).

Honest trade-offs (in the docs)

  • Spawns the bundled claude CLI per call (~5–7s/call) — the keyless convenience path, not the fast one. A key is faster for a many-call research run.
  • Uses your individual Claude Code subscription. Anthropic restricts offering claude.ai login in distributed products, so this is for local/individual use, not shipping a multi-user service on one subscription.

Gate

ruff check · ruff format --check · pyright (0 errors) · gen_ts_types.py --check PASSED · import metalworks stays free (SDK lazy-imported). Full suite 1188 passed clean-room (the 1 local failure is the pre-existing wedged-~/.metalworks/store.db disk I/O error from stale MCP servers — unrelated; touches nothing in llm//config). No version bump — tagging left to you.

🤖 Generated with Claude Code

…chat floor

Adds a `claude-code` provider (`ClaudeCodeChatModel` over the Claude Agent SDK)
so metalworks runs on the user's existing Claude Code login with NO API key, and
makes it the keyless chat floor: when no key/ref/METALWORKS_MODEL is configured
and the SDK + `claude` CLI are present, resolve_chat() falls back to it instead
of raising MissingKeyError (the chat analogue of the embeddings local-model
floor). Any explicit key/ref still wins.

- llm/adapters/claude_code.py: non-agentic completion (allowed_tools=[],
  max_turns=1, bypassPermissions); native structured via the SDK output_format
  json_schema → ResultMessage.structured_output, with the schema-in-prompt
  ladder as fallback; an async→sync bridge (one shared daemon event loop +
  run_coroutine_threadsafe) so the sync, thread-pooled protocol drives the
  async-only SDK; SDK errors mapped to actionable MetalworksError.
- config.py: claude-code in _NATIVE_PROVIDERS; resolve_chat dispatch;
  _claude_code_available() (find_spec + shutil.which, no subprocess) gates the
  floor before the final MissingKeyError; the raise now also names the extra.
- pyproject: claude-code = ["claude-agent-sdk>=0.2.110"] (+ in all).
- tests: offline (fake SDK module) — text, native structured, prompt fallback,
  is_error, non-agentic options, alias strip, MissingExtraError, concurrent
  bridge; floor resolution (engages / key-wins / unavailable-raises / ref).
- docs + CHANGELOG. Verified live: keyless complete_text/structured + floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Jun 26, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
metalworks 🟢 Ready View Preview Jun 26, 2026, 12:46 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@pgsharma pgsharma merged commit 9aa5e4d into main Jun 26, 2026
8 checks passed
@pgsharma pgsharma deleted the feat/claude-code-provider branch June 26, 2026 01:06
pgsharma pushed a commit that referenced this pull request Jun 26, 2026
Cut 0.4.0: the keyless Claude Code path (chat floor #186, web-search floor #187,
caveat fix #188) plus the 0.3.1–0.3.3 reliability run, with a full docs/metadata
pass and a positioning refresh.

- Version → 0.4.0 (pyproject + __init__ + plugin.json) + CHANGELOG [Unreleased] → [0.4.0].
- Docs audit: surface the keyless `claude-code` path across README, docs/ (installation,
  index, quickstart, cli, claude-code, configuration, extending, demand-research, ai-agents,
  custom-chatmodel, internals), plugin docs + skills, CONTRIBUTING; add the `claude-code` extra
  everywhere it's listed; fix the stale "submissions come from the HF Parquet mirror" claim (the
  live Arctic Shift API has been the default since 0.1.1).
- Positioning: drop "marketing research" (markety) and de-narrow the whole-product framing from
  Reddit-only to "real conversations across the web (Reddit, HN, forums, …)"; refresh keywords
  (out: marketing; in: validation, startups, founders). Reddit kept where it's a genuine feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pgsharma added a commit that referenced this pull request Jun 26, 2026
Cut 0.4.0: the keyless Claude Code path (chat floor #186, web-search floor #187,
caveat fix #188) plus the 0.3.1–0.3.3 reliability run, with a full docs/metadata
pass and a positioning refresh.

- Version → 0.4.0 (pyproject + __init__ + plugin.json) + CHANGELOG [Unreleased] → [0.4.0].
- Docs audit: surface the keyless `claude-code` path across README, docs/ (installation,
  index, quickstart, cli, claude-code, configuration, extending, demand-research, ai-agents,
  custom-chatmodel, internals), plugin docs + skills, CONTRIBUTING; add the `claude-code` extra
  everywhere it's listed; fix the stale "submissions come from the HF Parquet mirror" claim (the
  live Arctic Shift API has been the default since 0.1.1).
- Positioning: drop "marketing research" (markety) and de-narrow the whole-product framing from
  Reddit-only to "real conversations across the web (Reddit, HN, forums, …)"; refresh keywords
  (out: marketing; in: validation, startups, founders). Reddit kept where it's a genuine feature.

Co-authored-by: Zpoof <praguns0726@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to 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.

2 participants