mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* fix: think --model fails loud on unresolvable model; never persist empty synthesis (#1698) Slash-form model ids (anthropic/claude-sonnet-4-6) silently degraded to the no-LLM stub and wrote empty synthesis pages with exit 0 (reporter saw 200). Three compounding defects, fixed: - normalizeModelId (src/core/model-id.ts): one shared provider:model normalizer replacing 4 colon-only inlines; slash→colon, bare→default, malformed leading-separator (:foo) returned unchanged so resolveRecipe throws loud. - validateModelId + probeChatModel (gateway.ts): shared id-validity + key probe; runThink hard-errors on an explicit --model it can't run (no silent degrade). - synthesisOk + persist-skip (think/index.ts): empty/malformed/empty-JSON synthesis is never persisted; --save with no synthesis exits 1. - auto-think (cycle/auto-think.ts): empty synthesis no longer counts complete or advances the cooldown (the autonomous third caller of persistSynthesis). - hasAnthropicKey consolidated into src/core/ai/anthropic-key.ts (3 copies → 1). MCP think op sets modelExplicit; saved_slug '' maps to null. * chore: bump version and changelog (v0.42.4.0) #1698 think --model fail-loud wave. Also files the P3 follow-up TODO for the provider-symmetric early gate (D1 accept-as-is). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
/**
|
|
* #1698 — shared `hasAnthropicKey` (consolidated from 3 private copies).
|
|
*
|
|
* Hermetic: every case isolates env + GBRAIN_HOME via `withEnv` (R1) so the
|
|
* dev machine's real ~/.gbrain/config.json never leaks into the "neither" case.
|
|
*/
|
|
|
|
import { describe, test, expect, afterEach } from 'bun:test';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { withEnv } from '../helpers/with-env.ts';
|
|
import { hasAnthropicKey } from '../../src/core/ai/anthropic-key.ts';
|
|
|
|
const tmpDirs: string[] = [];
|
|
function freshHome(withConfig?: Record<string, unknown>): string {
|
|
const home = mkdtempSync(join(tmpdir(), 'gbrain-akey-'));
|
|
tmpDirs.push(home);
|
|
if (withConfig) {
|
|
const dir = join(home, '.gbrain');
|
|
mkdirSync(dir, { recursive: true });
|
|
writeFileSync(join(dir, 'config.json'), JSON.stringify(withConfig), 'utf8');
|
|
}
|
|
return home;
|
|
}
|
|
|
|
afterEach(() => {
|
|
while (tmpDirs.length) {
|
|
const d = tmpDirs.pop()!;
|
|
try { rmSync(d, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
}
|
|
});
|
|
|
|
describe('hasAnthropicKey', () => {
|
|
test('env ANTHROPIC_API_KEY set → true (no config read needed)', async () => {
|
|
const home = freshHome(); // empty home so config can't accidentally satisfy it
|
|
await withEnv(
|
|
{ ANTHROPIC_API_KEY: 'sk-test', GBRAIN_HOME: home, DATABASE_URL: undefined, GBRAIN_DATABASE_URL: undefined },
|
|
async () => {
|
|
expect(hasAnthropicKey()).toBe(true);
|
|
},
|
|
);
|
|
});
|
|
|
|
test('gbrain config anthropic_api_key set (no env) → true', async () => {
|
|
const home = freshHome({ anthropic_api_key: 'sk-from-config' });
|
|
await withEnv(
|
|
{ ANTHROPIC_API_KEY: undefined, GBRAIN_HOME: home, DATABASE_URL: undefined, GBRAIN_DATABASE_URL: undefined },
|
|
async () => {
|
|
expect(hasAnthropicKey()).toBe(true);
|
|
},
|
|
);
|
|
});
|
|
|
|
test('neither env nor config → false', async () => {
|
|
const home = freshHome(); // no config.json written
|
|
await withEnv(
|
|
{ ANTHROPIC_API_KEY: undefined, GBRAIN_HOME: home, DATABASE_URL: undefined, GBRAIN_DATABASE_URL: undefined },
|
|
async () => {
|
|
expect(hasAnthropicKey()).toBe(false);
|
|
},
|
|
);
|
|
});
|
|
});
|