@aetheros/provider-claude
v0.2.0
Published
Claude provider for AetherOS and Aevron: Anthropic API adapter, optional Claude Code executor, and reusable Claude Code templates.
Readme
@aetheros/provider-claude
Claude for AetherOS. Claude is one provider adapter for Aevron (@aetheros/aevron), next to OpenAI. It is not an AI engine: Aevron keeps providers, tools and approvals; this package only translates Aevron requests to Claude and back.
| Piece | What it does |
|---|---|
| ClaudeProvider | Anthropic Messages API adapter (official @anthropic-ai/sdk): configurable models, tool calling, streaming, structured output, typed errors |
| aevronTools(engine, specs) | Exposes tools already registered in Aevron to Claude; every call goes through engine.executeTool (host approval, actor, tenant) |
| ClaudeCodeProvider + createClaudeCodeExecutor | Optional: runs the Claude Code CLI (claude -p) in one directory, read-only by default |
| templates/claude-code/ + aether-claude init | Reusable Claude Code agents, skills, hooks and settings for any project |
Setup
Credentials stay out of the repository. Set ANTHROPIC_API_KEY in your shell or a git-ignored .env (see .env.example); the SDK also accepts ANTHROPIC_AUTH_TOKEN or an ant auth login profile. The key is never stored on public fields, logged, or put in error messages.
Anthropic API adapter
import {AevronEngine} from '@aetheros/aevron';
import {ClaudeProvider} from '@aetheros/provider-claude';
const engine = new AevronEngine();
engine.registerProvider(new ClaudeProvider()); // id "claude"
const {output} = await engine.generate({providerId: 'claude', prompt: 'Hello'});Options (all optional): model (default claude-opus-5-5, or ANTHROPIC_MODEL via fromEnvironment()), effort (low…max), system, maxTokens (16000; 64000 when streaming), maxToolRounds (8), maxRetries and timeoutMs (passed to the SDK), refusalFallback (default on), promptCache (default on) and cacheMinTokens, platform (anthropic, bedrock or vertex, see below), id, client (inject an SDK client, e.g. for tests), fetchImpl.
Both provider.run() and Aevron's generate() return {provider, output, model, stopReason, usage, toolCalls, data?, refusal?}. generate passes messages, responseFormat, onEvent, signal and tools straight through; context may also carry system, effort, promptCache and stream.
- Streaming: pass
onEvent(torunor the constructor). Events:{type: 'text', text},{type: 'tool_call', id, input},{type: 'tool_result', id, ok}. - Structured output:
responseFormat: {schema}sendsoutput_config.format(JSON schema); the parsed object isdata, and invalid JSON throwsinvalid_structured_output. - Refusals:
stopReason: 'refusal'comes back withrefusal: {category, explanation}, never as parsed data. On models that support it (Fable 5.1, Opus 5.5, Opus 5, Sonnet 5.5) the request opts into Anthropic's server-side fallback (fallbacks: "default"), so a declined request can be answered by a fallback model. Turn it off withrefusalFallback: false. - Errors: SDK errors become
ClaudeProviderErrorwithcode(authentication,permission,not_found,invalid_request,rate_limited,overloaded,server_error,connection,timeout,aborted,unsupported, …),retryable,statusandrequestId. Response bodies are never copied into messages. The SDK retries retryable errors itself (maxRetries, default 2). - Prompt caching (on by default): when the tool definitions plus system prompt are large enough (
cacheMinTokens, default 1024, estimated at four characters a token), the last tool definition and the system prompt getcache_control: {type: 'ephemeral'}, and the request uses automatic caching so the conversation prefix is reused on the next turn. At most four breakpoints are used, counting any you put inmessages. Turn it off withpromptCache: false(constructor, request orcontext).usage.inputTokenscounts all input;usage.cachedInputTokens(read from cache) andusage.cacheWriteTokens(written to cache) appear when non-zero. - Effort:
effort: 'low' | 'medium' | 'high'(the provider-neutral levels; Claude also acceptsxhighandmax) is sent asoutput_config.effort. On Opus 4.6 to 4.8 and Sonnet 4.6 it also turns on adaptive thinking (newer models think by default). Models without an effort setting (Haiku 4.5, Sonnet 4.5 and older) refuse it withunsupportedbefore anything is sent. - Documents:
{type: 'document', mediaType, data (base64), name?}in message content. PDFs (application/pdf) become base64 document blocks and plain text (text/plain) becomes a text document;nameis the title. Other types are refused withunsupported; images and documents together over 32 MB withrequest_too_large. The API also limits a PDF to 600 pages (100 on 200K-context models).
Company clouds: Amazon Bedrock and Google Vertex AI
new ClaudeProvider({platform: 'bedrock', region: 'eu-west-1'}); // https://bedrock-mantle.eu-west-1.api.aws/anthropic
new ClaudeProvider({platform: 'vertex', project: 'my-project', region: 'global'});Bedrock uses the Messages API at
bedrock-mantle(Claude Opus 4.7 and later), signed with AWS Signature V4 (Node crypto, no extra packages). Credentials:AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYandAWS_SESSION_TOKEN(temporary credentials), or a Bedrock bearer token inAWS_BEARER_TOKEN_BEDROCK, orcredentials: async () => ({accessKeyId, secretAccessKey, sessionToken})(a credential helper you write, for example around your SSO login). Region fromregionorAWS_REGION. Model ids get theanthropic.prefix automatically (claude-opus-5-5becomesanthropic.claude-opus-5-5); ids that already contain a dot are kept.Vertex AI calls
rawPredict/streamRawPredictwithanthropic_version: 'vertex-2023-10-16'in the body (vertexVersionchanges it). Access token fromGOOGLE_CLOUD_ACCESS_TOKENoraccessToken: async () => token; project fromprojectorGOOGLE_CLOUD_PROJECT; region fromregion,GOOGLE_CLOUD_LOCATIONorCLOUD_ML_REGION(defaultglobal). Get a token for one hour with:export GOOGLE_CLOUD_ACCESS_TOKEN="$(gcloud auth print-access-token)"PowerShell:
$env:GOOGLE_CLOUD_ACCESS_TOKEN = gcloud auth print-access-tokenendpointoverrides the address (https only, for a company proxy or private endpoint). Credentials are read before each request and are never stored on public fields or put in errors.fromEnvironment()picks the cloud fromANTHROPIC_PLATFORM(bedrockorvertex) orCLAUDE_CODE_USE_BEDROCK=1/CLAUDE_CODE_USE_VERTEX=1.Server-side refusal fallback is a Claude API feature and is off on both clouds. Model availability, prices and features follow each cloud.
Tools, approval and tenants
Simplest: let Aevron expose tools per call.
engine.registerTool({id: 'crm.lookup', description: 'Find a customer', inputSchema: {type: 'object', properties: {email: {type: 'string'}}}, execute: lookup});
await engine.generate({providerId: 'claude', prompt: 'Find [email protected]', tools: ['crm.lookup'], actor: 'user-1', tenantId: 'acme'});Or fix the tool list on the provider with aevronTools:
const engine = new AevronEngine({approve: async ({id, effect, actor, tenantId}) => myPolicy(id, effect, actor, tenantId)});
engine.registerTool({id: 'crm.lookup', execute: async ({input, tenantId}) => lookup(tenantId, input)});
engine.registerProvider(new ClaudeProvider({
tools: aevronTools(engine, [{id: 'crm.lookup', description: 'Find a customer', inputSchema: {type: 'object', properties: {email: {type: 'string'}}, required: ['email']}}])
}));
await engine.generate({providerId: 'claude', prompt: 'Find [email protected]', context: {actor: 'user-1', tenantId: 'acme'}});- Claude sees only the tools you list. A registered tool that is not listed answers "Unknown tool." and never runs.
- Each call goes through
engine.executeTool({id, input, actor, tenantId}): no approve callback, orfalse, means the tool does not run and Claude gets an error result. - Tool use without
actorandtenantIdis refused before any request is sent. - Rounds are capped (
maxToolRounds); tool input cut off atmax_tokensis never run. Tool ids are mapped to Claude-safe names (crm.lookup→crm_lookup). - Any object
{tools, invoke(id, input, {actor, tenantId})}works as a toolset, so hosts can bridge their own runtime the same way.
Claude Code executor (optional)
import {ClaudeCodeProvider, createClaudeCodeExecutor} from '@aetheros/provider-claude';
const executor = createClaudeCodeExecutor({cwd: '/abs/project', allowedTools: ['Read', 'Grep']});
engine.registerProvider(new ClaudeCodeProvider({id: 'claude-code', executor}));Nothing runs unless the host creates an executor. It runs claude -p --output-format json without a shell, with the prompt on stdin. The default permissionMode: 'plan' is read-only. default and acceptEdits require an approve callback that must return true for every run, and bypassPermissions is refused. It also supports model, maxTurns, allowedTools/disallowedTools, appendSystemPrompt, timeoutMs, signal and env. On Windows, point command at the claude.exe path.
Claude Code templates
npx aether-claude init --dry-run # show what would be added
npx aether-claude init # copy templates; existing files are never overwritten
npx aether-claude doctor # which Claude Code files the project has| File | Purpose |
|---|---|
| CLAUDE.md | Generic project rules: session start, issues and slices, branches and PRs, approvals, no secrets |
| .claude/agents/ | planner, architect, implementer, reviewer, security-reviewer, docs-writer, devops, release, screens |
| .claude/skills/ | build, plan-change, review-change, ship-check, handover, sprint, status, idea, run-flows |
| .claude/hooks/approval-gate.mjs | Locks paths listed in .aether/workflow.json until the owner answers "Approve…" in the session |
| .claude/hooks/session-start.mjs | Prints the handover's key sections at the start of every session |
| .claude/hooks/guard-git.mjs | Blocks force pushes, pushes to protected branches (AETHER_PROTECTED_BRANCHES, default main,master) and --no-verify commits |
| .claude/hooks/guard-secrets.mjs | Blocks writing .env files and content that looks like an API key, token or private key |
| .claude/hooks/status-event.mjs | Records agent activity for the Control Room (event kinds, agent types and times only) |
| .claude/settings.json | Wires all hooks and denies reading .env files |
Install them together with the project kits via aether install github memory claude (see @aetheros/workflow). The templates are generic. Put product rules in the project's own CLAUDE.md after installing; nothing in this package is specific to one product.
Examples
examples/aevron-tools.mjs (tool with host approval), examples/streaming-structured.mjs, examples/claude-code.mjs. They call the real API or CLI and may cost money; the tests use mocks only.
Limitations
- Tool inputs are validated by the API against each tool's schema; this package doesn't validate them again on the client.
strict: trueper tool gives schema-exact inputs. Fine-grained tool-input streaming (eager_input_streaming) isn't enabled. - Tool calls in one turn run one after another, not in parallel.
- No Batches, Files API, server tools (web search, code execution) or MCP connector yet.
- Refusal fallback uses a beta header and is Claude API–only; it is off automatically with
platform: 'bedrock'or'vertex'; disable it yourself when injecting another cloud'sclient. - Company clouds: Bedrock is the
bedrock-mantleMessages API only (not the older InvokeModel/Converse API); Vertex AI needs a short-lived access token from the environment or a helper (no built-in Google login). Neither was tested against the real clouds here, only against stand-in servers. - The Claude Code executor returns final text only (no streaming), needs the CLI installed and signed in, and can't use Aevron tools.
- The hooks use simple pattern checks. They are guard rails, not a security boundary.
