Try LLM providers in OB1 priority order: OpenRouter → OpenAI → Anthropic. * Fails fast on non-transient errors (4xx) so a config mistake does not burn * through all three providers (Wave 2.5 HIGH-1).
(text: string, budget: BudgetTracker)
| 525 | * through all three providers (Wave 2.5 HIGH-1). |
| 526 | */ |
| 527 | async function callLLM(text: string, budget: BudgetTracker): Promise<ExtractedThought[]> { |
| 528 | budget.check(); |
| 529 | budget.callsMade++; |
| 530 | |
| 531 | const errors: string[] = []; |
| 532 | if (OPENROUTER_API_KEY) { |
| 533 | try { return await callOpenRouter(text); } catch (err) { |
| 534 | const msg = (err as Error).message; |
| 535 | errors.push(`openrouter: ${msg}`); |
| 536 | if (!isTransientError(err)) { |
| 537 | throw new Error(`OpenRouter non-transient failure (no fallback): ${msg}`); |
| 538 | } |
| 539 | console.warn("OpenRouter extraction transient error, trying next provider:", msg); |
| 540 | } |
| 541 | } |
| 542 | if (OPENAI_API_KEY) { |
| 543 | try { return await callOpenAI(text); } catch (err) { |
| 544 | const msg = (err as Error).message; |
| 545 | errors.push(`openai: ${msg}`); |
| 546 | if (!isTransientError(err)) { |
| 547 | throw new Error(`OpenAI non-transient failure (no fallback): ${msg}`); |
| 548 | } |
| 549 | console.warn("OpenAI extraction transient error, trying next provider:", msg); |
| 550 | } |
| 551 | } |
| 552 | if (ANTHROPIC_API_KEY) { |
| 553 | try { return await callAnthropic(text); } catch (err) { |
| 554 | errors.push(`anthropic: ${(err as Error).message}`); |
| 555 | throw new Error(`All LLM providers failed: ${errors.join("; ")}`); |
| 556 | } |
| 557 | } |
| 558 | if (errors.length > 0) { |
| 559 | throw new Error(`All configured LLM providers failed transiently: ${errors.join("; ")}`); |
| 560 | } |
| 561 | throw new Error("No LLM API key configured (OPENROUTER_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY)"); |
| 562 | } |
| 563 | |
| 564 | async function extractThoughts(text: string, budget: BudgetTracker): Promise<ExtractedThought[]> { |
| 565 | const words = countWords(text); |
no test coverage detected
searching dependent graphs…