From: Stefan Gasser Date: Sat, 17 Jan 2026 20:07:39 +0000 (+0100) Subject: Reorganize for multi-provider support (#48) X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=d68cf3dc23092356bae71bcd82c39a33361fcde1;p=sgasser-llm-shield.git Reorganize for multi-provider support (#48) * Reorganize for multi-provider support Move LLM client to providers/ and rename proxy routes to openai: - src/services/llm-client.ts → src/providers/openai-client.ts - src/routes/proxy.ts → src/routes/openai.ts - Rename proxyRoutes export to openaiRoutes Prepares codebase for adding Anthropic provider support. * Fix import sorting for biome check --- diff --git a/src/index.ts b/src/index.ts index c041135..88c2f69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { getPIIDetector } from "./pii/detect"; import { dashboardRoutes } from "./routes/dashboard"; import { healthRoutes } from "./routes/health"; import { infoRoutes } from "./routes/info"; -import { proxyRoutes } from "./routes/proxy"; +import { openaiRoutes } from "./routes/openai"; import { getLogger } from "./services/logger"; type Variables = { @@ -42,7 +42,7 @@ app.get("/favicon.svg", (c) => { app.route("/", healthRoutes); app.route("/", infoRoutes); -app.route("/openai/v1", proxyRoutes); +app.route("/openai/v1", openaiRoutes); if (config.dashboard.enabled) { app.route("/dashboard", dashboardRoutes); diff --git a/src/pii/mask.test.ts b/src/pii/mask.test.ts index fdd582e..7cac72c 100644 --- a/src/pii/mask.test.ts +++ b/src/pii/mask.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import type { MaskingConfig } from "../config"; -import type { ChatMessage } from "../services/llm-client"; +import type { ChatMessage } from "../providers/openai-client"; import { createPIIResult } from "../test-utils/detection-results"; import type { PIIEntity } from "./detect"; import { diff --git a/src/pii/mask.ts b/src/pii/mask.ts index 13d1c13..6133e71 100644 --- a/src/pii/mask.ts +++ b/src/pii/mask.ts @@ -1,5 +1,5 @@ import type { MaskingConfig } from "../config"; -import type { ChatCompletionResponse, ChatMessage } from "../services/llm-client"; +import type { ChatCompletionResponse, ChatMessage } from "../providers/openai-client"; import { resolveConflicts } from "../utils/conflict-resolver"; import { createPlaceholderContext, diff --git a/src/services/llm-client.ts b/src/providers/openai-client.ts similarity index 100% rename from src/services/llm-client.ts rename to src/providers/openai-client.ts diff --git a/src/routes/proxy.test.ts b/src/routes/openai.test.ts similarity index 96% rename from src/routes/proxy.test.ts rename to src/routes/openai.test.ts index efebac3..b9fe618 100644 --- a/src/routes/proxy.test.ts +++ b/src/routes/openai.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test } from "bun:test"; import { Hono } from "hono"; -import { proxyRoutes } from "./proxy"; +import { openaiRoutes } from "./openai"; const app = new Hono(); -app.route("/openai/v1", proxyRoutes); +app.route("/openai/v1", openaiRoutes); describe("POST /openai/v1/chat/completions", () => { test("returns 400 for missing messages", async () => { diff --git a/src/routes/proxy.ts b/src/routes/openai.ts similarity index 99% rename from src/routes/proxy.ts rename to src/routes/openai.ts index 99b91b5..b49a4ec 100644 --- a/src/routes/proxy.ts +++ b/src/routes/openai.ts @@ -5,16 +5,16 @@ import { proxy } from "hono/proxy"; import { z } from "zod"; import { getConfig, type MaskingConfig } from "../config"; import { unmaskResponse as unmaskPIIResponse } from "../pii/mask"; -import { detectSecretsInMessages, type MessageSecretsResult } from "../secrets/detect"; -import { maskMessages as maskSecretsMessages, unmaskSecretsResponse } from "../secrets/mask"; -import { getRouter, type MaskDecision, type RoutingDecision } from "../services/decision"; import { type ChatCompletionRequest, type ChatCompletionResponse, type ChatMessage, LLMError, type LLMResult, -} from "../services/llm-client"; +} from "../providers/openai-client"; +import { detectSecretsInMessages, type MessageSecretsResult } from "../secrets/detect"; +import { maskMessages as maskSecretsMessages, unmaskSecretsResponse } from "../secrets/mask"; +import { getRouter, type MaskDecision, type RoutingDecision } from "../services/decision"; import { logRequest, type RequestLogData } from "../services/logger"; import { createUnmaskingStream } from "../services/stream-transformer"; import { extractTextContent } from "../utils/content"; @@ -36,7 +36,7 @@ const ChatCompletionSchema = z }) .passthrough(); -export const proxyRoutes = new Hono(); +export const openaiRoutes = new Hono(); /** * Type guard for MaskDecision @@ -83,7 +83,7 @@ function createErrorLogData( /** * POST /v1/chat/completions - OpenAI-compatible chat completion endpoint */ -proxyRoutes.post( +openaiRoutes.post( "/chat/completions", zValidator("json", ChatCompletionSchema, (result, c) => { if (!result.success) { @@ -474,7 +474,7 @@ function formatMessagesForLog(messages: ChatMessage[]): string { /** * Wildcard proxy for /models, /embeddings, /audio/*, /images/*, etc. */ -proxyRoutes.all("/*", (c) => { +openaiRoutes.all("/*", (c) => { const { openai } = getRouter().getProvidersInfo(); const path = c.req.path.replace(/^\/openai\/v1/, ""); diff --git a/src/secrets/detect.ts b/src/secrets/detect.ts index 36e5edd..dfe106e 100644 --- a/src/secrets/detect.ts +++ b/src/secrets/detect.ts @@ -1,5 +1,5 @@ import type { SecretsDetectionConfig } from "../config"; -import type { ChatMessage } from "../services/llm-client"; +import type { ChatMessage } from "../providers/openai-client"; import type { ContentPart } from "../utils/content"; import { patternDetectors } from "./patterns"; import type { diff --git a/src/secrets/mask.ts b/src/secrets/mask.ts index 0c8cf19..2d3139c 100644 --- a/src/secrets/mask.ts +++ b/src/secrets/mask.ts @@ -1,4 +1,4 @@ -import type { ChatCompletionResponse, ChatMessage } from "../services/llm-client"; +import type { ChatCompletionResponse, ChatMessage } from "../providers/openai-client"; import { resolveOverlaps } from "../utils/conflict-resolver"; import { createPlaceholderContext, diff --git a/src/secrets/multimodal.test.ts b/src/secrets/multimodal.test.ts index e5a4a2a..6a23a8e 100644 --- a/src/secrets/multimodal.test.ts +++ b/src/secrets/multimodal.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import type { PIIDetectionResult, PIIEntity } from "../pii/detect"; import { maskMessages } from "../pii/mask"; -import type { ChatMessage } from "../services/llm-client"; +import type { ChatMessage } from "../providers/openai-client"; import type { ContentPart } from "../utils/content"; /** diff --git a/src/services/decision.ts b/src/services/decision.ts index a6bef8b..a07286d 100644 --- a/src/services/decision.ts +++ b/src/services/decision.ts @@ -1,9 +1,9 @@ import { type Config, getConfig } from "../config"; import { getPIIDetector, type PIIDetectionResult } from "../pii/detect"; import { createMaskingContext, maskMessages } from "../pii/mask"; +import { type ChatMessage, LLMClient } from "../providers/openai-client"; import type { MessageSecretsResult } from "../secrets/detect"; import type { PlaceholderContext } from "../utils/message-transform"; -import { type ChatMessage, LLMClient } from "./llm-client"; /** * Routing decision result for route mode diff --git a/src/utils/message-transform.test.ts b/src/utils/message-transform.test.ts index 0c68abc..72c4359 100644 --- a/src/utils/message-transform.test.ts +++ b/src/utils/message-transform.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { ChatMessage } from "../services/llm-client"; +import type { ChatMessage } from "../providers/openai-client"; import type { Span } from "./conflict-resolver"; import { createPlaceholderContext, diff --git a/src/utils/message-transform.ts b/src/utils/message-transform.ts index 424d152..408b4c1 100644 --- a/src/utils/message-transform.ts +++ b/src/utils/message-transform.ts @@ -9,7 +9,7 @@ * This module provides shared infrastructure to avoid duplication. */ -import type { ChatMessage } from "../services/llm-client"; +import type { ChatMessage } from "../providers/openai-client"; import type { Span } from "./conflict-resolver"; import type { ContentPart } from "./content"; import { findPartialPlaceholderStart } from "./placeholders";