diff --git a/.gitignore b/.gitignore index 0e964bb..c5815b0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ deploy/terraform.tfstate* .claude/settings.local.json .mcp.json skills-lock.json + +.private/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 84d60e2..4f26b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). +## [1.2.2] - 2026-04-08 + +### Added +- Vertex AI service account JSON key authentication in `--setup` flow +- Google Vertex AI and Anthropic via Vertex AI as first-class setup options +- Auto-detection of `GOOGLE_APPLICATION_CREDENTIALS` environment variable during setup +- Keychain storage for Vertex credentials (project, location, key file path) + +### Fixed +- `/analyze` endpoint now logs errors to stderr before responding (#37) + +### Changed +- Fixed Vertex provider IDs (`google-vertex`, `anthropic-vertex`) to match registry keys +- Test fixtures replaced with smaller, cheaper PDFs (1-pager.pdf, attention-is-all-you-need.pdf) +- Test URL documents updated from IC datasheets to CS/ML papers, sorted by page count + ## [1.2.1] - 2026-04-06 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index b62fab3..b3f8c3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,6 +81,10 @@ Before committing: npm run type-check && npm run lint && npm test ``` +## Testing with PDFs + +Always use `test/fixtures/1-pager.pdf` for MCP tool testing. It is small and cheap on LLM API calls. Never use `test/fixtures/oversized-doc.pdf` or other large PDFs unless the user gives explicit approval. + ## Release Process Branch protection requires releases to go through a PR: diff --git a/package.json b/package.json index 8313a62..0c644ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@intelligentelectron/pdf-analyzer", - "version": "1.2.1", + "version": "1.2.2", "description": "MCP server for analyzing PDF documents using AI (Google Gemini, Anthropic Claude, OpenAI)", "type": "module", "main": "dist/index.js", diff --git a/src/cli/commands.ts b/src/cli/commands.ts index ee2b61f..3d5e188 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -17,8 +17,13 @@ import { getModel, setModel, deleteAllCredentials, + deleteStoredValue, + deleteVertexCredentials, + setVertexProject, + setVertexLocation, + setVertexKeyFile, } from "../keychain.js"; -import { providerList } from "../providers/registry.js"; +import { getSetupProviderList } from "../providers/registry.js"; /** * Print version information. @@ -36,14 +41,14 @@ export const printHelp = (): void => { ${BINARY_NAME} v${VERSION} MCP server for analyzing PDF documents using AI. -Supports Google Gemini, Anthropic Claude, and OpenAI. +Supports Google Gemini, Google Vertex AI, Anthropic Claude, and OpenAI. USAGE: ${BINARY_NAME} [OPTIONS] OPTIONS: --version, -v Print version and exit - --setup Choose an LLM provider and store your API key + --setup Choose an LLM provider and store credentials --set-key Alias for --setup (deprecated) --update Check for updates and apply if available --uninstall Remove ${BINARY_NAME} from the system @@ -52,9 +57,9 @@ OPTIONS: PROVIDER SETUP: ${BINARY_NAME} --setup - Lets you choose a provider (Google Gemini, Anthropic Claude, or OpenAI) - and stores your API key in the OS credential store (macOS Keychain, - Windows Credential Manager, or Linux secret-tool). + Lets you choose a provider and stores credentials in the OS credential + store (macOS Keychain, Windows Credential Manager, or Linux secret-tool). + Vertex AI providers authenticate with a service account JSON key file. INSTALLATION: curl -fsSL https://raw.githubusercontent.com/${GITHUB_REPO}/main/install.sh | bash @@ -85,17 +90,66 @@ function assertNotCancelled(value: T | symbol): asserts value is T { } /** - * Handle the --setup flag: choose provider and store API key. + * Resolve a file path, expanding ~ to home directory and resolving to absolute. + */ +function resolveKeyFilePath(p: string): string { + if (p.startsWith("~")) { + p = path.join(os.homedir(), p.slice(1)); + } + return path.resolve(p); +} + +/** + * Check if a provider is a Vertex AI provider (uses service account auth, not API key). + */ +function isVertexProvider(id: string): boolean { + return id.includes("-vertex"); +} + +/** + * Validate a service account JSON key file at the given path. + * Returns an error message string if invalid, or undefined if valid. + */ +function validateKeyFile(value: string | undefined): string | undefined { + if (!value) return "Key file path is required"; + const resolved = resolveKeyFilePath(value); + if (!fs.existsSync(resolved)) return `File not found: ${resolved}`; + try { + const content = JSON.parse(fs.readFileSync(resolved, "utf-8")); + if (content.type !== "service_account") { + return "Not a service account key (expected type: service_account)"; + } + if (!content.project_id) return "Key file is missing project_id"; + } catch { + return "File is not valid JSON"; + } +} + +/** + * Prompt user for a service account JSON key file path. + */ +function promptForKeyFile() { + return clack.text({ + message: "Path to service account JSON key file", + placeholder: "/path/to/service-account-key.json", + validate: validateKeyFile, + }); +} + +/** + * Handle the --setup flag: choose provider and store credentials. */ export const handleSetupCommand = async (): Promise => { clack.intro("pdf-analyzer setup"); + const allProviders = await getSetupProviderList(); + const existingProvider = getActiveProvider(); const existingKey = getApiKey(); const existingModel = getModel(); - if (existingProvider && existingKey) { - const providerConfig = providerList.find((p) => p.id === existingProvider); + if (existingProvider && (existingKey || isVertexProvider(existingProvider))) { + const providerConfig = allProviders.find((p) => p.id === existingProvider); const providerName = providerConfig?.displayName ?? existingProvider; const modelName = providerConfig?.models.find((m) => m.id === existingModel)?.displayName ?? existingModel; @@ -111,14 +165,14 @@ export const handleSetupCommand = async (): Promise => { const providerId = await clack.select({ message: "Choose your LLM provider", - options: providerList.map((p) => ({ + options: allProviders.map((p) => ({ value: p.id, label: p.displayName, })), }); assertNotCancelled(providerId); - const selected = providerList.find((p) => p.id === providerId)!; + const selected = allProviders.find((p) => p.id === providerId)!; const modelId = await clack.select({ message: "Choose a model", @@ -131,23 +185,78 @@ export const handleSetupCommand = async (): Promise => { const selectedModel = selected.models.find((m) => m.id === modelId)!; - clack.note( - `Model: ${selectedModel.displayName} (${modelId})\nGet your API key from: ${selected.apiKeyUrl}`, - selected.displayName - ); + try { + if (isVertexProvider(selected.id)) { + // Vertex AI: detect GOOGLE_APPLICATION_CREDENTIALS or prompt for key file + let resolvedPath: string; + + const envKeyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS; + if (envKeyFile && !validateKeyFile(envKeyFile)) { + const resolved = resolveKeyFilePath(envKeyFile); + const useEnvFile = await clack.confirm({ + message: `Detected GOOGLE_APPLICATION_CREDENTIALS: ${resolved}\n Use this service account key?`, + }); + assertNotCancelled(useEnvFile); + + if (useEnvFile) { + resolvedPath = resolved; + } else { + const keyFilePath = await promptForKeyFile(); + assertNotCancelled(keyFilePath); + resolvedPath = resolveKeyFilePath(keyFilePath); + } + } else { + clack.note( + "Set GOOGLE_APPLICATION_CREDENTIALS in your shell to auto-detect next time.\nYou can also drag and drop the file into the terminal.", + "Service Account Key" + ); + const keyFilePath = await promptForKeyFile(); + assertNotCancelled(keyFilePath); + resolvedPath = resolveKeyFilePath(keyFilePath); + } - const key = await clack.password({ - message: "Enter your API key", - validate: (value) => { - if (!value) return "API key is required"; - }, - }); - assertNotCancelled(key); + const keyContent = JSON.parse(fs.readFileSync(resolvedPath, "utf-8")); + + const project = await clack.text({ + message: "Google Cloud project ID", + defaultValue: keyContent.project_id, + placeholder: keyContent.project_id, + }); + assertNotCancelled(project); + + const location = await clack.text({ + message: "Vertex AI location", + defaultValue: "us-central1", + placeholder: "us-central1", + }); + assertNotCancelled(location); + + setActiveProvider(selected.id); + setModel(modelId); + setVertexKeyFile(resolvedPath); + setVertexProject(project); + setVertexLocation(location); + deleteStoredValue("API_KEY"); + } else { + clack.note( + `Model: ${selectedModel.displayName} (${modelId})\nGet your API key from: ${selected.apiKeyUrl}`, + selected.displayName + ); + + const key = await clack.password({ + message: "Enter your API key", + validate: (value) => { + if (!value) return "API key is required"; + }, + }); + assertNotCancelled(key); + + setActiveProvider(selected.id); + setModel(modelId); + setApiKey(key); + deleteVertexCredentials(); + } - try { - setActiveProvider(selected.id); - setModel(modelId); - setApiKey(key); clack.outro(`${selected.displayName} (${selectedModel.displayName}) configured successfully.`); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; diff --git a/src/keychain.ts b/src/keychain.ts index dd9021b..adb2e65 100644 --- a/src/keychain.ts +++ b/src/keychain.ts @@ -219,6 +219,9 @@ export function deleteStoredValue(account: string, service: string = DEFAULT_SER const PROVIDER_ACCOUNT = "PROVIDER"; const API_KEY_ACCOUNT = "API_KEY"; const MODEL_ACCOUNT = "MODEL"; +const VERTEX_PROJECT_ACCOUNT = "VERTEX_PROJECT"; +const VERTEX_LOCATION_ACCOUNT = "VERTEX_LOCATION"; +const VERTEX_KEY_FILE_ACCOUNT = "VERTEX_KEY_FILE"; /** Get the active provider ID. Returns null if not set. */ export function getActiveProvider(): string | null { @@ -250,9 +253,49 @@ export function setModel(modelId: string): void { setStoredValue(MODEL_ACCOUNT, modelId); } -/** Delete all stored credentials (provider + API key + model). Best-effort. */ +/** Get the stored Vertex AI project ID. Returns null if not set. */ +export function getVertexProject(): string | null { + return getStoredValue(VERTEX_PROJECT_ACCOUNT); +} + +/** Store the Vertex AI project ID. */ +export function setVertexProject(value: string): void { + setStoredValue(VERTEX_PROJECT_ACCOUNT, value); +} + +/** Get the stored Vertex AI location. Returns null if not set. */ +export function getVertexLocation(): string | null { + return getStoredValue(VERTEX_LOCATION_ACCOUNT); +} + +/** Store the Vertex AI location. */ +export function setVertexLocation(value: string): void { + setStoredValue(VERTEX_LOCATION_ACCOUNT, value); +} + +/** Get the stored Vertex AI service account key file path. Returns null if not set. */ +export function getVertexKeyFile(): string | null { + return getStoredValue(VERTEX_KEY_FILE_ACCOUNT); +} + +/** Store the Vertex AI service account key file path. */ +export function setVertexKeyFile(value: string): void { + setStoredValue(VERTEX_KEY_FILE_ACCOUNT, value); +} + +/** Delete Vertex-specific credentials (project, location, key file). Best-effort. */ +export function deleteVertexCredentials(): void { + deleteStoredValue(VERTEX_PROJECT_ACCOUNT); + deleteStoredValue(VERTEX_LOCATION_ACCOUNT); + deleteStoredValue(VERTEX_KEY_FILE_ACCOUNT); +} + +/** Delete all stored credentials (provider + API key + model + Vertex). Best-effort. */ export function deleteAllCredentials(): void { deleteStoredValue(PROVIDER_ACCOUNT); deleteStoredValue(API_KEY_ACCOUNT); deleteStoredValue(MODEL_ACCOUNT); + deleteStoredValue(VERTEX_PROJECT_ACCOUNT); + deleteStoredValue(VERTEX_LOCATION_ACCOUNT); + deleteStoredValue(VERTEX_KEY_FILE_ACCOUNT); } diff --git a/src/providers/anthropic-vertex.test.ts b/src/providers/anthropic-vertex.test.ts index d8327f8..2c1d731 100644 --- a/src/providers/anthropic-vertex.test.ts +++ b/src/providers/anthropic-vertex.test.ts @@ -3,8 +3,8 @@ import { anthropicVertexProvider } from "./anthropic-vertex.js"; import { anthropicProvider } from "./anthropic.js"; describe("anthropicVertexProvider", () => { - it('has id "anthropic" for provider-specific behavior', () => { - expect(anthropicVertexProvider.id).toBe("anthropic"); + it('has id "anthropic-vertex" matching registry key', () => { + expect(anthropicVertexProvider.id).toBe("anthropic-vertex"); }); it("has same models as direct anthropic provider", () => { diff --git a/src/providers/anthropic-vertex.ts b/src/providers/anthropic-vertex.ts index 1a290ca..c343a33 100644 --- a/src/providers/anthropic-vertex.ts +++ b/src/providers/anthropic-vertex.ts @@ -66,15 +66,16 @@ function isTokenLimitError(error: unknown): boolean { } export const anthropicVertexProvider: ProviderConfig = { - id: "anthropic", + id: "anthropic-vertex", displayName: "Anthropic via Vertex AI", models: MODELS, defaultModel: DEFAULT_MODEL, apiKeyUrl: "", - createModel: (_apiKey: string, modelId: string) => { + createModel: (apiKey: string, modelId: string) => { const client = createVertexAnthropic({ project: getProject(), location: getLocation(), + ...(apiKey ? { googleAuthOptions: { keyFile: apiKey } } : {}), }); return client(modelId); }, diff --git a/src/providers/google-vertex.test.ts b/src/providers/google-vertex.test.ts index b9aff1c..9e95573 100644 --- a/src/providers/google-vertex.test.ts +++ b/src/providers/google-vertex.test.ts @@ -3,8 +3,8 @@ import { vertexProvider } from "./google-vertex.js"; import { isGoogleTokenLimitError } from "./google-shared.js"; describe("vertexProvider", () => { - it('has id "google" for cached URI routing', () => { - expect(vertexProvider.id).toBe("google"); + it('has id "google-vertex" matching registry key', () => { + expect(vertexProvider.id).toBe("google-vertex"); }); it("has 2 models", () => { diff --git a/src/providers/google-vertex.ts b/src/providers/google-vertex.ts index a2a909e..c174ba3 100644 --- a/src/providers/google-vertex.ts +++ b/src/providers/google-vertex.ts @@ -50,15 +50,16 @@ async function preparePdf(source: PdfSource): Promise { } export const vertexProvider: ProviderConfig = { - id: "google", + id: "google-vertex", displayName: "Google Vertex AI", models: GOOGLE_MODELS, defaultModel: GOOGLE_DEFAULT_MODEL, apiKeyUrl: "", - createModel: (_apiKey: string, modelId: string) => { + createModel: (apiKey: string, modelId: string) => { const vertex = createVertex({ project: getProject(), location: getLocation(), + ...(apiKey ? { googleAuthOptions: { keyFile: apiKey } } : {}), }); return vertex(modelId); }, diff --git a/src/providers/registry.test.ts b/src/providers/registry.test.ts index 474ba20..bfba7ad 100644 --- a/src/providers/registry.test.ts +++ b/src/providers/registry.test.ts @@ -5,14 +5,27 @@ vi.mock("../keychain.js", () => ({ getActiveProvider: vi.fn(() => null), getApiKey: vi.fn(() => null), getModel: vi.fn(() => null), + getVertexProject: vi.fn(() => null), + getVertexLocation: vi.fn(() => null), + getVertexKeyFile: vi.fn(() => null), })); -import { resolveActiveProvider, providers } from "./registry.js"; -import { getActiveProvider, getApiKey, getModel } from "../keychain.js"; +import { resolveActiveProvider, providers, getSetupProviderList } from "./registry.js"; +import { + getActiveProvider, + getApiKey, + getModel, + getVertexProject, + getVertexLocation, + getVertexKeyFile, +} from "../keychain.js"; const mockedGetActiveProvider = vi.mocked(getActiveProvider); const mockedGetApiKey = vi.mocked(getApiKey); const mockedGetModel = vi.mocked(getModel); +const mockedGetVertexProject = vi.mocked(getVertexProject); +const mockedGetVertexLocation = vi.mocked(getVertexLocation); +const mockedGetVertexKeyFile = vi.mocked(getVertexKeyFile); describe("resolveActiveProvider", () => { beforeEach(() => { @@ -20,6 +33,9 @@ describe("resolveActiveProvider", () => { mockedGetActiveProvider.mockReturnValue(null); mockedGetApiKey.mockReturnValue(null); mockedGetModel.mockReturnValue(null); + mockedGetVertexProject.mockReturnValue(null); + mockedGetVertexLocation.mockReturnValue(null); + mockedGetVertexKeyFile.mockReturnValue(null); }); // --- Env var path --- @@ -77,4 +93,59 @@ describe("resolveActiveProvider", () => { await expect(resolveActiveProvider()).rejects.toThrow(/No provider configured/); await expect(resolveActiveProvider()).rejects.toThrow(/--setup/); }); + + // --- Vertex keychain path --- + + it("resolves google-vertex from keychain with key file as apiKey", async () => { + mockedGetActiveProvider.mockReturnValue("google-vertex"); + mockedGetVertexProject.mockReturnValue("my-project"); + mockedGetVertexLocation.mockReturnValue("europe-west1"); + mockedGetVertexKeyFile.mockReturnValue("/path/to/key.json"); + + const result = await resolveActiveProvider(); + expect(result.provider.id).toBe("google-vertex"); + expect(result.apiKey).toBe("/path/to/key.json"); + expect(result.modelId).toBe(result.provider.defaultModel); + }); + + it("resolves anthropic-vertex from keychain", async () => { + mockedGetActiveProvider.mockReturnValue("anthropic-vertex"); + mockedGetVertexProject.mockReturnValue("my-project"); + mockedGetVertexLocation.mockReturnValue("us-central1"); + mockedGetVertexKeyFile.mockReturnValue("/path/to/key.json"); + + const result = await resolveActiveProvider(); + expect(result.provider.id).toBe("anthropic-vertex"); + expect(result.apiKey).toBe("/path/to/key.json"); + }); + + it("returns empty apiKey for Vertex when no key file stored (ADC)", async () => { + mockedGetActiveProvider.mockReturnValue("google-vertex"); + mockedGetVertexProject.mockReturnValue("my-project"); + + const result = await resolveActiveProvider(); + expect(result.apiKey).toBe(""); + }); + + it("does not overwrite existing VERTEX_PROJECT env var", async () => { + vi.stubEnv("VERTEX_PROJECT", "env-project"); + mockedGetActiveProvider.mockReturnValue("google-vertex"); + mockedGetVertexProject.mockReturnValue("keychain-project"); + + await resolveActiveProvider(); + expect(process.env.VERTEX_PROJECT).toBe("env-project"); + }); +}); + +describe("getSetupProviderList", () => { + it("returns 5 providers including Vertex", async () => { + const list = await getSetupProviderList(); + expect(list).toHaveLength(5); + const ids = list.map((p) => p.id); + expect(ids).toContain("google"); + expect(ids).toContain("google-vertex"); + expect(ids).toContain("anthropic"); + expect(ids).toContain("anthropic-vertex"); + expect(ids).toContain("openai"); + }); }); diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 0a05be5..ace3990 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -14,7 +14,14 @@ import { openaiProvider } from "./openai.js"; // Vertex providers are loaded lazily to avoid pulling in @ai-sdk/google-vertex in stdio mode. // import { vertexProvider } from "./google-vertex.js"; // import { anthropicVertexProvider } from "./anthropic-vertex.js"; -import { getActiveProvider as getStoredProvider, getApiKey, getModel } from "../keychain.js"; +import { + getActiveProvider as getStoredProvider, + getApiKey, + getModel, + getVertexProject, + getVertexLocation, + getVertexKeyFile, +} from "../keychain.js"; /** Eagerly loaded providers (no cloud-only deps). */ const eagerProviders: Record = { @@ -45,9 +52,24 @@ async function loadVertexProviders(): Promise> { /** All supported providers, keyed by ID. Vertex providers are added lazily. */ export const providers: Record = { ...eagerProviders }; -/** Ordered list for display in the setup menu. */ +/** Ordered list for display in the setup menu (API-key providers only). */ export const providerList: ProviderConfig[] = [googleProvider, anthropicProvider, openaiProvider]; +/** + * Full provider list including Vertex AI providers, for the --setup menu. + * Vertex providers are lazy-loaded here (acceptable in CLI path). + */ +export async function getSetupProviderList(): Promise { + const vp = await loadVertexProviders(); + return [ + googleProvider, + vp["google-vertex"], + anthropicProvider, + vp["anthropic-vertex"], + openaiProvider, + ]; +} + /** * Look up a provider by ID, loading vertex providers lazily if needed. */ @@ -107,7 +129,6 @@ export async function resolveActiveProvider(): Promise<{ // Fall back to keychain const providerId = getStoredProvider(); - const apiKey = getApiKey(); if (!providerId) { throw new Error( @@ -115,7 +136,7 @@ export async function resolveActiveProvider(): Promise<{ ); } - const provider = providers[providerId]; + const provider = await getProvider(providerId); if (!provider) { throw new Error( @@ -123,17 +144,36 @@ export async function resolveActiveProvider(): Promise<{ ); } - if (!apiKey) { - throw new Error( - `API key not found for ${provider.displayName}. Run \`pdf-analyzer --setup\` to set your API key.` - ); - } - const storedModel = getModel(); const modelId = storedModel && provider.models.some((m) => m.id === storedModel) ? storedModel : provider.defaultModel; + // Vertex providers: inject keychain values into env vars, return key file path as apiKey + if (providerId.includes("-vertex")) { + const keychainProject = getVertexProject(); + const keychainLocation = getVertexLocation(); + const keyFile = getVertexKeyFile(); + + if (!process.env.VERTEX_PROJECT && keychainProject) { + process.env.VERTEX_PROJECT = keychainProject; + } + if (!process.env.VERTEX_LOCATION && keychainLocation) { + process.env.VERTEX_LOCATION = keychainLocation; + } + + return { provider, apiKey: keyFile ?? "", modelId }; + } + + // Non-Vertex providers: require API key + const apiKey = getApiKey(); + + if (!apiKey) { + throw new Error( + `API key not found for ${provider.displayName}. Run \`pdf-analyzer --setup\` to set your API key.` + ); + } + return { provider, apiKey, modelId }; } diff --git a/src/server.ts b/src/server.ts index 15b1ed9..78ff429 100644 --- a/src/server.ts +++ b/src/server.ts @@ -156,6 +156,7 @@ export const createServer = (mode: "stdio" | "http" = "stdio"): McpServer => { const result = await analyzePdf(provider, apiKey, modelId, { pdf_source, queries }); return formatResult(result); } catch (error) { + console.error("[analyze_pdf] Tool error:", error); const message = error instanceof Error ? error.message : "Unknown error occurred"; if (message.includes("No provider configured") || message.includes("API key not found")) { diff --git a/src/service.test.ts b/src/service.test.ts index ccf4b17..48885b7 100644 --- a/src/service.test.ts +++ b/src/service.test.ts @@ -82,7 +82,7 @@ describe("validateLocalPath", () => { }); it("accepts valid PDF files", () => { - expect(() => validateLocalPath(process.cwd() + "/test/fixtures/m3000a.pdf")).not.toThrow(); + expect(() => validateLocalPath(process.cwd() + "/test/fixtures/1-pager.pdf")).not.toThrow(); }); }); diff --git a/src/service.ts b/src/service.ts index d5fbb39..67903f3 100644 --- a/src/service.ts +++ b/src/service.ts @@ -222,6 +222,7 @@ async function processChunkQueue( if (!provider.isTokenLimitError(error)) throw error; // Token limit hit, split this chunk and retry + console.warn(`[chunker] Token limit exceeded (${chunk.pageCount} pages), splitting in half`); const [firstHalf, secondHalf] = await splitPdfInHalf(chunk); queue.unshift(firstHalf, secondHalf); } @@ -345,6 +346,9 @@ export async function analyzePdf( return await analyzePdfDirect(provider, apiKey, modelId, source, queries); } catch (error) { if (!provider.isTokenLimitError(error)) throw error; + console.warn( + "[analyzePdf] Token limit exceeded for full PDF, falling back to chunked processing" + ); } // Token limit exceeded, read bytes, split into chunks, and process via work queue diff --git a/src/transports/http.ts b/src/transports/http.ts index d04206a..5f3e2d5 100644 --- a/src/transports/http.ts +++ b/src/transports/http.ts @@ -46,6 +46,7 @@ async function handleAnalyze(req: IncomingMessage, res: ServerResponse): Promise res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result)); } catch (error) { + console.error("[/analyze] Error processing PDF:", error); const message = error instanceof Error ? error.message : "Unknown error"; res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: message })); diff --git a/test/fixtures/1-pager.pdf b/test/fixtures/1-pager.pdf new file mode 100644 index 0000000..f46dbe5 Binary files /dev/null and b/test/fixtures/1-pager.pdf differ diff --git a/test/fixtures/attention-is-all-you-need.pdf b/test/fixtures/attention-is-all-you-need.pdf new file mode 100644 index 0000000..97d7c51 Binary files /dev/null and b/test/fixtures/attention-is-all-you-need.pdf differ diff --git a/test/fixtures/m3000a.pdf b/test/fixtures/m3000a.pdf deleted file mode 100644 index cb8e66a..0000000 Binary files a/test/fixtures/m3000a.pdf and /dev/null differ diff --git a/test/fixtures/url-documents.md b/test/fixtures/url-documents.md index 3224477..e6fb0df 100644 --- a/test/fixtures/url-documents.md +++ b/test/fixtures/url-documents.md @@ -1,79 +1,76 @@ -# Test URLs - IC Datasheets +# Test URLs - PDF Documents URLs tested successfully with the PDF Analyzer MCP Server. +Sorted by page count (smallest first). -## Microcontrollers +## Small (under 20 pages) -1. STMicroelectronics STM32F103 - +1. Bitcoin Whitepaper (9 pages, 0.17 MB) + -2. Microchip PIC16F877A - +2. ImageNet / AlexNet (9 pages, 1.35 MB) + -3. NXP LPC1768 - +3. MapReduce - Google (13 pages, 0.18 MB) + -4. Renesas RA4M1 - +4. Bigtable - Google (14 pages, 0.21 MB) + -## Analog ICs +5. Attention Is All You Need (15 pages, 2.11 MB) + -5. Texas Instruments LM7805 - +6. Google File System (15 pages, 0.26 MB) + -6. Analog Devices AD8221 - +7. BERT (16 pages, 0.73 MB) + -7. ON Semiconductor LM358 - +8. Dynamo - Amazon (16 pages, 0.85 MB) + -8. Texas Instruments MAX232 - +9. TCP Congestion Control - RFC 5681 (18 pages, 0.03 MB) + -## Power Management +10. Raft Consensus Algorithm (18 pages, 0.54 MB) + -9. Infineon IRS2184 - +## Medium (20-50 pages) -10. Monolithic Power Systems MP1584 - +11. GPT-2 (24 pages, 0.55 MB) + -11. Diodes Inc AP2112 - +12. Borg - Google / Kubernetes (24 pages, 0.36 MB) + -## Memory +13. Diffusion Models / DDPM (25 pages, 9.79 MB) + -12. Micron MT48LC16M16A2 SDRAM - +14. LoRA (26 pages, 1.53 MB) + -13. Winbond W25Q128JV SPI Flash - +15. ReAct - Reasoning + Acting (33 pages, 0.60 MB) + -## RF/Wireless +16. Constitutional AI - Anthropic (34 pages, 1.99 MB) + -14. Nordic Semiconductor nRF52832 - +17. FlashAttention (34 pages, 2.50 MB) + -15. Silicon Labs CP2102 - +18. Let's Encrypt / ACME (45 pages, 0.29 MB) + -## Interface ICs +## Large (50+ pages) -16. FTDI FT232RL - +19. NIST Cybersecurity Framework (55 pages, 1.01 MB) + -17. Broadcom HCPL-0611 - +20. HTTP/2 - RFC 7540 (96 pages, 0.13 MB) + -## Sensors +21. TLS 1.3 - RFC 8446 (160 pages, 0.21 MB) + -18. Bosch Sensortec BMP280 - - -19. TDK/InvenSense MPU-6050 - - -## Logic ICs - -20. Nexperia 74HC595 - +22. HTTP/1.1 - RFC 2616 (176 pages, 0.24 MB) + diff --git a/test/test-report-ic-datasheets.md b/test/test-report-ic-datasheets.md deleted file mode 100644 index c003a39..0000000 --- a/test/test-report-ic-datasheets.md +++ /dev/null @@ -1,476 +0,0 @@ -# PDF Analyzer Test Report - IC Datasheets - -**Test Date:** 2026-01-30 -**Tool Version:** PDF Analyzer MCP Server (gemini-3-pro-preview) -**Test Scope:** 20 IC datasheets from 20 different vendors - ---- - -## Executive Summary - -| Metric | Value | -|--------|-------| -| **Total Tests** | 20 | -| **Successful** | 20 | -| **Failed** | 0 | -| **Success Rate** | **100%** | -| **URLs Requiring Retry** | 2 | - -The PDF Analyzer successfully analyzed all 20 IC datasheets from 20 different vendors, correctly extracting part numbers, electrical specifications, and typical applications from each document. - ---- - -## Test Methodology - -### Query Set (3 queries per datasheet) -1. **Basic Info:** "What is this IC's part number and brief description?" -2. **Specifications:** "What are the key electrical specifications (voltage, current, frequency)?" -3. **Applications:** "What are the typical applications for this IC?" - -### Success Criteria -- URL must resolve to valid PDF content -- All 3 queries must receive meaningful, accurate responses -- Response must correctly identify the IC and extract relevant technical data - ---- - -## Detailed Results - -### 1. STMicroelectronics - STM32F103 (ARM Cortex-M3 MCU) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.st.com/resource/en/datasheet/stm32f103c8.pdf | -| **File URI** | `8b1voal3alh1` | - -**Extracted Data:** -- **Part Number:** STM32F103x8 / STM32F103xB -- **Description:** Medium-density ARM Cortex-M3 32-bit MCU with 64/128KB Flash, USB, CAN, 7 timers -- **Voltage:** 2.0-3.6V (VDD), 1.8-3.6V (VBAT) -- **Current:** 150mA max, ~50mA in Run mode @ 72MHz -- **Frequency:** 72 MHz max -- **Applications:** Motor drives, medical equipment, PLCs, printers, alarm systems, HVACs - ---- - -### 2. Texas Instruments - LM7805 (Linear Voltage Regulator) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.ti.com/lit/ds/symlink/lm340.pdf | -| **File URI** | `2eifry8hxux8` | - -**Extracted Data:** -- **Part Number:** LM340, LM340A, LM7805, LM7812, LM7815 -- **Description:** Wide VIN 1.5A Fixed Voltage Regulators with thermal shutdown -- **Voltage:** 5V, 12V, 15V fixed outputs; 35V max input -- **Current:** Up to 1.5A output -- **Applications:** Industrial power supplies, SMPS post regulation, HVAC, motor drivers - ---- - -### 3. Analog Devices - AD8221 (Instrumentation Amplifier) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.analog.com/media/en/technical-documentation/data-sheets/AD8221.pdf | -| **File URI** | `owggw4wuclpg` | - -**Extracted Data:** -- **Part Number:** AD8221 -- **Description:** Gain programmable precision instrumentation amplifier with highest CMRR in class -- **Voltage:** ±2.3V to ±18V supply -- **Current:** 0.9mA quiescent, 0.4nA input bias -- **Frequency:** 825kHz bandwidth @ G=1, 80dB CMRR to 10kHz -- **Applications:** Weigh scales, bridge amplifiers, medical instrumentation, strain gages - ---- - -### 4. Microchip - PIC16F877A (8-bit MCU) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://ww1.microchip.com/downloads/en/devicedoc/39582b.pdf | -| **File URI** | `tp2shmat7cjm` | - -**Extracted Data:** -- **Part Number:** PIC16F873A, PIC16F874A, PIC16F876A, PIC16F877A -- **Description:** 28/40/44-Pin Enhanced Flash Microcontrollers, 35-instruction RISC CPU -- **Voltage:** 2.0V to 5.5V -- **Current:** 250mA max VDD, 25mA per I/O pin -- **Frequency:** DC to 20MHz -- **Applications:** Serial communication, embedded control, motor control, networking - ---- - -### 5. NXP - LPC1768 (ARM Cortex-M3 MCU) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://datasheet.octopart.com/LPC1768FBD100,551-NXP-datasheet-8326490.pdf | -| **File URI** | `brjibjx01qm7` | - -**Extracted Data:** -- **Part Number:** LPC1768, LPC1766, LPC1765, LPC1764 -- **Description:** 32-bit ARM Cortex-M3 MCU with Ethernet, USB Host/Device/OTG, CAN -- **Voltage:** 2.4V to 3.6V (single 3.3V supply) -- **Current:** 42mA active @ 100MHz, 517nA deep power-down -- **Frequency:** Up to 100MHz -- **Applications:** eMetering, lighting, industrial networking, alarm systems, motor control - ---- - -### 6. Infineon - IRS2184 (Half-Bridge Driver) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS (retry required) | -| **Original URL** | https://www.infineon.com/dgdl/... (404 error) | -| **Working URL** | https://www.farnell.com/datasheets/57932.pdf | -| **File URI** | `iinb0ndhzynh` | - -**Extracted Data:** -- **Part Number:** IRS2184, IRS21844, IRS2184S, IRS21844S -- **Description:** Half-bridge driver for high voltage power MOSFETs and IGBTs -- **Voltage:** 600V high side, 10-20V gate drive supply -- **Current:** 1.9A source, 2.3A sink -- **Frequency:** Up to 1MHz switching -- **Applications:** Half-bridge MOSFET/IGBT drive up to 600V - ---- - -### 7. ON Semiconductor - LM358 (Op-Amp) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.onsemi.com/download/data-sheet/pdf/lm358-d.pdf | -| **File URI** | `6cfqufn329p1` | - -**Extracted Data:** -- **Part Number:** LM258, LM358, LM358A, LM358E, LM2904 -- **Description:** Single supply dual operational amplifier with ground-referenced input -- **Voltage:** 3.0V to 32V single supply, ±1.5V to ±16V split -- **Current:** 0.7mA quiescent @ 5V, 40mA source output -- **Frequency:** ~1MHz unity gain -- **Applications:** Voltage reference, Wien bridge oscillator, comparator, filters - ---- - -### 8. Texas Instruments - MAX232 (RS-232 Driver) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.ti.com/lit/ds/symlink/max232.pdf | -| **File URI** | `0dw5bt95za1n` | - -**Extracted Data:** -- **Part Number:** MAX232 -- **Description:** Dual driver/receiver with capacitive voltage generator for RS-232 -- **Voltage:** Single 5V supply (4.5V to 5.5V), ±30V input tolerant -- **Current:** 8mA typical supply current -- **Frequency:** 120kbit/s data rate -- **Applications:** Terminals, modems, computers, battery-powered systems - ---- - -### 9. Nordic Semiconductor - nRF52832 (Bluetooth SoC) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS (retry required) | -| **Original URL** | https://infocenter.nordicsemi.com/pdf/... (HTML error) | -| **Working URL** | https://www.mouser.com/datasheet/2/297/nRF52832_PS_v1_8-2942485.pdf | -| **File URI** | `7q36tu7g0i00` | - -**Extracted Data:** -- **Part Number:** nRF52832 -- **Description:** Bluetooth LE SoC with ARM Cortex-M4 and FPU for ultra-low power -- **Voltage:** 1.7V to 3.6V -- **Current:** 5.3mA TX @ 0dBm, 5.4mA RX, 0.3µA System OFF -- **Frequency:** 64MHz processor -- **Applications:** IoT, home automation, health/fitness, remote controls, beacons - ---- - -### 10. Silicon Labs - CP2102 (USB-to-UART Bridge) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.silabs.com/documents/public/data-sheets/CP2102-9.pdf | -| **File URI** | `kyhzvdeffk85` | - -**Extracted Data:** -- **Part Number:** CP2102/CP2109 -- **Description:** Single-chip USB-to-UART bridge with integrated USB controller and oscillator -- **Voltage:** 3.0-3.6V self-powered, 4.0-5.25V bus-powered -- **Current:** 20mA normal, 80µA suspended -- **Frequency:** 48MHz internal oscillator, 300bps to 1Mbps UART -- **Applications:** RS-232 legacy upgrade, USB interface cables, serial adapters - ---- - -### 11. Renesas - RA4M1 (ARM Cortex-M4 MCU) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://docs.arduino.cc/resources/datasheets/ra4m1-datasheet.pdf | -| **File URI** | `ezrckrb37gd9` | - -**Extracted Data:** -- **Part Number:** R7FA4M1AB3CFP (RA4M1 Group) -- **Description:** 48MHz ARM Cortex-M4 with FPU, LCD controller, capacitive touch -- **Voltage:** 1.6V to 5.5V -- **Current:** 8.3mA active @ 48MHz, 0.8µA standby -- **Frequency:** 48MHz max -- **Applications:** HMI applications with LCD and touch sensing - ---- - -### 12. Monolithic Power Systems - MP1584 (DC-DC Converter) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://content.instructables.com/FT8/0X4N/J9SVVWSX/FT80X4NJ9SVVWSX.pdf | -| **File URI** | `2hrdtsa4me7f` | - -**Extracted Data:** -- **Part Number:** MP1584 -- **Description:** High frequency step-down switching regulator with integrated high-side MOSFET -- **Voltage:** 4.5V to 28V input -- **Current:** 3A output, 100µA quiescent -- **Frequency:** 100kHz to 1.5MHz programmable switching -- **Applications:** Automotive, industrial power, distributed power, battery systems - ---- - -### 13. Micron - MT48LC16M16A2 (SDRAM) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://datasheet.octopart.com/MT48LC16M16A2TG-7E-IT:D-Micron-datasheet-7627877.pdf | -| **File URI** | `gp2tjyxa0mtf` | - -**Extracted Data:** -- **Part Number:** MT48LC64M4A2, MT48LC32M8A2, MT48LC16M16A2 -- **Description:** 256Mb synchronous DRAM, quad-bank architecture -- **Voltage:** 3.3V ±0.3V -- **Current:** 125-135mA operating, 2.5mA self-refresh -- **Frequency:** 133MHz to 167MHz (PC100/PC133 compliant) -- **Applications:** 3.3V memory systems - ---- - -### 14. Winbond - W25Q128JV (SPI Flash) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf | -| **File URI** | `aamvci5biwz6` | - -**Extracted Data:** -- **Part Number:** W25Q128JV -- **Description:** 128M-bit serial flash memory with Dual/Quad SPI -- **Voltage:** 2.7V to 3.6V -- **Current:** <1µA power-down, 15-20mA active read -- **Frequency:** Up to 133MHz SPI (532MHz equivalent Quad I/O) -- **Applications:** Code shadowing, execute-in-place (XIP), voice/text/data storage - ---- - -### 15. FTDI - FT232RL (USB-to-UART) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://ftdichip.com/wp-content/uploads/2020/08/DS_FT232R.pdf | -| **File URI** | `mjzeosvdnovg` | - -**Extracted Data:** -- **Part Number:** FT232R (FT232RL in SSOP-28, FT232RQ in QFN-32) -- **Description:** Single-chip USB to serial UART interface with integrated EEPROM -- **Voltage:** 3.3V to 5.25V VCC, 1.8V to 5.25V VCCIO -- **Current:** 15mA operating, 70µA USB suspend -- **Frequency:** 12MHz internal oscillator -- **Applications:** USB-RS232/RS422/RS485 converters, MCU/FPGA interfacing, instrumentation - ---- - -### 16. Bosch Sensortec - BMP280 (Pressure Sensor) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf | -| **File URI** | `mmuftoev44v5` | - -**Extracted Data:** -- **Part Number:** BMP280 -- **Description:** Digital barometric pressure sensor using piezo-resistive technology -- **Voltage:** 1.71V to 3.6V (VDD), 1.2V to 3.6V (VDDIO) -- **Current:** 2.7µA @ 1Hz, 0.1µA sleep, 1120µA peak -- **Frequency:** I²C up to 3.4MHz, SPI up to 10MHz -- **Applications:** GPS enhancement, indoor navigation, weather forecast, altitude detection - ---- - -### 17. TDK/InvenSense - MPU-6050 (IMU) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf | -| **File URI** | `70jkmhmwrpic` | - -**Extracted Data:** -- **Part Number:** MPU-6000, MPU-6050 -- **Description:** 6-axis MotionTracking device with 3-axis gyro, 3-axis accelerometer, DMP -- **Voltage:** 2.375V to 3.46V (VDD), 1.71V to VDD (VLOGIC) -- **Current:** 3.9mA full operation, 5µA idle, 10-110µA low-power accel -- **Frequency:** I²C up to 400kHz, SPI up to 20MHz -- **Applications:** Image stabilization, gesture recognition, gaming, wearables, toys - ---- - -### 18. Nexperia - 74HC595 (Shift Register) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://assets.nexperia.com/documents/data-sheet/74HC_HCT595.pdf | -| **File URI** | `v1gdeka02o8j` | - -**Extracted Data:** -- **Part Number:** 74HC595, 74HCT595 -- **Description:** 8-bit serial-in, serial/parallel-out shift register with 3-state outputs -- **Voltage:** 2.0V to 6.0V (HC), 4.5V to 5.5V (HCT) -- **Current:** ±35mA output, 70mA supply max -- **Frequency:** 100MHz typical shift frequency -- **Applications:** Serial-to-parallel conversion, remote control holding register - ---- - -### 19. Diodes Inc - AP2112 (LDO Regulator) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://www.diodes.com/assets/Datasheets/AP2112.pdf | -| **File URI** | `gy6t33ipl2k3` | - -**Extracted Data:** -- **Part Number:** AP2112 -- **Description:** 600mA CMOS LDO regulator with enable and auto-discharge -- **Voltage:** 2.5V to 6.0V input, 1.2V/1.8V/2.5V/2.6V/3.3V fixed output -- **Current:** 600mA output, 55µA quiescent, 0.01µA standby -- **Frequency:** -65dB PSRR @ 1kHz, 50µVrms noise -- **Applications:** Laptop computers, LCD monitors, portable DVD - ---- - -### 20. Broadcom - HCPL-0611 (Optocoupler) - -| Field | Value | -|-------|-------| -| **Status** | ✅ SUCCESS | -| **URL** | https://datasheet.octopart.com/HCPL-0611-Avago-datasheet-7617305.pdf | -| **File URI** | `s27idpbvw1my` | - -**Extracted Data:** -- **Part Number:** HCPL-0611 (and related: 6N137, HCPL-0600, HCPL-2601, etc.) -- **Description:** High CMR, high speed TTL compatible optocoupler with Schmitt trigger output -- **Voltage:** 4.5V to 5.5V supply, 3750V isolation -- **Current:** 5mA input current -- **Frequency:** 10MBd speed, 15kV/µs CMR, 48-50ns propagation delay -- **Applications:** Isolated line receivers, processor interfaces, motor drives, power supplies - ---- - -## Error Analysis - -### Initial URL Failures (2) - -| # | Vendor | Part | Original URL Issue | Resolution | -|---|--------|------|-------------------|------------| -| 6 | Infineon | IRS2184 | 404 Not Found | Used Farnell mirror | -| 9 | Nordic | nRF52832 | Content-Type: text/html | Used Mouser mirror | - -**Root Causes:** -1. **IRS2184:** Infineon's dynamic URL with query parameters was not resolving correctly -2. **nRF52832:** Nordic's infocenter URL returns an HTML wrapper page instead of direct PDF - -**Mitigation:** Distributor mirrors (Farnell, Mouser) provided reliable PDF access for both cases. - ---- - -## Performance Observations - -### Response Quality Assessment - -| Aspect | Rating | Notes | -|--------|--------|-------| -| **Part Number Extraction** | Excellent | Correctly identified all primary and variant part numbers | -| **Specification Accuracy** | Excellent | Extracted voltage, current, frequency with correct units | -| **Application Coverage** | Very Good | Listed applications accurately; some datasheets lack explicit application sections | -| **Multi-Part Datasheets** | Excellent | Correctly handled datasheets covering multiple part variants | - -### Document Characteristics Handled - -- **Page counts:** 16 to 500+ pages -- **File sizes:** ~100KB to multi-MB -- **Document types:** Product specifications, datasheets, data briefs -- **Formats:** Single-part and multi-part family datasheets - ---- - -## Vendor Coverage Summary - -| Category | Vendors Tested | All Successful | -|----------|---------------|----------------| -| **Microcontrollers** | STMicroelectronics, Microchip, NXP, Renesas | ✅ | -| **Analog ICs** | Texas Instruments, Analog Devices, ON Semi | ✅ | -| **Power Management** | Infineon, Monolithic Power, Diodes Inc | ✅ | -| **Memory** | Micron, Winbond | ✅ | -| **RF/Wireless** | Nordic Semiconductor, Silicon Labs | ✅ | -| **Interface ICs** | FTDI, Broadcom | ✅ | -| **Sensors** | Bosch Sensortec, TDK/InvenSense | ✅ | -| **Logic ICs** | Nexperia | ✅ | - ---- - -## Recommendations - -### For PDF Analyzer Users - -1. **Prefer distributor mirrors** (Mouser, Farnell, Digi-Key, Octopart) when manufacturer URLs fail -2. **Reuse file_uri** for follow-up queries on the same document (48-hour cache) -3. **Be specific with queries** - the analyzer handles technical questions well - -### For PDF Analyzer Development - -1. **URL redirect handling** - Consider following redirects for HTML-wrapped PDF download pages -2. **Error messages** - Current error messages are clear and actionable -3. **Large document handling** - Successfully processed 500+ page datasheets - ---- - -## Conclusion - -The PDF Analyzer MCP tool demonstrates **100% success rate** across 20 diverse IC datasheets from 20 different vendors. It accurately extracts technical specifications from complex engineering documents with varying formats and sizes. The tool's ability to handle multi-part family datasheets and extract structured data makes it highly suitable for engineering documentation analysis. - -The only issues encountered were URL-related (server-side 404 errors and HTML wrappers), which were easily resolved using alternative PDF sources. The Gemini 3 Pro model powering the analyzer showed excellent comprehension of technical semiconductor documentation. - ---- - -*Report generated by PDF Analyzer MCP Server testing suite* diff --git a/todo.md b/todo.md index 68251e7..02272e0 100644 --- a/todo.md +++ b/todo.md @@ -24,3 +24,10 @@ - [x] Stage 6: Dockerfile for Cloud Run deployment - [x] Stage 7: E2E tests for Cloud Run deployment - [x] Fix lazy imports for cloud-only packages (regression contract #3) +- [x] Add Vertex AI service account JSON key auth to --setup flow +- [x] Add Vertex credential slots to keychain (project, location, key file) +- [x] Fix Vertex provider IDs (google -> google-vertex, anthropic -> anthropic-vertex) +- [x] Add googleAuthOptions.keyFile support to Vertex providers +- [x] Add getSetupProviderList() and Vertex-aware resolution to registry +- [x] Add Vertex-aware setup flow to CLI commands +- [x] Update tests for Vertex setup changes