comfy-qa 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/qa-research.ts +20 -16
- package/src/agent/research.ts +22 -2
- package/src/utils/github.ts +38 -28
package/package.json
CHANGED
package/src/agent/qa-research.ts
CHANGED
|
@@ -79,26 +79,14 @@ interface ResearchResults {
|
|
|
79
79
|
|
|
80
80
|
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY_QA ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
81
81
|
const OPENROUTER_KEY = process.env.OPENROUTER_API_KEY ?? "";
|
|
82
|
+
const OPENROUTER_MODEL = process.env.OPENROUTER_MODEL ?? "openai/gpt-4.5";
|
|
82
83
|
|
|
83
84
|
import Anthropic from "@anthropic-ai/sdk";
|
|
84
85
|
|
|
85
86
|
const anthropicClient = ANTHROPIC_KEY ? new Anthropic({ apiKey: ANTHROPIC_KEY, timeout: 60_000 }) : null;
|
|
86
87
|
|
|
87
88
|
async function callLLM(system: string, messages: any[]): Promise<string> {
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const res = await anthropicClient.messages.create({
|
|
91
|
-
model: "claude-sonnet-4-20250514",
|
|
92
|
-
max_tokens: 8192,
|
|
93
|
-
system,
|
|
94
|
-
messages,
|
|
95
|
-
});
|
|
96
|
-
return res.content?.[0]?.type === "text" ? res.content[0].text : "";
|
|
97
|
-
} catch (err: any) {
|
|
98
|
-
console.log(` ⚠ Anthropic SDK: ${err.message?.slice(0, 80)}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
89
|
+
// Prefer OpenRouter
|
|
102
90
|
if (OPENROUTER_KEY) {
|
|
103
91
|
try {
|
|
104
92
|
const controller = new AbortController();
|
|
@@ -108,19 +96,35 @@ async function callLLM(system: string, messages: any[]): Promise<string> {
|
|
|
108
96
|
signal: controller.signal,
|
|
109
97
|
headers: { Authorization: `Bearer ${OPENROUTER_KEY}`, "content-type": "application/json" },
|
|
110
98
|
body: JSON.stringify({
|
|
111
|
-
model:
|
|
99
|
+
model: OPENROUTER_MODEL,
|
|
112
100
|
messages: [{ role: "system", content: system }, ...messages],
|
|
113
101
|
max_tokens: 8192,
|
|
114
102
|
}),
|
|
115
103
|
});
|
|
116
104
|
clearTimeout(timer);
|
|
117
105
|
const json = (await res.json()) as any;
|
|
118
|
-
|
|
106
|
+
if (json.choices?.[0]?.message?.content) return json.choices[0].message.content;
|
|
107
|
+
console.log(` ⚠ OpenRouter: ${json.error?.message?.slice(0, 80) ?? "empty response"}`);
|
|
119
108
|
} catch (err: any) {
|
|
120
109
|
console.log(` ⚠ OpenRouter: ${err.message?.slice(0, 60)}`);
|
|
121
110
|
}
|
|
122
111
|
}
|
|
123
112
|
|
|
113
|
+
// Fallback: Anthropic SDK
|
|
114
|
+
if (anthropicClient) {
|
|
115
|
+
try {
|
|
116
|
+
const res = await anthropicClient.messages.create({
|
|
117
|
+
model: "claude-sonnet-4-20250514",
|
|
118
|
+
max_tokens: 8192,
|
|
119
|
+
system,
|
|
120
|
+
messages,
|
|
121
|
+
});
|
|
122
|
+
return res.content?.[0]?.type === "text" ? res.content[0].text : "";
|
|
123
|
+
} catch (err: any) {
|
|
124
|
+
console.log(` ⚠ Anthropic SDK: ${err.message?.slice(0, 80)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
124
128
|
return "";
|
|
125
129
|
}
|
|
126
130
|
|
package/src/agent/research.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { $ } from "bun";
|
|
2
2
|
import type { PRInfo, IssueInfo } from "../utils/github";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const OPENROUTER_KEY = process.env.OPENROUTER_API_KEY ?? "";
|
|
5
|
+
const OPENROUTER_MODEL = process.env.OPENROUTER_MODEL ?? "openai/gpt-4.5";
|
|
6
|
+
|
|
5
7
|
async function callClaude(prompt: string): Promise<string> {
|
|
8
|
+
// Prefer OpenRouter
|
|
9
|
+
if (OPENROUTER_KEY) {
|
|
10
|
+
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: { Authorization: `Bearer ${OPENROUTER_KEY}`, "content-type": "application/json" },
|
|
13
|
+
body: JSON.stringify({
|
|
14
|
+
model: OPENROUTER_MODEL,
|
|
15
|
+
messages: [{ role: "user", content: prompt }],
|
|
16
|
+
max_tokens: 4096,
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
const json = (await res.json()) as any;
|
|
20
|
+
if (json.choices?.[0]?.message?.content) return json.choices[0].message.content;
|
|
21
|
+
console.log(` ⚠ OpenRouter: ${json.error?.message?.slice(0, 80) ?? "empty response"}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Fallback: Anthropic SDK
|
|
6
25
|
const apiKey = process.env.ANTHROPIC_API_KEY_QA ?? process.env.ANTHROPIC_API_KEY;
|
|
7
26
|
if (apiKey) {
|
|
8
27
|
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
@@ -14,7 +33,8 @@ async function callClaude(prompt: string): Promise<string> {
|
|
|
14
33
|
});
|
|
15
34
|
return response.content[0].type === "text" ? response.content[0].text : "";
|
|
16
35
|
}
|
|
17
|
-
|
|
36
|
+
|
|
37
|
+
// Last resort: claude CLI
|
|
18
38
|
const proc = Bun.spawn(["claude", "--print", "--model", "claude-opus-4-6"], {
|
|
19
39
|
stdin: new TextEncoder().encode(prompt),
|
|
20
40
|
stdout: "pipe",
|
package/src/utils/github.ts
CHANGED
|
@@ -37,46 +37,56 @@ export function parseRef(ref: string): { owner: string; repo: string; number?: n
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export async function fetchPR(owner: string, repo: string, number: number): Promise<PRInfo> {
|
|
40
|
-
const
|
|
41
|
-
|
|
40
|
+
const [prJson, filesJson, commentsJson] = await Promise.all([
|
|
41
|
+
$`gh api repos/${owner}/${repo}/pulls/${number}`.text(),
|
|
42
|
+
$`gh api repos/${owner}/${repo}/pulls/${number}/files --paginate`.text(),
|
|
43
|
+
$`gh api repos/${owner}/${repo}/issues/${number}/comments --paginate`.text(),
|
|
44
|
+
]);
|
|
45
|
+
const pr = JSON.parse(prJson);
|
|
46
|
+
const files = JSON.parse(filesJson);
|
|
47
|
+
const comments = JSON.parse(commentsJson);
|
|
42
48
|
return {
|
|
43
|
-
number:
|
|
44
|
-
title:
|
|
45
|
-
body:
|
|
46
|
-
state:
|
|
47
|
-
headRefName:
|
|
48
|
-
baseRefName:
|
|
49
|
-
author:
|
|
50
|
-
labels: (
|
|
51
|
-
url:
|
|
52
|
-
files:
|
|
53
|
-
path: f.
|
|
49
|
+
number: pr.number,
|
|
50
|
+
title: pr.title,
|
|
51
|
+
body: pr.body || "",
|
|
52
|
+
state: pr.state,
|
|
53
|
+
headRefName: pr.head.ref,
|
|
54
|
+
baseRefName: pr.base.ref,
|
|
55
|
+
author: pr.user?.login || "unknown",
|
|
56
|
+
labels: (pr.labels || []).map((l: any) => l.name),
|
|
57
|
+
url: pr.html_url,
|
|
58
|
+
files: files.map((f: any) => ({
|
|
59
|
+
path: f.filename,
|
|
54
60
|
additions: f.additions,
|
|
55
61
|
deletions: f.deletions,
|
|
56
62
|
})),
|
|
57
|
-
comments:
|
|
58
|
-
author: c.
|
|
63
|
+
comments: comments.map((c: any) => ({
|
|
64
|
+
author: c.user?.login || "unknown",
|
|
59
65
|
body: c.body || "",
|
|
60
|
-
createdAt: c.
|
|
66
|
+
createdAt: c.created_at,
|
|
61
67
|
})),
|
|
62
68
|
};
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export async function fetchIssue(owner: string, repo: string, number: number): Promise<IssueInfo> {
|
|
66
|
-
const
|
|
67
|
-
|
|
72
|
+
const [issueJson, commentsJson] = await Promise.all([
|
|
73
|
+
$`gh api repos/${owner}/${repo}/issues/${number}`.text(),
|
|
74
|
+
$`gh api repos/${owner}/${repo}/issues/${number}/comments --paginate`.text(),
|
|
75
|
+
]);
|
|
76
|
+
const issue = JSON.parse(issueJson);
|
|
77
|
+
const comments = JSON.parse(commentsJson);
|
|
68
78
|
return {
|
|
69
|
-
number:
|
|
70
|
-
title:
|
|
71
|
-
body:
|
|
72
|
-
state:
|
|
73
|
-
author:
|
|
74
|
-
labels: (
|
|
75
|
-
url:
|
|
76
|
-
comments:
|
|
77
|
-
author: c.
|
|
79
|
+
number: issue.number,
|
|
80
|
+
title: issue.title,
|
|
81
|
+
body: issue.body || "",
|
|
82
|
+
state: issue.state,
|
|
83
|
+
author: issue.user?.login || "unknown",
|
|
84
|
+
labels: (issue.labels || []).map((l: any) => l.name),
|
|
85
|
+
url: issue.html_url,
|
|
86
|
+
comments: comments.map((c: any) => ({
|
|
87
|
+
author: c.user?.login || "unknown",
|
|
78
88
|
body: c.body || "",
|
|
79
|
-
createdAt: c.
|
|
89
|
+
createdAt: c.created_at,
|
|
80
90
|
})),
|
|
81
91
|
};
|
|
82
92
|
}
|