swadhinbiswas Add free-model AI providers and external agent webhook integration · 2/18/2026
eb6c565
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import type { AIAdapter, AICompletionResult, AIConfig, AIReviewPrompt } from "./types";
import { logger } from "@/lib/logger";
type ExternalAgentResponse = {
content?: string;
summary?: string;
suggestions?: unknown[];
tokensUsed?: number;
usage?: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
};
};
export class ExternalAgentAdapter implements AIAdapter {
async complete(prompt: AIReviewPrompt, config: AIConfig): Promise<AICompletionResult> {
const webhookUrl = config.baseUrl || process.env.EXTERNAL_AGENT_WEBHOOK_URL;
if (!webhookUrl) throw new Error("External agent webhook URL not configured");
logger.info({ provider: "external_agent", webhookUrl }, "Calling external review agent");
const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
},
body: JSON.stringify({
provider: "opencodehub",
model: config.model || "external-agent",
prompt,
metadata: {
source: "ai-review",
},
}),
});
// 202 is valid for async agent pipelines; mark accepted with empty suggestions.
if (response.status === 202) {
return {
content: JSON.stringify({
summary: "External agent accepted the review request",
suggestions: [],
}),
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
};
}
if (!response.ok) {
throw new Error(`External agent error: ${response.status} ${await response.text()}`);
}
const data = (await response.json()) as ExternalAgentResponse;
const content =
data.content ||
JSON.stringify({
summary: data.summary || "External agent review completed",
suggestions: data.suggestions || [],
});
const usage = data.usage || {
totalTokens: data.tokensUsed || 0,
inputTokens: 0,
outputTokens: 0,
};
return {
content,
usage: {
inputTokens: usage.inputTokens || 0,
outputTokens: usage.outputTokens || 0,
totalTokens: usage.totalTokens || 0,
},
};
}
}