The client is a parameter rather than a module import, so every test passes a stub. A test that reaches the real API is a defect in the test: this runs on a route a stranger with a link can trigger, and each call costs money. getAnthropicClient returns null rather than throwing when there is no key. An unconfigured environment is a working one, and the worker treats null exactly as it treats a failed call — one path rather than two. parsed_output is guarded, not asserted. The SDK returns null there when the answer did not satisfy the schema, which is what a model replying in prose looks like; failing cleanly leaves the submission queued for a retry, where asserting would crash the worker mid-loop. Absent usage figures are treated as zero for the same reason: undercounting a cost is survivable, throwing away a draft that actually succeeded is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
31 lines
962 B
TypeScript
31 lines
962 B
TypeScript
import Anthropic from '@anthropic-ai/sdk';
|
|
|
|
/**
|
|
* The client, or null when there is no key.
|
|
*
|
|
* Null rather than a throw, because an unconfigured environment is a working
|
|
* one: submissions still arrive and wait undrafted. The worker treats null
|
|
* exactly as it treats a failed call, which keeps one path rather than two.
|
|
*
|
|
* Constructed once and cached. The SDK holds a connection pool, and building
|
|
* one per submission would be wasteful on a route a stranger can trigger.
|
|
*/
|
|
let cached: Anthropic | null = null;
|
|
let resolved = false;
|
|
|
|
export function getAnthropicClient(): Anthropic | null {
|
|
if (resolved) return cached;
|
|
|
|
const key = process.env.ANTHROPIC_API_KEY;
|
|
cached = key !== undefined && key.trim() !== '' ? new Anthropic({ apiKey: key }) : null;
|
|
resolved = true;
|
|
|
|
return cached;
|
|
}
|
|
|
|
/** Exposed for tests, which need a fresh decision per case. */
|
|
export function resetAnthropicClient(): void {
|
|
cached = null;
|
|
resolved = false;
|
|
}
|