feat: add provider abstraction for review backends
This commit is contained in:
@@ -40,5 +40,6 @@ export function loadConfig() {
|
||||
maxFiles: parseNum('CHATGPT_MCP_MAX_FILES', process.env.CHATGPT_MCP_MAX_FILES, 5),
|
||||
maxLogChars: parseNum('CHATGPT_MCP_MAX_LOG_CHARS', process.env.CHATGPT_MCP_MAX_LOG_CHARS, 10000),
|
||||
redactSecrets: parseBool('CHATGPT_MCP_REDACT_SECRETS', process.env.CHATGPT_MCP_REDACT_SECRETS, true),
|
||||
chatgptMcpProvider: process.env.CHATGPT_MCP_PROVIDER || 'openai',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Chat provider factory.
|
||||
// Returns a chat provider based on config value CHATGPT_MCP_PROVIDER.
|
||||
|
||||
import { openaiProvider } from "./openai.js";
|
||||
|
||||
const SUPPORTED_PROVIDERS = new Set(["openai"]);
|
||||
|
||||
/**
|
||||
* Create a chat provider from configuration.
|
||||
* Defaults to OpenAI if no provider is specified or value is invalid.
|
||||
* @param {object} config - Loaded config object (must contain openaiApiKey).
|
||||
* @param {string} config.chatgptMcpProvider - Provider name (default: "openai").
|
||||
* @returns {{ send: (input: object, cfg: object) => Promise<{ content: string }> }} Chat provider.
|
||||
*/
|
||||
export function createChatProvider(config) {
|
||||
const providerName = config?.chatgptMcpProvider || "openai";
|
||||
|
||||
if (!SUPPORTED_PROVIDERS.has(providerName)) {
|
||||
throw new Error(
|
||||
`Unsupported chat provider "${providerName}". Supported providers: ${[...SUPPORTED_PROVIDERS].join(", ")}.`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (providerName) {
|
||||
case "openai":
|
||||
return openaiProvider;
|
||||
default:
|
||||
// Should not reach here because of the set check above.
|
||||
throw new Error(`Unknown provider "${providerName}".`);
|
||||
}
|
||||
}
|
||||
|
||||
export { openaiProvider };
|
||||
@@ -0,0 +1,29 @@
|
||||
// OpenAI chat provider adapter.
|
||||
// Wraps src/openai/client.js + src/openai/responses.js into the provider interface.
|
||||
|
||||
import { createOpenAIClient } from "../openai/client.js";
|
||||
import { sendOpenAIResponse } from "../openai/responses.js";
|
||||
|
||||
/**
|
||||
* Chat provider backed by OpenAI Responses API.
|
||||
* Implements { send(input, config) => Promise<{ content: string }> }.
|
||||
*/
|
||||
export const openaiProvider = {
|
||||
/**
|
||||
* @param {object} input - validated tool input (post-budget check).
|
||||
* @param {object} config - full config from loadConfig().
|
||||
* @returns {Promise<{ content: string }>}
|
||||
*/
|
||||
async send(input, config) {
|
||||
const client = createOpenAIClient(config);
|
||||
|
||||
const result = await sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: input }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
|
||||
return { content: result.content };
|
||||
},
|
||||
};
|
||||
+26
-17
@@ -7,8 +7,7 @@ import { handleReviewCode } from "./tools/review-code.js";
|
||||
import { handleDebugIssue } from "./tools/debug-issue.js";
|
||||
import { handleArchitectureReview } from "./tools/architecture-review.js";
|
||||
import { loadConfig } from "./config/env.js";
|
||||
import { createOpenAIClient } from "./openai/client.js";
|
||||
import { sendOpenAIResponse } from "./openai/responses.js";
|
||||
import { createChatProvider } from "./providers/factory.js";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "chatgpt-mcp",
|
||||
@@ -23,10 +22,12 @@ server.registerTool(
|
||||
inputSchema: baseInputSchema,
|
||||
},
|
||||
async (input) => {
|
||||
const config = loadConfig();
|
||||
const provider = createChatProvider(config);
|
||||
|
||||
const result = await handleAskChatGpt(input, {
|
||||
loadConfig,
|
||||
createOpenAIClient,
|
||||
sendOpenAIResponse,
|
||||
loadConfig: () => config,
|
||||
createProvider: () => ({ send: provider.send.bind(provider) }),
|
||||
});
|
||||
|
||||
const text = result.ok
|
||||
@@ -51,10 +52,12 @@ server.registerTool(
|
||||
inputSchema: baseInputSchema,
|
||||
},
|
||||
async (input) => {
|
||||
const config = loadConfig();
|
||||
const provider = createChatProvider(config);
|
||||
|
||||
const result = await handleReviewPlan(input, {
|
||||
loadConfig,
|
||||
createOpenAIClient,
|
||||
sendOpenAIResponse,
|
||||
loadConfig: () => config,
|
||||
createProvider: () => ({ send: provider.send.bind(provider) }),
|
||||
});
|
||||
|
||||
const text = result.ok
|
||||
@@ -79,10 +82,12 @@ server.registerTool(
|
||||
inputSchema: baseInputSchema,
|
||||
},
|
||||
async (input) => {
|
||||
const config = loadConfig();
|
||||
const provider = createChatProvider(config);
|
||||
|
||||
const result = await handleReviewCode(input, {
|
||||
loadConfig,
|
||||
createOpenAIClient,
|
||||
sendOpenAIResponse,
|
||||
loadConfig: () => config,
|
||||
createProvider: () => ({ send: provider.send.bind(provider) }),
|
||||
});
|
||||
|
||||
const text = result.ok
|
||||
@@ -107,10 +112,12 @@ server.registerTool(
|
||||
inputSchema: baseInputSchema,
|
||||
},
|
||||
async (input) => {
|
||||
const config = loadConfig();
|
||||
const provider = createChatProvider(config);
|
||||
|
||||
const result = await handleDebugIssue(input, {
|
||||
loadConfig,
|
||||
createOpenAIClient,
|
||||
sendOpenAIResponse,
|
||||
loadConfig: () => config,
|
||||
createProvider: () => ({ send: provider.send.bind(provider) }),
|
||||
});
|
||||
|
||||
const text = result.ok
|
||||
@@ -135,10 +142,12 @@ server.registerTool(
|
||||
inputSchema: baseInputSchema,
|
||||
},
|
||||
async (input) => {
|
||||
const config = loadConfig();
|
||||
const provider = createChatProvider(config);
|
||||
|
||||
const result = await handleArchitectureReview(input, {
|
||||
loadConfig,
|
||||
createOpenAIClient,
|
||||
sendOpenAIResponse,
|
||||
loadConfig: () => config,
|
||||
createProvider: () => ({ send: provider.send.bind(provider) }),
|
||||
});
|
||||
|
||||
const text = result.ok
|
||||
|
||||
@@ -15,8 +15,7 @@ import { buildArchitectureReviewPrompt } from "../prompts/architecture-review.js
|
||||
* Raw tool input per ARCHITECTURE.md §7 schema.
|
||||
* @param {{
|
||||
* loadConfig: () => object,
|
||||
* createOpenAIClient: (config: object) => any,
|
||||
* sendOpenAIResponse: (client: any, params: object) => Promise<any>
|
||||
* createProvider: (config: object) => { send: (input: object, cfg: object) => Promise<{ content: string }> }
|
||||
* }} deps
|
||||
* Injected external dependencies.
|
||||
* @returns {Promise<{ ok: true, answer: string, warnings: string[] } | { ok: false, error: string, warnings: string[] }>}
|
||||
@@ -49,25 +48,20 @@ export async function handleArchitectureReview(input, deps) {
|
||||
|
||||
const promptMessages = buildArchitectureReviewPrompt(budget.input);
|
||||
|
||||
// --- 5. Create OpenAI client ---
|
||||
// --- 5. Create chat provider ---
|
||||
|
||||
let client;
|
||||
let provider;
|
||||
try {
|
||||
client = deps.createOpenAIClient(config);
|
||||
provider = deps.createProvider(config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
// --- 6. Send to OpenAI ---
|
||||
// --- 6. Send via provider ---
|
||||
|
||||
let aiResult;
|
||||
try {
|
||||
aiResult = await deps.sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: promptMessages }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
aiResult = await provider.send(budget.input, config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ import { buildAskChatGptPrompt } from "../prompts/ask-chatgpt.js";
|
||||
* Raw tool input per ARCHITECTURE.md §7 schema.
|
||||
* @param {{
|
||||
* loadConfig: () => object,
|
||||
* createOpenAIClient: (config: object) => any,
|
||||
* sendOpenAIResponse: (client: any, params: object) => Promise<any>
|
||||
* createProvider: (config: object) => { send: (input: object, cfg: object) => Promise<{ content: string }> }
|
||||
* }} deps
|
||||
* Injected external dependencies.
|
||||
* @returns {Promise<{ ok: true, answer: string, warnings: string[] } | { ok: false, error: string, warnings: string[] }>}
|
||||
@@ -49,25 +48,21 @@ export async function handleAskChatGpt(input, deps) {
|
||||
|
||||
const promptMessages = buildAskChatGptPrompt(budget.input);
|
||||
|
||||
// --- 5. Create OpenAI client ---
|
||||
// --- 5. Create chat provider ---
|
||||
|
||||
let client;
|
||||
let provider;
|
||||
try {
|
||||
client = deps.createOpenAIClient(config);
|
||||
provider = deps.createProvider(config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
// --- 6. Send to OpenAI ---
|
||||
// --- 6. Send via provider ---
|
||||
|
||||
let aiResult;
|
||||
try {
|
||||
aiResult = await deps.sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: promptMessages }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
aiResult = await provider.send(budget.input, config);
|
||||
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ import { buildDebugIssuePrompt } from "../prompts/debug-issue.js";
|
||||
* Raw tool input per ARCHITECTURE.md §7 schema.
|
||||
* @param {{
|
||||
* loadConfig: () => object,
|
||||
* createOpenAIClient: (config: object) => any,
|
||||
* sendOpenAIResponse: (client: any, params: object) => Promise<any>
|
||||
* createProvider: (config: object) => { send: (input: object, cfg: object) => Promise<{ content: string }> }
|
||||
* }} deps
|
||||
* Injected external dependencies.
|
||||
* @returns {Promise<{ ok: true, answer: string, warnings: string[] } | { ok: false, error: string, warnings: string[] }>}
|
||||
@@ -49,25 +48,20 @@ export async function handleDebugIssue(input, deps) {
|
||||
|
||||
const promptMessages = buildDebugIssuePrompt(budget.input);
|
||||
|
||||
// --- 5. Create OpenAI client ---
|
||||
// --- 5. Create chat provider ---
|
||||
|
||||
let client;
|
||||
let provider;
|
||||
try {
|
||||
client = deps.createOpenAIClient(config);
|
||||
provider = deps.createProvider(config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
// --- 6. Send to OpenAI ---
|
||||
// --- 6. Send via provider ---
|
||||
|
||||
let aiResult;
|
||||
try {
|
||||
aiResult = await deps.sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: promptMessages }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
aiResult = await provider.send(budget.input, config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ import { buildReviewCodePrompt } from "../prompts/review-code.js";
|
||||
* Raw tool input per ARCHITECTURE.md §7 schema.
|
||||
* @param {{
|
||||
* loadConfig: () => object,
|
||||
* createOpenAIClient: (config: object) => any,
|
||||
* sendOpenAIResponse: (client: any, params: object) => Promise<any>
|
||||
* createProvider: (config: object) => { send: (input: object, cfg: object) => Promise<{ content: string }> }
|
||||
* }} deps
|
||||
* Injected external dependencies.
|
||||
* @returns {Promise<{ ok: true, answer: string, warnings: string[] } | { ok: false, error: string, warnings: string[] }>}
|
||||
@@ -49,25 +48,20 @@ export async function handleReviewCode(input, deps) {
|
||||
|
||||
const promptMessages = buildReviewCodePrompt(budget.input);
|
||||
|
||||
// --- 5. Create OpenAI client ---
|
||||
// --- 5. Create chat provider ---
|
||||
|
||||
let client;
|
||||
let provider;
|
||||
try {
|
||||
client = deps.createOpenAIClient(config);
|
||||
provider = deps.createProvider(config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
// --- 6. Send to OpenAI ---
|
||||
// --- 6. Send via provider ---
|
||||
|
||||
let aiResult;
|
||||
try {
|
||||
aiResult = await deps.sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: promptMessages }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
aiResult = await provider.send(budget.input, config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ import { buildReviewPlanPrompt } from "../prompts/review-plan.js";
|
||||
* Raw tool input per ARCHITECTURE.md §7 schema.
|
||||
* @param {{
|
||||
* loadConfig: () => object,
|
||||
* createOpenAIClient: (config: object) => any,
|
||||
* sendOpenAIResponse: (client: any, params: object) => Promise<any>
|
||||
* createProvider: (config: object) => { send: (input: object, cfg: object) => Promise<{ content: string }> }
|
||||
* }} deps
|
||||
* Injected external dependencies.
|
||||
* @returns {Promise<{ ok: true, answer: string, warnings: string[] } | { ok: false, error: string, warnings: string[] }>}
|
||||
@@ -49,25 +48,21 @@ export async function handleReviewPlan(input, deps) {
|
||||
|
||||
const promptMessages = buildReviewPlanPrompt(budget.input);
|
||||
|
||||
// --- 5. Create OpenAI client ---
|
||||
// --- 5. Create chat provider ---
|
||||
|
||||
let client;
|
||||
let provider;
|
||||
try {
|
||||
client = deps.createOpenAIClient(config);
|
||||
provider = deps.createProvider(config);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
// --- 6. Send to OpenAI ---
|
||||
// --- 6. Send via provider ---
|
||||
|
||||
let aiResult;
|
||||
try {
|
||||
aiResult = await deps.sendOpenAIResponse(client, {
|
||||
input: [{ role: "system", content: promptMessages }],
|
||||
model: config.openaiModel,
|
||||
temperature: config.temperature,
|
||||
maxOutputTokens: config.maxOutputTokens,
|
||||
});
|
||||
aiResult = await provider.send(budget.input, config);
|
||||
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err), warnings: [] };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user