@maintainabilityai/research-runner 0.1.13 → 0.1.15
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/dist/llm/llm-router.js
CHANGED
|
@@ -3,23 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.callLlm = callLlm;
|
|
4
4
|
const anthropic_client_1 = require("./anthropic-client");
|
|
5
5
|
const github_models_client_1 = require("./github-models-client");
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Per-tier per-provider model id lookup. `githubModelsFallback` (when
|
|
8
|
+
* present) is tried automatically on 401/403/404 from the primary —
|
|
9
|
+
* how we let users with a GMT (Copilot-Pro user PAT) reach the
|
|
10
|
+
* "custom"-tier `gpt-5-chat` while users on a workflow bot token
|
|
11
|
+
* still work via the "low"-tier fallback.
|
|
12
|
+
*/
|
|
7
13
|
const MODEL_BY_TIER = {
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
plan: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
// Plan tier — small structured-JSON output, one shot, fits any cap.
|
|
15
|
+
// Try gpt-5-chat first (custom tier, only reachable with a Copilot-
|
|
16
|
+
// enrolled PAT like GMT). Fall back to gpt-4.1-mini (low tier, works
|
|
17
|
+
// on every token including the Actions bot). Empirically equivalent
|
|
18
|
+
// output quality on the V3-anchor prompt — the upgrade is "if free,
|
|
19
|
+
// why not"; the fallback keeps everyone working.
|
|
20
|
+
plan: {
|
|
21
|
+
anthropic: 'claude-haiku-4-5',
|
|
22
|
+
githubModels: 'openai/gpt-5-chat',
|
|
23
|
+
githubModelsFallback: 'openai/gpt-4.1-mini',
|
|
24
|
+
},
|
|
25
|
+
// Synth tier — 200K context, non-reasoning (verified live with
|
|
26
|
+
// reasoning_tokens=0, finish_reason=stop). Picked over gpt-5 and
|
|
27
|
+
// gpt-5-mini because those are reasoning models that consume the
|
|
19
28
|
// completion budget on hidden chain-of-thought before producing any
|
|
20
|
-
// visible markdown
|
|
21
|
-
//
|
|
22
|
-
//
|
|
29
|
+
// visible markdown. Synth needs predictable structured output. No
|
|
30
|
+
// fallback model here — synth runs on the agent side now (Copilot
|
|
31
|
+
// Coding Agent / @claude), so the runner doesn't fire synth itself.
|
|
23
32
|
synth: { anthropic: 'claude-sonnet-4-6', githubModels: 'openai/gpt-5-chat' },
|
|
24
33
|
};
|
|
25
34
|
/**
|
|
@@ -57,28 +66,54 @@ async function callAnthropicTier(opts, tierModels) {
|
|
|
57
66
|
httpStatus: r.httpStatus,
|
|
58
67
|
};
|
|
59
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* GitHub Models returns 401 / 403 / 404 when the token can't reach the
|
|
71
|
+
* requested model (typically the workflow bot token hitting a "custom"-
|
|
72
|
+
* tier model like gpt-5-chat). These are recoverable via fallback;
|
|
73
|
+
* everything else (timeouts, 5xx, 413 cap, parse errors) should
|
|
74
|
+
* propagate.
|
|
75
|
+
*/
|
|
76
|
+
function isModelAccessError(err) {
|
|
77
|
+
if (!(err instanceof Error)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return /GitHub Models returned 40[134]:/.test(err.message);
|
|
81
|
+
}
|
|
60
82
|
async function callGitHubModelsTier(opts, tierModels) {
|
|
61
83
|
if (!opts.githubToken) {
|
|
62
84
|
throw new Error(`callLlm: provider=github-models requires githubToken (set GITHUB_TOKEN; workflow needs \`permissions: models: read\`).`);
|
|
63
85
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
const callOne = async (model) => {
|
|
87
|
+
const r = await (0, github_models_client_1.callGitHubModels)({
|
|
88
|
+
token: opts.githubToken,
|
|
89
|
+
model,
|
|
90
|
+
system: opts.system,
|
|
91
|
+
prompt: opts.prompt,
|
|
92
|
+
maxTokens: opts.maxTokens,
|
|
93
|
+
temperature: opts.temperature,
|
|
94
|
+
fetchImpl: opts.fetchImpl,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
provider: 'github-models',
|
|
98
|
+
model,
|
|
99
|
+
text: r.text,
|
|
100
|
+
inputTokens: r.inputTokens,
|
|
101
|
+
outputTokens: r.outputTokens,
|
|
102
|
+
costUsd: r.costUsd,
|
|
103
|
+
httpStatus: r.httpStatus,
|
|
104
|
+
};
|
|
81
105
|
};
|
|
106
|
+
try {
|
|
107
|
+
return await callOne(tierModels.githubModels);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (tierModels.githubModelsFallback && isModelAccessError(err)) {
|
|
111
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
112
|
+
process.stderr.write(`[research-runner] ⚠ github-models ${tierModels.githubModels} access-denied, falling back to ${tierModels.githubModelsFallback}. Cause: ${cause.slice(0, 200)}\n`);
|
|
113
|
+
return await callOne(tierModels.githubModelsFallback);
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
82
117
|
}
|
|
83
118
|
async function callLlm(opts) {
|
|
84
119
|
const tierModels = MODEL_BY_TIER[opts.tier];
|
|
@@ -515,7 +515,9 @@ async function runArcheologist(opts) {
|
|
|
515
515
|
provider: brief.llm_provider,
|
|
516
516
|
// plan_queries is the only LLM hop we run now (synth handed off
|
|
517
517
|
// to the assigned agent). Surface that model in the Hatter's Tag.
|
|
518
|
-
|
|
518
|
+
// Plan-tier primary (router falls back to gpt-4.1-mini on access denial).
|
|
519
|
+
// Synth runs on the agent side, so this is the only LLM hop the runner makes.
|
|
520
|
+
model: 'openai/gpt-5-chat',
|
|
519
521
|
input_tokens: totalInputTokens,
|
|
520
522
|
output_tokens: totalOutputTokens,
|
|
521
523
|
cost_usd: roundUsd(totalCostUsd),
|
|
@@ -40,7 +40,10 @@ async function runHackerNewsSearch(opts) {
|
|
|
40
40
|
fromQuery: query,
|
|
41
41
|
title: r.title,
|
|
42
42
|
url,
|
|
43
|
-
|
|
43
|
+
// Show HN / Ask HN posts carry a self-post body. URL-only
|
|
44
|
+
// submissions have no body — leave empty; the synth agent
|
|
45
|
+
// can read the linked URL if it needs more context.
|
|
46
|
+
content: r.storyText,
|
|
44
47
|
score: pointsToScore(r.points),
|
|
45
48
|
publishedDate: r.createdAt || undefined,
|
|
46
49
|
authors: r.author ? [r.author] : undefined,
|
|
@@ -15,6 +15,8 @@ export interface HackerNewsResult {
|
|
|
15
15
|
points: number;
|
|
16
16
|
numComments: number;
|
|
17
17
|
createdAt: string;
|
|
18
|
+
/** Self-post body for Show HN / Ask HN; empty for URL submissions. */
|
|
19
|
+
storyText: string;
|
|
18
20
|
}
|
|
19
21
|
export interface HackerNewsSearchOpts {
|
|
20
22
|
query: string;
|
|
@@ -39,6 +39,29 @@ async function hackerNewsSearch(opts) {
|
|
|
39
39
|
points: h.points ?? 0,
|
|
40
40
|
numComments: h.num_comments ?? 0,
|
|
41
41
|
createdAt: h.created_at ?? '',
|
|
42
|
+
// Algolia returns the self-post body for Show HN / Ask HN. URL-only
|
|
43
|
+
// submissions have this empty. Strip basic HTML tags so the excerpt
|
|
44
|
+
// is readable in the issue comment.
|
|
45
|
+
storyText: stripBasicHtml(h.story_text ?? '').slice(0, 2000),
|
|
42
46
|
})).filter(r => r.objectId && r.title);
|
|
43
47
|
return { query: opts.query, results, responseBytes: Buffer.byteLength(rawText, 'utf8'), httpStatus };
|
|
44
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* HN Algolia returns Show HN / Ask HN bodies with light HTML
|
|
51
|
+
* (`<p>`, `<i>`, etc.). Strip tags + decode the common entities so
|
|
52
|
+
* the excerpt blockquote in the issue body reads cleanly. No need for
|
|
53
|
+
* a full HTML parser — these posts are plain text with a sprinkle of
|
|
54
|
+
* inline tags.
|
|
55
|
+
*/
|
|
56
|
+
function stripBasicHtml(s) {
|
|
57
|
+
return s
|
|
58
|
+
.replace(/<\/?(?:p|br|i|b|em|strong|code|pre|a|ul|ol|li)[^>]*>/gi, ' ')
|
|
59
|
+
.replace(/ /g, ' ')
|
|
60
|
+
.replace(/&/g, '&')
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>')
|
|
63
|
+
.replace(/"/g, '"')
|
|
64
|
+
.replace(/'/g, "'")
|
|
65
|
+
.replace(/\s+/g, ' ')
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainabilityai/research-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "MaintainabilityAI",
|