ghagga-core 2.0.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/README.md +51 -0
- package/dist/agents/consensus.d.ts +68 -0
- package/dist/agents/consensus.d.ts.map +1 -0
- package/dist/agents/consensus.js +216 -0
- package/dist/agents/consensus.js.map +1 -0
- package/dist/agents/prompts.d.ts +18 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +194 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/agents/simple.d.ts +49 -0
- package/dist/agents/simple.d.ts.map +1 -0
- package/dist/agents/simple.js +135 -0
- package/dist/agents/simple.js.map +1 -0
- package/dist/agents/workflow.d.ts +40 -0
- package/dist/agents/workflow.d.ts.map +1 -0
- package/dist/agents/workflow.js +127 -0
- package/dist/agents/workflow.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/context.d.ts +23 -0
- package/dist/memory/context.d.ts.map +1 -0
- package/dist/memory/context.js +36 -0
- package/dist/memory/context.js.map +1 -0
- package/dist/memory/persist.d.ts +22 -0
- package/dist/memory/persist.d.ts.map +1 -0
- package/dist/memory/persist.js +103 -0
- package/dist/memory/persist.js.map +1 -0
- package/dist/memory/privacy.d.ts +19 -0
- package/dist/memory/privacy.d.ts.map +1 -0
- package/dist/memory/privacy.js +77 -0
- package/dist/memory/privacy.js.map +1 -0
- package/dist/memory/search.d.ts +20 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +76 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/pipeline.d.ts +30 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +267 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/providers/fallback.d.ts +46 -0
- package/dist/providers/fallback.d.ts.map +1 -0
- package/dist/providers/fallback.js +84 -0
- package/dist/providers/fallback.js.map +1 -0
- package/dist/providers/index.d.ts +40 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +76 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/tools/cpd.d.ts +24 -0
- package/dist/tools/cpd.d.ts.map +1 -0
- package/dist/tools/cpd.js +130 -0
- package/dist/tools/cpd.js.map +1 -0
- package/dist/tools/runner.d.ts +19 -0
- package/dist/tools/runner.d.ts.map +1 -0
- package/dist/tools/runner.js +61 -0
- package/dist/tools/runner.js.map +1 -0
- package/dist/tools/semgrep.d.ts +12 -0
- package/dist/tools/semgrep.d.ts.map +1 -0
- package/dist/tools/semgrep.js +97 -0
- package/dist/tools/semgrep.js.map +1 -0
- package/dist/tools/trivy.d.ts +11 -0
- package/dist/tools/trivy.d.ts.map +1 -0
- package/dist/tools/trivy.js +74 -0
- package/dist/tools/trivy.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/diff.d.ts +53 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +103 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/stack-detect.d.ts +15 -0
- package/dist/utils/stack-detect.d.ts.map +1 -0
- package/dist/utils/stack-detect.js +54 -0
- package/dist/utils/stack-detect.js.map +1 -0
- package/dist/utils/token-budget.d.ts +31 -0
- package/dist/utils/token-budget.d.ts.map +1 -0
- package/dist/utils/token-budget.js +62 -0
- package/dist/utils/token-budget.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy-safe text sanitization.
|
|
3
|
+
*
|
|
4
|
+
* Strips sensitive data (API keys, tokens, passwords) from text
|
|
5
|
+
* before it gets persisted to memory. This ensures that even if
|
|
6
|
+
* a diff contains credentials, they won't be stored in the database.
|
|
7
|
+
*/
|
|
8
|
+
// ─── Patterns ───────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Regex patterns for common secret formats.
|
|
11
|
+
* Each pattern is paired with a human-readable replacement label.
|
|
12
|
+
*/
|
|
13
|
+
const SENSITIVE_PATTERNS = [
|
|
14
|
+
// Anthropic API keys
|
|
15
|
+
{ pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/g, replacement: '[REDACTED_ANTHROPIC_KEY]' },
|
|
16
|
+
// OpenAI API keys (classic sk-... and newer sk-proj-... with internal hyphens)
|
|
17
|
+
{ pattern: /sk-[a-zA-Z0-9_-]{20,}/g, replacement: '[REDACTED_OPENAI_KEY]' },
|
|
18
|
+
// AWS Access Key IDs
|
|
19
|
+
{ pattern: /AKIA[0-9A-Z]{16}/g, replacement: '[REDACTED_AWS_KEY]' },
|
|
20
|
+
// AWS Secret Access Keys (typically 40 chars, base64-ish)
|
|
21
|
+
{ pattern: /(?<=AWS_SECRET_ACCESS_KEY\s*=\s*)[A-Za-z0-9/+=]{40}/g, replacement: '[REDACTED_AWS_SECRET]' },
|
|
22
|
+
// GitHub tokens (classic and fine-grained)
|
|
23
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36,}/g, replacement: '[REDACTED_GITHUB_PAT]' },
|
|
24
|
+
{ pattern: /gho_[a-zA-Z0-9]{36,}/g, replacement: '[REDACTED_GITHUB_OAUTH]' },
|
|
25
|
+
{ pattern: /ghs_[a-zA-Z0-9]{36,}/g, replacement: '[REDACTED_GITHUB_APP]' },
|
|
26
|
+
{ pattern: /ghr_[a-zA-Z0-9]{36,}/g, replacement: '[REDACTED_GITHUB_REFRESH]' },
|
|
27
|
+
{ pattern: /github_pat_[a-zA-Z0-9_]{22,}/g, replacement: '[REDACTED_GITHUB_FINE_PAT]' },
|
|
28
|
+
// Google API keys
|
|
29
|
+
{ pattern: /AIza[0-9A-Za-z_-]{35}/g, replacement: '[REDACTED_GOOGLE_KEY]' },
|
|
30
|
+
// Slack tokens
|
|
31
|
+
{ pattern: /xox[bpors]-[0-9a-zA-Z-]{10,}/g, replacement: '[REDACTED_SLACK_TOKEN]' },
|
|
32
|
+
// Generic Bearer tokens in headers
|
|
33
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9._-]{20,}/gi, replacement: 'Bearer [REDACTED_TOKEN]' },
|
|
34
|
+
// Generic "password" / "secret" / "token" assignments
|
|
35
|
+
// Matches: password = "...", PASSWORD: "...", secret: '...', token='...'
|
|
36
|
+
{
|
|
37
|
+
pattern: /(?<=(password|secret|token|api_key|apikey|api-key)\s*[:=]\s*['"])[^'"]{8,}(?=['"])/gi,
|
|
38
|
+
replacement: '[REDACTED]',
|
|
39
|
+
},
|
|
40
|
+
// Base64-encoded strings that look like they could be secrets (64+ chars)
|
|
41
|
+
// Only match when preceded by common secret-related variable names
|
|
42
|
+
{
|
|
43
|
+
pattern: /(?<=(SECRET|KEY|TOKEN|CREDENTIAL|PASSWORD)\s*[:=]\s*['"]?)[A-Za-z0-9+/]{64,}={0,2}(?=['"]?)/gi,
|
|
44
|
+
replacement: '[REDACTED_BASE64]',
|
|
45
|
+
},
|
|
46
|
+
// Private keys (PEM format)
|
|
47
|
+
{
|
|
48
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
|
|
49
|
+
replacement: '[REDACTED_PRIVATE_KEY]',
|
|
50
|
+
},
|
|
51
|
+
// JWT tokens (three base64url segments separated by dots)
|
|
52
|
+
{
|
|
53
|
+
pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g,
|
|
54
|
+
replacement: '[REDACTED_JWT]',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
// ─── Main Function ──────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Strip sensitive data from text before persisting to memory.
|
|
60
|
+
*
|
|
61
|
+
* Applies all known secret patterns and replaces matches with
|
|
62
|
+
* human-readable redaction labels. The patterns are applied in
|
|
63
|
+
* order, so more specific patterns take precedence.
|
|
64
|
+
*
|
|
65
|
+
* @param text - The text to sanitize
|
|
66
|
+
* @returns Sanitized text with secrets replaced by redaction labels
|
|
67
|
+
*/
|
|
68
|
+
export function stripPrivateData(text) {
|
|
69
|
+
let sanitized = text;
|
|
70
|
+
for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
|
|
71
|
+
// Reset regex lastIndex for each application (since we use /g flag)
|
|
72
|
+
pattern.lastIndex = 0;
|
|
73
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
74
|
+
}
|
|
75
|
+
return sanitized;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=privacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/memory/privacy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mEAAmE;AAEnE;;;GAGG;AACH,MAAM,kBAAkB,GAAoD;IAC1E,qBAAqB;IACrB,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAElF,+EAA+E;IAC/E,EAAE,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,uBAAuB,EAAE;IAE3E,qBAAqB;IACrB,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,oBAAoB,EAAE;IAEnE,0DAA0D;IAC1D,EAAE,OAAO,EAAE,sDAAsD,EAAE,WAAW,EAAE,uBAAuB,EAAE;IAEzG,2CAA2C;IAC3C,EAAE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,uBAAuB,EAAE;IAC1E,EAAE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,yBAAyB,EAAE;IAC5E,EAAE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,uBAAuB,EAAE;IAC1E,EAAE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,2BAA2B,EAAE;IAC9E,EAAE,OAAO,EAAE,+BAA+B,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAEvF,kBAAkB;IAClB,EAAE,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,uBAAuB,EAAE;IAE3E,eAAe;IACf,EAAE,OAAO,EAAE,+BAA+B,EAAE,WAAW,EAAE,wBAAwB,EAAE;IAEnF,mCAAmC;IACnC,EAAE,OAAO,EAAE,gCAAgC,EAAE,WAAW,EAAE,yBAAyB,EAAE;IAErF,sDAAsD;IACtD,yEAAyE;IACzE;QACE,OAAO,EAAE,sFAAsF;QAC/F,WAAW,EAAE,YAAY;KAC1B;IAED,0EAA0E;IAC1E,mEAAmE;IACnE;QACE,OAAO,EAAE,+FAA+F;QACxG,WAAW,EAAE,mBAAmB;KACjC;IAED,4BAA4B;IAC5B;QACE,OAAO,EAAE,mGAAmG;QAC5G,WAAW,EAAE,wBAAwB;KACtC;IAED,0DAA0D;IAC1D;QACE,OAAO,EAAE,mEAAmE;QAC5E,WAAW,EAAE,gBAAgB;KAC9B;CACF,CAAC;AAEF,mEAAmE;AAEnE;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,SAAS,GAAG,IAAI,CAAC;IAErB,KAAK,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,kBAAkB,EAAE,CAAC;QAC1D,oEAAoE;QACpE,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QACtB,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory search — retrieves relevant past observations for prompt injection.
|
|
3
|
+
*
|
|
4
|
+
* Builds a search query from the file paths in the current diff,
|
|
5
|
+
* then uses PostgreSQL full-text search (via ghagga-db) to find
|
|
6
|
+
* observations from past reviews of the same project.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Search past review observations for context relevant to the current diff.
|
|
10
|
+
*
|
|
11
|
+
* Returns a formatted string suitable for injection into agent prompts,
|
|
12
|
+
* or null if no relevant observations are found (or if db is unavailable).
|
|
13
|
+
*
|
|
14
|
+
* @param db - Database instance (from ghagga-db). Typed as unknown for loose coupling.
|
|
15
|
+
* @param project - Project identifier (e.g., "owner/repo")
|
|
16
|
+
* @param fileList - List of file paths in the current diff
|
|
17
|
+
* @returns Formatted memory context string, or null
|
|
18
|
+
*/
|
|
19
|
+
export declare function searchMemoryForContext(db: unknown, project: string, fileList: string[]): Promise<string | null>;
|
|
20
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/memory/search.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqDH;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,OAAO,EACX,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory search — retrieves relevant past observations for prompt injection.
|
|
3
|
+
*
|
|
4
|
+
* Builds a search query from the file paths in the current diff,
|
|
5
|
+
* then uses PostgreSQL full-text search (via ghagga-db) to find
|
|
6
|
+
* observations from past reviews of the same project.
|
|
7
|
+
*/
|
|
8
|
+
import { searchObservations } from 'ghagga-db';
|
|
9
|
+
import { formatMemoryContext } from './context.js';
|
|
10
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Build a search query from file paths.
|
|
13
|
+
*
|
|
14
|
+
* Extracts meaningful segments from file paths (directory names,
|
|
15
|
+
* file names without extensions) to use as search terms.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ["src/auth/login.ts", "lib/db/pool.ts"]
|
|
19
|
+
* → "auth login db pool"
|
|
20
|
+
*/
|
|
21
|
+
function buildSearchQuery(fileList) {
|
|
22
|
+
const terms = new Set();
|
|
23
|
+
for (const filePath of fileList) {
|
|
24
|
+
// Split path into segments
|
|
25
|
+
const segments = filePath.split('/').filter(Boolean);
|
|
26
|
+
for (const segment of segments) {
|
|
27
|
+
// Skip common uninformative directories
|
|
28
|
+
if (['src', 'lib', 'dist', 'build', 'node_modules', 'test', 'tests'].includes(segment)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// Remove file extension and add as a search term
|
|
32
|
+
const name = segment.replace(/\.[^.]+$/, '');
|
|
33
|
+
if (name.length > 2) {
|
|
34
|
+
terms.add(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...terms].slice(0, 10).join(' ');
|
|
39
|
+
}
|
|
40
|
+
// ─── Main Function ──────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Search past review observations for context relevant to the current diff.
|
|
43
|
+
*
|
|
44
|
+
* Returns a formatted string suitable for injection into agent prompts,
|
|
45
|
+
* or null if no relevant observations are found (or if db is unavailable).
|
|
46
|
+
*
|
|
47
|
+
* @param db - Database instance (from ghagga-db). Typed as unknown for loose coupling.
|
|
48
|
+
* @param project - Project identifier (e.g., "owner/repo")
|
|
49
|
+
* @param fileList - List of file paths in the current diff
|
|
50
|
+
* @returns Formatted memory context string, or null
|
|
51
|
+
*/
|
|
52
|
+
export async function searchMemoryForContext(db, project, fileList) {
|
|
53
|
+
try {
|
|
54
|
+
if (!db)
|
|
55
|
+
return null;
|
|
56
|
+
const query = buildSearchQuery(fileList);
|
|
57
|
+
if (!query)
|
|
58
|
+
return null;
|
|
59
|
+
// Search with a reasonable limit — we don't want to flood the prompt
|
|
60
|
+
const observations = await searchObservations(db, project, query, { limit: 8 });
|
|
61
|
+
if (!observations || observations.length === 0)
|
|
62
|
+
return null;
|
|
63
|
+
// Format observations for prompt injection
|
|
64
|
+
return formatMemoryContext(observations.map((obs) => ({
|
|
65
|
+
type: obs.type,
|
|
66
|
+
title: obs.title,
|
|
67
|
+
content: obs.content,
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// Memory is optional — never let it break the review pipeline
|
|
72
|
+
console.warn('[ghagga] Memory search failed (degrading gracefully):', error instanceof Error ? error.message : String(error));
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/memory/search.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAYnD,mEAAmE;AAEnE;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,QAAkB;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAChC,2BAA2B;QAC3B,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAErD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,wCAAwC;YACxC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvF,SAAS;YACX,CAAC;YAED,iDAAiD;YACjD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YAC7C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED,mEAAmE;AAEnE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,EAAW,EACX,OAAe,EACf,QAAkB;IAElB,IAAI,CAAC;QACH,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAErB,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,qEAAqE;QACrE,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAC3C,EAA8C,EAC9C,OAAO,EACP,KAAK,EACL,EAAE,KAAK,EAAE,CAAC,EAAE,CACb,CAAC;QAEF,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5D,2CAA2C;QAC3C,OAAO,mBAAmB,CACxB,YAAY,CAAC,GAAG,CAAC,CAAC,GAAgB,EAAE,EAAE,CAAC,CAAC;YACtC,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8DAA8D;QAC9D,OAAO,CAAC,IAAI,CACV,uDAAuD,EACvD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main review pipeline orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates the entire review flow:
|
|
5
|
+
* 1. Validate input
|
|
6
|
+
* 2. Parse and filter the diff
|
|
7
|
+
* 3. Detect tech stacks
|
|
8
|
+
* 4. Run static analysis tools
|
|
9
|
+
* 5. Search memory for past context
|
|
10
|
+
* 6. Execute the selected agent mode
|
|
11
|
+
* 7. Persist new observations to memory
|
|
12
|
+
* 8. Return the final result
|
|
13
|
+
*
|
|
14
|
+
* Each step degrades gracefully — if static analysis fails, or
|
|
15
|
+
* memory is unavailable, the pipeline continues with what it has.
|
|
16
|
+
*/
|
|
17
|
+
import type { ReviewInput, ReviewResult } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Run the full review pipeline.
|
|
20
|
+
*
|
|
21
|
+
* This is the primary entry point for all review operations.
|
|
22
|
+
* It orchestrates parsing, analysis, agent execution, and
|
|
23
|
+
* memory operations in a resilient pipeline that degrades
|
|
24
|
+
* gracefully when optional components fail.
|
|
25
|
+
*
|
|
26
|
+
* @param input - Complete review input with diff, config, and settings
|
|
27
|
+
* @returns ReviewResult with status, findings, and metadata
|
|
28
|
+
*/
|
|
29
|
+
export declare function reviewPipeline(input: ReviewInput): Promise<ReviewResult>;
|
|
30
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAC;AA4B1E;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAiK9E"}
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main review pipeline orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates the entire review flow:
|
|
5
|
+
* 1. Validate input
|
|
6
|
+
* 2. Parse and filter the diff
|
|
7
|
+
* 3. Detect tech stacks
|
|
8
|
+
* 4. Run static analysis tools
|
|
9
|
+
* 5. Search memory for past context
|
|
10
|
+
* 6. Execute the selected agent mode
|
|
11
|
+
* 7. Persist new observations to memory
|
|
12
|
+
* 8. Return the final result
|
|
13
|
+
*
|
|
14
|
+
* Each step degrades gracefully — if static analysis fails, or
|
|
15
|
+
* memory is unavailable, the pipeline continues with what it has.
|
|
16
|
+
*/
|
|
17
|
+
import { parseDiffFiles, filterIgnoredFiles, truncateDiff } from './utils/diff.js';
|
|
18
|
+
import { detectStacks } from './utils/stack-detect.js';
|
|
19
|
+
import { calculateTokenBudget } from './utils/token-budget.js';
|
|
20
|
+
import { runStaticAnalysis, formatStaticAnalysisContext } from './tools/runner.js';
|
|
21
|
+
import { searchMemoryForContext } from './memory/search.js';
|
|
22
|
+
import { persistReviewObservations } from './memory/persist.js';
|
|
23
|
+
import { buildStackHints } from './agents/prompts.js';
|
|
24
|
+
import { runSimpleReview } from './agents/simple.js';
|
|
25
|
+
import { runWorkflowReview } from './agents/workflow.js';
|
|
26
|
+
import { runConsensusReview } from './agents/consensus.js';
|
|
27
|
+
// ─── Validation ─────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Validate the review input for required fields.
|
|
30
|
+
* Throws descriptive errors for misconfiguration.
|
|
31
|
+
*/
|
|
32
|
+
function validateInput(input) {
|
|
33
|
+
if (!input.diff || input.diff.trim().length === 0) {
|
|
34
|
+
throw new Error('Review input must include a non-empty diff');
|
|
35
|
+
}
|
|
36
|
+
if (!input.apiKey) {
|
|
37
|
+
throw new Error('Review input must include an API key');
|
|
38
|
+
}
|
|
39
|
+
if (!input.provider) {
|
|
40
|
+
throw new Error('Review input must specify an LLM provider');
|
|
41
|
+
}
|
|
42
|
+
if (!input.model) {
|
|
43
|
+
throw new Error('Review input must specify a model');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ─── Pipeline ───────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Run the full review pipeline.
|
|
49
|
+
*
|
|
50
|
+
* This is the primary entry point for all review operations.
|
|
51
|
+
* It orchestrates parsing, analysis, agent execution, and
|
|
52
|
+
* memory operations in a resilient pipeline that degrades
|
|
53
|
+
* gracefully when optional components fail.
|
|
54
|
+
*
|
|
55
|
+
* @param input - Complete review input with diff, config, and settings
|
|
56
|
+
* @returns ReviewResult with status, findings, and metadata
|
|
57
|
+
*/
|
|
58
|
+
export async function reviewPipeline(input) {
|
|
59
|
+
const startTime = Date.now();
|
|
60
|
+
const emit = input.onProgress ?? (() => { });
|
|
61
|
+
// ── Step 1: Validate ───────────────────────────────────────
|
|
62
|
+
validateInput(input);
|
|
63
|
+
emit({ step: 'validate', message: 'Input validated' });
|
|
64
|
+
// ── Step 2: Parse and filter the diff ──────────────────────
|
|
65
|
+
const allFiles = parseDiffFiles(input.diff);
|
|
66
|
+
const filteredFiles = filterIgnoredFiles(allFiles, input.settings.ignorePatterns);
|
|
67
|
+
emit({
|
|
68
|
+
step: 'parse-diff',
|
|
69
|
+
message: `Parsed ${allFiles.length} files from diff, ${filteredFiles.length} after filtering`,
|
|
70
|
+
detail: filteredFiles.map((f) => ` ${f.path}`).join('\n'),
|
|
71
|
+
});
|
|
72
|
+
// If all files were filtered out, skip the review
|
|
73
|
+
if (filteredFiles.length === 0) {
|
|
74
|
+
return createSkippedResult(input, startTime);
|
|
75
|
+
}
|
|
76
|
+
// Reconstruct filtered diff and get file list
|
|
77
|
+
const filteredDiff = filteredFiles.map((f) => f.content).join('\n');
|
|
78
|
+
const fileList = filteredFiles.map((f) => f.path);
|
|
79
|
+
// ── Step 3: Detect tech stacks ─────────────────────────────
|
|
80
|
+
const stacks = detectStacks(fileList);
|
|
81
|
+
const stackHints = buildStackHints(stacks);
|
|
82
|
+
emit({
|
|
83
|
+
step: 'detect-stacks',
|
|
84
|
+
message: `Detected ${stacks.length} tech stack(s)`,
|
|
85
|
+
detail: stacks.length > 0 ? stacks.map((s) => ` ${s}`).join('\n') : ' (none detected)',
|
|
86
|
+
});
|
|
87
|
+
// ── Step 4: Truncate diff to fit token budget ──────────────
|
|
88
|
+
const { diffBudget } = calculateTokenBudget(input.model);
|
|
89
|
+
const { truncated: truncatedDiff } = truncateDiff(filteredDiff, diffBudget);
|
|
90
|
+
emit({
|
|
91
|
+
step: 'token-budget',
|
|
92
|
+
message: `Token budget: ${diffBudget.toLocaleString()} tokens for diff`,
|
|
93
|
+
});
|
|
94
|
+
// ── Step 5: Run static analysis (in parallel with memory) ──
|
|
95
|
+
emit({ step: 'static-analysis', message: 'Running static analysis & memory search...' });
|
|
96
|
+
const [staticResult, memoryContext] = await Promise.all([
|
|
97
|
+
runStaticAnalysisSafe(fileList, input),
|
|
98
|
+
searchMemorySafe(input, fileList),
|
|
99
|
+
]);
|
|
100
|
+
const staticContext = formatStaticAnalysisContext(staticResult);
|
|
101
|
+
{
|
|
102
|
+
const toolsSummary = Object.entries(staticResult)
|
|
103
|
+
.map(([name, result]) => ` ${name}: ${result.status} (${result.findings.length} findings)`)
|
|
104
|
+
.join('\n');
|
|
105
|
+
emit({
|
|
106
|
+
step: 'static-results',
|
|
107
|
+
message: 'Static analysis complete',
|
|
108
|
+
detail: toolsSummary + (memoryContext ? '\n memory: loaded' : '\n memory: disabled'),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// ── Step 6: Execute agent mode ─────────────────────────────
|
|
112
|
+
emit({ step: 'agent-start', message: `Running ${input.mode} agent...` });
|
|
113
|
+
let result;
|
|
114
|
+
switch (input.mode) {
|
|
115
|
+
case 'simple':
|
|
116
|
+
result = await runSimpleReview({
|
|
117
|
+
diff: truncatedDiff,
|
|
118
|
+
provider: input.provider,
|
|
119
|
+
model: input.model,
|
|
120
|
+
apiKey: input.apiKey,
|
|
121
|
+
staticContext,
|
|
122
|
+
memoryContext,
|
|
123
|
+
stackHints,
|
|
124
|
+
onProgress: input.onProgress,
|
|
125
|
+
});
|
|
126
|
+
break;
|
|
127
|
+
case 'workflow':
|
|
128
|
+
result = await runWorkflowReview({
|
|
129
|
+
diff: truncatedDiff,
|
|
130
|
+
provider: input.provider,
|
|
131
|
+
model: input.model,
|
|
132
|
+
apiKey: input.apiKey,
|
|
133
|
+
staticContext,
|
|
134
|
+
memoryContext,
|
|
135
|
+
stackHints,
|
|
136
|
+
onProgress: input.onProgress,
|
|
137
|
+
});
|
|
138
|
+
break;
|
|
139
|
+
case 'consensus':
|
|
140
|
+
// Consensus mode uses the primary model with different stances
|
|
141
|
+
// In production, the caller would configure multiple models via the context
|
|
142
|
+
result = await runConsensusReview({
|
|
143
|
+
diff: truncatedDiff,
|
|
144
|
+
models: [
|
|
145
|
+
{ provider: input.provider, model: input.model, apiKey: input.apiKey, stance: 'for' },
|
|
146
|
+
{ provider: input.provider, model: input.model, apiKey: input.apiKey, stance: 'against' },
|
|
147
|
+
{ provider: input.provider, model: input.model, apiKey: input.apiKey, stance: 'neutral' },
|
|
148
|
+
],
|
|
149
|
+
staticContext,
|
|
150
|
+
memoryContext,
|
|
151
|
+
stackHints,
|
|
152
|
+
onProgress: input.onProgress,
|
|
153
|
+
});
|
|
154
|
+
break;
|
|
155
|
+
default: {
|
|
156
|
+
const _exhaustive = input.mode;
|
|
157
|
+
throw new Error(`Unknown review mode: ${_exhaustive}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// ── Step 7: Merge static analysis into result ──────────────
|
|
161
|
+
result.staticAnalysis = staticResult;
|
|
162
|
+
result.memoryContext = memoryContext;
|
|
163
|
+
// Add static analysis findings to the result's findings array
|
|
164
|
+
const staticFindings = [
|
|
165
|
+
...staticResult.semgrep.findings,
|
|
166
|
+
...staticResult.trivy.findings,
|
|
167
|
+
...staticResult.cpd.findings,
|
|
168
|
+
];
|
|
169
|
+
result.findings = [...result.findings, ...staticFindings];
|
|
170
|
+
// Track which tools ran successfully
|
|
171
|
+
result.metadata.toolsRun = [];
|
|
172
|
+
result.metadata.toolsSkipped = [];
|
|
173
|
+
for (const [name, tool] of Object.entries(staticResult)) {
|
|
174
|
+
if (tool.status === 'success') {
|
|
175
|
+
result.metadata.toolsRun.push(name);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
result.metadata.toolsSkipped.push(name);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Update execution time to cover the full pipeline
|
|
182
|
+
result.metadata.executionTimeMs = Date.now() - startTime;
|
|
183
|
+
// ── Step 8: Persist to memory (fire-and-forget) ────────────
|
|
184
|
+
if (input.settings.enableMemory && input.db && input.context) {
|
|
185
|
+
// Don't await — memory persistence shouldn't block the response
|
|
186
|
+
persistReviewObservations(input.db, input.context.repoFullName, input.context.prNumber, result).catch((error) => {
|
|
187
|
+
console.warn('[ghagga] Memory persist failed (non-fatal):', error instanceof Error ? error.message : String(error));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Run static analysis with graceful degradation.
|
|
195
|
+
* Returns a result with all tools skipped if anything goes wrong.
|
|
196
|
+
*/
|
|
197
|
+
async function runStaticAnalysisSafe(fileList, input) {
|
|
198
|
+
try {
|
|
199
|
+
// Build a file map for static analysis (paths only, content from diff)
|
|
200
|
+
const files = new Map();
|
|
201
|
+
for (const path of fileList) {
|
|
202
|
+
files.set(path, ''); // Content is extracted from diff by the tool runner
|
|
203
|
+
}
|
|
204
|
+
return await runStaticAnalysis(files, '.', {
|
|
205
|
+
enableSemgrep: input.settings.enableSemgrep,
|
|
206
|
+
enableTrivy: input.settings.enableTrivy,
|
|
207
|
+
enableCpd: input.settings.enableCpd,
|
|
208
|
+
customRules: input.settings.customRules,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.warn('[ghagga] Static analysis failed (degrading gracefully):', error instanceof Error ? error.message : String(error));
|
|
213
|
+
const errorResult = {
|
|
214
|
+
status: 'error',
|
|
215
|
+
findings: [],
|
|
216
|
+
error: error instanceof Error ? error.message : String(error),
|
|
217
|
+
executionTimeMs: 0,
|
|
218
|
+
};
|
|
219
|
+
return {
|
|
220
|
+
semgrep: errorResult,
|
|
221
|
+
trivy: errorResult,
|
|
222
|
+
cpd: errorResult,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Search memory with graceful degradation.
|
|
228
|
+
* Returns null if memory is disabled or unavailable.
|
|
229
|
+
*/
|
|
230
|
+
async function searchMemorySafe(input, fileList) {
|
|
231
|
+
if (!input.settings.enableMemory || !input.db || !input.context) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
return await searchMemoryForContext(input.db, input.context.repoFullName, fileList);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.warn('[ghagga] Memory search failed (degrading gracefully):', error instanceof Error ? error.message : String(error));
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Create a SKIPPED result when all files are filtered out.
|
|
244
|
+
*/
|
|
245
|
+
function createSkippedResult(input, startTime) {
|
|
246
|
+
return {
|
|
247
|
+
status: 'SKIPPED',
|
|
248
|
+
summary: 'All files in the diff matched ignore patterns. No review was performed.',
|
|
249
|
+
findings: [],
|
|
250
|
+
staticAnalysis: {
|
|
251
|
+
semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
252
|
+
trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
253
|
+
cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
254
|
+
},
|
|
255
|
+
memoryContext: null,
|
|
256
|
+
metadata: {
|
|
257
|
+
mode: input.mode,
|
|
258
|
+
provider: input.provider,
|
|
259
|
+
model: input.model,
|
|
260
|
+
tokensUsed: 0,
|
|
261
|
+
executionTimeMs: Date.now() - startTime,
|
|
262
|
+
toolsRun: [],
|
|
263
|
+
toolsSkipped: ['semgrep', 'trivy', 'cpd'],
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider fallback chain.
|
|
3
|
+
*
|
|
4
|
+
* Tries each provider/model pair in order. If one fails with a
|
|
5
|
+
* server error (5xx) or times out, it moves to the next provider.
|
|
6
|
+
* Client errors (4xx) are NOT retried — they indicate misconfiguration.
|
|
7
|
+
*/
|
|
8
|
+
import type { LLMProvider } from '../types.js';
|
|
9
|
+
export interface FallbackProvider {
|
|
10
|
+
provider: LLMProvider;
|
|
11
|
+
model: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}
|
|
14
|
+
export interface FallbackOptions {
|
|
15
|
+
/** Ordered list of providers to try */
|
|
16
|
+
providers: FallbackProvider[];
|
|
17
|
+
/** System prompt */
|
|
18
|
+
system: string;
|
|
19
|
+
/** User prompt */
|
|
20
|
+
prompt: string;
|
|
21
|
+
/** Temperature for generation (default: 0.3 for review consistency) */
|
|
22
|
+
temperature?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface FallbackResult {
|
|
25
|
+
/** Generated text */
|
|
26
|
+
text: string;
|
|
27
|
+
/** Provider that succeeded */
|
|
28
|
+
provider: LLMProvider;
|
|
29
|
+
/** Model that succeeded */
|
|
30
|
+
model: string;
|
|
31
|
+
/** Approximate tokens used (prompt + completion) */
|
|
32
|
+
tokensUsed: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate text with automatic provider fallback.
|
|
36
|
+
*
|
|
37
|
+
* Tries each provider in order. On retryable errors (5xx, timeout,
|
|
38
|
+
* rate limit), moves to the next provider. Throws the last error
|
|
39
|
+
* if all providers fail.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Providers, system prompt, and user prompt
|
|
42
|
+
* @returns Generated text with metadata about which provider succeeded
|
|
43
|
+
* @throws The last encountered error if all providers fail
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateWithFallback(options: FallbackOptions): Promise<FallbackResult>;
|
|
46
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/providers/fallback.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAE9B,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAC;IAEf,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IAEf,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IAEb,8BAA8B;IAC9B,QAAQ,EAAE,WAAW,CAAC;IAEtB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IAEd,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;CACpB;AAkCD;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider fallback chain.
|
|
3
|
+
*
|
|
4
|
+
* Tries each provider/model pair in order. If one fails with a
|
|
5
|
+
* server error (5xx) or times out, it moves to the next provider.
|
|
6
|
+
* Client errors (4xx) are NOT retried — they indicate misconfiguration.
|
|
7
|
+
*/
|
|
8
|
+
import { generateText } from 'ai';
|
|
9
|
+
import { createModel } from './index.js';
|
|
10
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Check if an error is a retryable server-side error (5xx or timeout).
|
|
13
|
+
*/
|
|
14
|
+
function isRetryableError(error) {
|
|
15
|
+
if (error instanceof Error) {
|
|
16
|
+
const message = error.message.toLowerCase();
|
|
17
|
+
// Network/timeout errors
|
|
18
|
+
if (message.includes('timeout') || message.includes('econnreset') || message.includes('econnrefused')) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
// Check for HTTP status codes in error (provider SDKs often include them)
|
|
22
|
+
const statusMatch = /status[:\s]*(\d{3})/i.exec(message);
|
|
23
|
+
if (statusMatch) {
|
|
24
|
+
const status = parseInt(statusMatch[1], 10);
|
|
25
|
+
return status >= 500;
|
|
26
|
+
}
|
|
27
|
+
// Rate limit (429) — also retryable with a different provider
|
|
28
|
+
if (message.includes('rate limit') || message.includes('429')) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// ─── Main Function ──────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Generate text with automatic provider fallback.
|
|
37
|
+
*
|
|
38
|
+
* Tries each provider in order. On retryable errors (5xx, timeout,
|
|
39
|
+
* rate limit), moves to the next provider. Throws the last error
|
|
40
|
+
* if all providers fail.
|
|
41
|
+
*
|
|
42
|
+
* @param options - Providers, system prompt, and user prompt
|
|
43
|
+
* @returns Generated text with metadata about which provider succeeded
|
|
44
|
+
* @throws The last encountered error if all providers fail
|
|
45
|
+
*/
|
|
46
|
+
export async function generateWithFallback(options) {
|
|
47
|
+
const { providers, system, prompt, temperature = 0.3 } = options;
|
|
48
|
+
if (providers.length === 0) {
|
|
49
|
+
throw new Error('No providers configured for fallback chain');
|
|
50
|
+
}
|
|
51
|
+
let lastError;
|
|
52
|
+
for (const { provider, model, apiKey } of providers) {
|
|
53
|
+
try {
|
|
54
|
+
const languageModel = createModel(provider, model, apiKey);
|
|
55
|
+
const result = await generateText({
|
|
56
|
+
model: languageModel,
|
|
57
|
+
system,
|
|
58
|
+
prompt,
|
|
59
|
+
temperature,
|
|
60
|
+
});
|
|
61
|
+
// Calculate tokens used from the response
|
|
62
|
+
const tokensUsed = (result.usage?.promptTokens ?? 0) + (result.usage?.completionTokens ?? 0);
|
|
63
|
+
return {
|
|
64
|
+
text: result.text,
|
|
65
|
+
provider,
|
|
66
|
+
model,
|
|
67
|
+
tokensUsed,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
lastError = error;
|
|
72
|
+
if (isRetryableError(error)) {
|
|
73
|
+
// Log and continue to next provider
|
|
74
|
+
console.warn(`[ghagga] Provider ${provider}/${model} failed with retryable error, trying next...`, error instanceof Error ? error.message : String(error));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Non-retryable error (4xx, auth, etc.) — throw immediately
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// All providers failed with retryable errors
|
|
82
|
+
throw lastError;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../src/providers/fallback.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAuCzC,mEAAmE;AAEnE;;GAEG;AACH,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAE5C,yBAAyB;QACzB,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACtG,OAAO,IAAI,CAAC;QACd,CAAC;QAED,0EAA0E;QAC1E,MAAM,WAAW,GAAG,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;YAC7C,OAAO,MAAM,IAAI,GAAG,CAAC;QACvB,CAAC;QAED,8DAA8D;QAC9D,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mEAAmE;AAEnE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAwB;IAExB,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;IAEjE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,SAAkB,CAAC;IAEvB,KAAK,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;YAE3D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;gBAChC,KAAK,EAAE,aAAa;gBACpB,MAAM;gBACN,MAAM;gBACN,WAAW;aACZ,CAAC,CAAC;YAEH,0CAA0C;YAC1C,MAAM,UAAU,GACd,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,IAAI,CAAC,CAAC,CAAC;YAE5E,OAAO;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ;gBACR,KAAK;gBACL,UAAU;aACX,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAK,CAAC;YAElB,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,oCAAoC;gBACpC,OAAO,CAAC,IAAI,CACV,qBAAqB,QAAQ,IAAI,KAAK,8CAA8C,EACpF,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;gBACF,SAAS;YACX,CAAC;YAED,4DAA4D;YAC5D,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,MAAM,SAAS,CAAC;AAClB,CAAC"}
|