`claude login` alone does not authorize SDK / non-interactive (`claude -p`) usage. Anthropic gates that behind a separate long-lived token minted by `claude setup-token`. Without it, subscription users hit `billing_error` from the SDK even though interactive Claude Code works fine. Changes: - ClaudeCodeConfig.oauthToken: new optional field. When set, the provider exports it as CLAUDE_CODE_OAUTH_TOKEN before invoking the SDK, which makes the SDK bill against the user's Pro/Max subscription instead of API credits. - apiKey continues to work for users with a console API key. If both are present, oauthToken takes precedence. - billing_error / authentication_failed messages now point users at `claude setup-token` so the fix is obvious from the error alone. - README: rewrite the Claude Code section to document both modes and the `claude setup-token` step explicitly. - examples/claude-code.ts: read CLAUDE_CODE_OAUTH_TOKEN from env so the smoke test actually works for subscribers.
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
/**
|
|
* Quick smoke test for ClaudeCodeProvider.
|
|
*
|
|
* Prereqs (pick one):
|
|
* - Claude Pro/Max subscription: run `claude setup-token`, then set
|
|
* CLAUDE_CODE_OAUTH_TOKEN=<the token it prints> in your environment
|
|
* (or pass it as `oauthToken` below).
|
|
* - Console API key: set ANTHROPIC_API_KEY (or pass `apiKey`).
|
|
*
|
|
* `claude login` alone is NOT sufficient for SDK use — Anthropic gates
|
|
* non-interactive (`claude -p`) invocations behind a separate token.
|
|
*
|
|
* Run: bun run examples/claude-code.ts
|
|
*/
|
|
|
|
import { ClaudeCodeProvider } from '../src/index.js';
|
|
|
|
async function main() {
|
|
const claude = new ClaudeCodeProvider({
|
|
defaultModel: 'sonnet',
|
|
oauthToken: process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
});
|
|
|
|
console.log('Initializing…');
|
|
await claude.initialize();
|
|
console.log('Ready.\n');
|
|
|
|
// ── Non-streaming completion ───────────────────────────────────────────
|
|
console.log('--- complete() ---');
|
|
const response = await claude.complete({
|
|
messages: [
|
|
{ role: 'system', content: 'You are concise. Reply in one sentence.' },
|
|
{ role: 'user', content: 'What is the capital of Japan?' }
|
|
],
|
|
maxTokens: 100
|
|
});
|
|
|
|
console.log(response.content);
|
|
console.log(`tokens: ${response.usage.totalTokens} | cost: $${response.metadata?.costUsd ?? '?'}\n`);
|
|
|
|
// ── Streaming ──────────────────────────────────────────────────────────
|
|
console.log('--- stream() ---');
|
|
let totalTokens = 0;
|
|
for await (const chunk of claude.stream({
|
|
messages: [{ role: 'user', content: 'Count from 1 to 5, one per line.' }],
|
|
maxTokens: 100
|
|
})) {
|
|
if (!chunk.isComplete) {
|
|
process.stdout.write(chunk.content);
|
|
} else {
|
|
totalTokens = chunk.usage?.totalTokens ?? 0;
|
|
}
|
|
}
|
|
console.log(`\ntokens: ${totalTokens}`);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Failed:', err.message);
|
|
if (err.type) console.error('Type:', err.type);
|
|
process.exit(1);
|
|
});
|