llm-cli-gateway 1.0.1 → 1.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/dist/logger.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const noopLogger = {
2
2
  info: () => { },
3
3
  error: () => { },
4
- debug: () => { }
4
+ debug: () => { },
5
5
  };
package/dist/metrics.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { CLI_TYPES } from "./session-manager.js";
2
- const createEmptyMetrics = () => Object.fromEntries(CLI_TYPES.map(cli => [cli, { requestCount: 0, successCount: 0, failureCount: 0, totalResponseTimeMs: 0 }]));
2
+ const createEmptyMetrics = () => Object.fromEntries(CLI_TYPES.map(cli => [
3
+ cli,
4
+ { requestCount: 0, successCount: 0, failureCount: 0, totalResponseTimeMs: 0 },
5
+ ]));
3
6
  export class PerformanceMetrics {
4
7
  metrics = createEmptyMetrics();
5
8
  recordRequest(cli, durationMs, success) {
@@ -21,22 +24,16 @@ export class PerformanceMetrics {
21
24
  let totalFailures = 0;
22
25
  for (const cli of CLI_TYPES) {
23
26
  const metrics = this.metrics[cli];
24
- const averageResponseTimeMs = metrics.requestCount > 0
25
- ? metrics.totalResponseTimeMs / metrics.requestCount
26
- : 0;
27
- const successRate = metrics.requestCount > 0
28
- ? metrics.successCount / metrics.requestCount
29
- : 0;
30
- const failureRate = metrics.requestCount > 0
31
- ? metrics.failureCount / metrics.requestCount
32
- : 0;
27
+ const averageResponseTimeMs = metrics.requestCount > 0 ? metrics.totalResponseTimeMs / metrics.requestCount : 0;
28
+ const successRate = metrics.requestCount > 0 ? metrics.successCount / metrics.requestCount : 0;
29
+ const failureRate = metrics.requestCount > 0 ? metrics.failureCount / metrics.requestCount : 0;
33
30
  byTool[cli] = {
34
31
  requestCount: metrics.requestCount,
35
32
  successCount: metrics.successCount,
36
33
  failureCount: metrics.failureCount,
37
34
  averageResponseTimeMs,
38
35
  successRate,
39
- failureRate
36
+ failureRate,
40
37
  };
41
38
  totalRequests += metrics.requestCount;
42
39
  totalSuccesses += metrics.successCount;
@@ -51,7 +48,7 @@ export class PerformanceMetrics {
51
48
  overallSuccessRate,
52
49
  overallFailureRate,
53
50
  byTool,
54
- generatedAt: new Date().toISOString()
51
+ generatedAt: new Date().toISOString(),
55
52
  };
56
53
  }
57
54
  }
@@ -9,7 +9,7 @@ import { createDatabaseConnection } from "./db.js";
9
9
  const logger = {
10
10
  info: (message, meta) => console.error(`[INFO] ${message}`, meta || ""),
11
11
  error: (message, meta) => console.error(`[ERROR] ${message}`, meta || ""),
12
- debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || "")
12
+ debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || ""),
13
13
  };
14
14
  /**
15
15
  * Migrate sessions from file-based storage to PostgreSQL
@@ -18,7 +18,7 @@ export async function migrateFromFile(filePath, pgManager) {
18
18
  const result = {
19
19
  migrated: 0,
20
20
  failed: 0,
21
- errors: []
21
+ errors: [],
22
22
  };
23
23
  // Read file-based sessions
24
24
  let fileData;
@@ -7,29 +7,26 @@ const FALLBACK_INFO = {
7
7
  models: {
8
8
  opus: "Most capable model. Best for: complex reasoning, nuanced analysis, difficult problems, research",
9
9
  sonnet: "Balanced performance. Best for: everyday coding, code review, general tasks (default)",
10
- haiku: "Fastest model. Best for: simple queries, quick answers, high-volume tasks, cost-sensitive use"
10
+ haiku: "Fastest model. Best for: simple queries, quick answers, high-volume tasks, cost-sensitive use",
11
11
  },
12
12
  defaultModel: "sonnet",
13
- modelOrder: ["opus", "sonnet", "haiku"]
13
+ modelOrder: ["opus", "sonnet", "haiku"],
14
14
  },
15
15
  codex: {
16
16
  description: "OpenAI's Codex CLI - best for code execution in sandboxed environments",
17
17
  models: {
18
- "gpt-5.4": "Frontier coding and professional-work model. Best for: most Codex tasks, long-running agentic work",
19
- "gpt-5.3-codex": "Specialized Codex model. Best for: agentic coding workflows with Codex-tuned behavior",
20
- "gpt-5.2": "Strong general-purpose GPT-5 model. Best for: broad coding and reasoning tasks",
21
- "gpt-5-pro": "Highest-capability GPT-5 model. Best for: deep reasoning and difficult professional workflows"
18
+ o3: "Most capable reasoning model. Best for: complex multi-step problems, math, science",
19
+ "o4-mini": "Fast reasoning model. Best for: coding tasks, quick iterations",
20
+ "gpt-4.1": "Latest GPT-4 variant. Best for: general coding, instruction following",
22
21
  },
23
- defaultModel: "gpt-5.4",
24
- modelOrder: ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2", "gpt-5-pro"]
25
22
  },
26
23
  gemini: {
27
24
  description: "Google's Gemini CLI - best for multimodal tasks and Google ecosystem integration",
28
25
  models: {
29
26
  "gemini-2.5-pro": "Most capable model. Best for: complex reasoning, long context, multimodal",
30
- "gemini-2.5-flash": "Fast model. Best for: quick responses, high throughput, cost-sensitive use"
31
- }
32
- }
27
+ "gemini-2.5-flash": "Fast model. Best for: quick responses, high throughput, cost-sensitive use",
28
+ },
29
+ },
33
30
  };
34
31
  const MODEL_CACHE_TTL_MS = 2 * 60 * 1000;
35
32
  let cachedInfo = null;
@@ -66,7 +63,7 @@ function buildCliInfo() {
66
63
  const info = {
67
64
  claude: cloneInfo(FALLBACK_INFO.claude),
68
65
  codex: cloneInfo(FALLBACK_INFO.codex),
69
- gemini: cloneInfo(FALLBACK_INFO.gemini)
66
+ gemini: cloneInfo(FALLBACK_INFO.gemini),
70
67
  };
71
68
  applyClaudeOverrides(info.claude);
72
69
  applyCodexOverrides(info.codex);
@@ -78,7 +75,7 @@ function cloneInfo(source) {
78
75
  description: source.description,
79
76
  models: { ...source.models },
80
77
  defaultModel: source.defaultModel,
81
- modelOrder: source.modelOrder ? [...source.modelOrder] : undefined
78
+ modelOrder: source.modelOrder ? [...source.modelOrder] : undefined,
82
79
  };
83
80
  }
84
81
  function applyClaudeOverrides(info) {
@@ -273,7 +270,8 @@ function collectGeminiModels() {
273
270
  .map(([name]) => name);
274
271
  const describedModels = {};
275
272
  order.forEach(model => {
276
- describedModels[model] = `Observed in local Gemini sessions (last seen ${formatDate(models[model].lastSeen)})`;
273
+ describedModels[model] =
274
+ `Observed in local Gemini sessions (last seen ${formatDate(models[model].lastSeen)})`;
277
275
  });
278
276
  return { models: describedModels, order };
279
277
  }
package/dist/optimizer.js CHANGED
@@ -5,18 +5,18 @@ const COURTESY_PATTERNS = [
5
5
  /\bI would like\b\s*/gi,
6
6
  /\bI need you to\b\s*/gi,
7
7
  /\bI just implemented\b\s*/gi,
8
- /\bPlease do the following:?\s*/gi
8
+ /\bPlease do the following:?\s*/gi,
9
9
  ];
10
10
  const ADJECTIVE_PATTERNS = [
11
11
  /\bcomprehensive\b/gi,
12
12
  /\bdetailed\b/gi,
13
13
  /\bthorough\b/gi,
14
14
  /\boverall\b/gi,
15
- /\bcritical\b/gi
15
+ /\bcritical\b/gi,
16
16
  ];
17
17
  const TASK_PREFIXES = [
18
18
  /^(First|Then|After that|Finally),?\s*/i,
19
- /^(First|Then|After that|Finally),?\s*you should\s*/i
19
+ /^(First|Then|After that|Finally),?\s*you should\s*/i,
20
20
  ];
21
21
  export function estimateTokens(text) {
22
22
  const trimmed = text.trim();
@@ -49,15 +49,15 @@ function optimizeText(text, mode) {
49
49
  function optimizeSegment(segment, mode) {
50
50
  const inlineParts = segment.split(/(`[^`]*`)/g);
51
51
  return inlineParts
52
- .map((part) => (part.startsWith("`") ? part : optimizePlain(part, mode)))
52
+ .map(part => (part.startsWith("`") ? part : optimizePlain(part, mode)))
53
53
  .join("");
54
54
  }
55
55
  function optimizePlain(text, mode) {
56
56
  let output = text;
57
- COURTESY_PATTERNS.forEach((pattern) => {
57
+ COURTESY_PATTERNS.forEach(pattern => {
58
58
  output = output.replace(pattern, "");
59
59
  });
60
- ADJECTIVE_PATTERNS.forEach((pattern) => {
60
+ ADJECTIVE_PATTERNS.forEach(pattern => {
61
61
  output = output.replace(pattern, "");
62
62
  });
63
63
  output = output.replace(/\bfound in the [^:.\n]+/gi, "");
@@ -147,7 +147,7 @@ function compressTaskLists(text) {
147
147
  }
148
148
  function cleanTaskItem(item) {
149
149
  let cleaned = item.trim();
150
- TASK_PREFIXES.forEach((pattern) => {
150
+ TASK_PREFIXES.forEach(pattern => {
151
151
  cleaned = cleaned.replace(pattern, "");
152
152
  });
153
153
  cleaned = cleaned.replace(/^you should\s*/i, "");
@@ -157,7 +157,7 @@ function cleanTaskItem(item) {
157
157
  }
158
158
  function applyArrowNotation(text) {
159
159
  const lines = text.split("\n");
160
- const output = lines.map((line) => {
160
+ const output = lines.map(line => {
161
161
  let updated = line;
162
162
  updated = updated.replace(/\bChange\s+(?:the\s+)?([A-Za-z][\w-]*)\s+to\s+(?:a\s+|an\s+)?([A-Za-z][\w-]*)([.!?]|$)/gi, (_m, from, to, end) => {
163
163
  return `${from.trim()} → ${to.trim()}${end || ""}`;
@@ -172,7 +172,7 @@ function applyArrowNotation(text) {
172
172
  }
173
173
  function applySlashNotation(text) {
174
174
  const lines = text.split("\n");
175
- const output = lines.map((line) => {
175
+ const output = lines.map(line => {
176
176
  const trimmed = line.trim();
177
177
  if (trimmed.length < 50 && /^[A-Za-z0-9][A-Za-z0-9\s/&-]+$/.test(trimmed)) {
178
178
  return line.replace(/\s+and\s+/, "/");
@@ -68,7 +68,14 @@ export class ProcessMonitor {
68
68
  }
69
69
  catch (err) {
70
70
  if (err.code === "ESRCH") {
71
- return { pid, alive: false, state: null, cpuPercent: null, memoryRssKb: null, sampledAt: now };
71
+ return {
72
+ pid,
73
+ alive: false,
74
+ state: null,
75
+ cpuPercent: null,
76
+ memoryRssKb: null,
77
+ sampledAt: now,
78
+ };
72
79
  }
73
80
  // EPERM = process exists but we can't signal it
74
81
  if (err.code === "EPERM") {
@@ -87,7 +94,7 @@ export class ProcessMonitor {
87
94
  const totalJiffies = getTotalCpuJiffies();
88
95
  const prev = this.prevSamples.get(pid);
89
96
  if (prev && totalJiffies !== null) {
90
- const processJiffiesDelta = (parsed.utime + parsed.stime) - (prev.utime + prev.stime);
97
+ const processJiffiesDelta = parsed.utime + parsed.stime - (prev.utime + prev.stime);
91
98
  const totalJiffiesDelta = totalJiffies - prev.totalJiffies;
92
99
  if (totalJiffiesDelta > 0) {
93
100
  cpuPercent = (processJiffiesDelta / totalJiffiesDelta) * 100;
@@ -96,8 +103,10 @@ export class ProcessMonitor {
96
103
  // Store for next delta
97
104
  if (totalJiffies !== null) {
98
105
  this.prevSamples.set(pid, {
99
- utime: parsed.utime, stime: parsed.stime,
100
- totalJiffies, timestamp: Date.now()
106
+ utime: parsed.utime,
107
+ stime: parsed.stime,
108
+ totalJiffies,
109
+ timestamp: Date.now(),
101
110
  });
102
111
  }
103
112
  }
@@ -121,17 +130,24 @@ export class ProcessMonitor {
121
130
  const runningForMs = Date.now() - new Date(job.startedAt).getTime();
122
131
  if (!job.pid) {
123
132
  return {
124
- jobId: job.jobId, cli: job.cli, status: job.status,
125
- processHealth: null, isDead: false, isZombie: false, runningForMs
133
+ jobId: job.jobId,
134
+ cli: job.cli,
135
+ status: job.status,
136
+ processHealth: null,
137
+ isDead: false,
138
+ isZombie: false,
139
+ runningForMs,
126
140
  };
127
141
  }
128
142
  const health = this.sampleProcess(job.pid);
129
143
  return {
130
- jobId: job.jobId, cli: job.cli, status: job.status,
144
+ jobId: job.jobId,
145
+ cli: job.cli,
146
+ status: job.status,
131
147
  processHealth: health,
132
148
  isDead: job.status === "running" && !health.alive,
133
149
  isZombie: job.status === "running" && health.state === "Z",
134
- runningForMs
150
+ runningForMs,
135
151
  };
136
152
  });
137
153
  }
@@ -18,6 +18,13 @@ export declare function validateSessionId(sessionId: string): void;
18
18
  * Pure function: determine --resume args and session provenance from request flags.
19
19
  * Does NOT perform any session I/O — callers handle create/update separately.
20
20
  */
21
+ /**
22
+ * Reject CLI arg values that start with "-" to prevent argument injection.
23
+ * spawn() doesn't invoke a shell so there's no shell injection, but a value
24
+ * like "--dangerously-skip-permissions" passed as a tool name would be
25
+ * interpreted as a flag by the child CLI.
26
+ */
27
+ export declare function sanitizeCliArgValues(values: string[], fieldName: string): string[];
21
28
  export declare function resolveSessionResumeArgs(opts: {
22
29
  sessionId?: string;
23
30
  resumeLatest?: boolean;
@@ -17,16 +17,38 @@ export function validateSessionId(sessionId) {
17
17
  * Pure function: determine --resume args and session provenance from request flags.
18
18
  * Does NOT perform any session I/O — callers handle create/update separately.
19
19
  */
20
+ /**
21
+ * Reject CLI arg values that start with "-" to prevent argument injection.
22
+ * spawn() doesn't invoke a shell so there's no shell injection, but a value
23
+ * like "--dangerously-skip-permissions" passed as a tool name would be
24
+ * interpreted as a flag by the child CLI.
25
+ */
26
+ export function sanitizeCliArgValues(values, fieldName) {
27
+ for (const v of values) {
28
+ if (v.startsWith("-")) {
29
+ throw new Error(`Invalid ${fieldName} value "${v}": values must not start with "-" (argument injection prevention)`);
30
+ }
31
+ }
32
+ return values;
33
+ }
20
34
  export function resolveSessionResumeArgs(opts) {
21
35
  if (opts.createNewSession) {
22
36
  return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
23
37
  }
24
38
  if (opts.resumeLatest && !opts.sessionId) {
25
- return { resumeArgs: ["--resume", "latest"], effectiveSessionId: undefined, userProvidedSession: false };
39
+ return {
40
+ resumeArgs: ["--resume", "latest"],
41
+ effectiveSessionId: undefined,
42
+ userProvidedSession: false,
43
+ };
26
44
  }
27
45
  if (opts.sessionId) {
28
46
  validateSessionId(opts.sessionId);
29
- return { resumeArgs: ["--resume", opts.sessionId], effectiveSessionId: opts.sessionId, userProvidedSession: true };
47
+ return {
48
+ resumeArgs: ["--resume", opts.sessionId],
49
+ effectiveSessionId: opts.sessionId,
50
+ userProvidedSession: true,
51
+ };
30
52
  }
31
53
  return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
32
54
  }
package/dist/resources.js CHANGED
@@ -18,8 +18,8 @@ export class ResourceProvider {
18
18
  annotations: {
19
19
  audience: ["user", "assistant"],
20
20
  priority: 0.7,
21
- lastModified: new Date().toISOString()
22
- }
21
+ lastModified: new Date().toISOString(),
22
+ },
23
23
  },
24
24
  {
25
25
  uri: "sessions://claude",
@@ -29,8 +29,8 @@ export class ResourceProvider {
29
29
  mimeType: "application/json",
30
30
  annotations: {
31
31
  audience: ["user", "assistant"],
32
- priority: 0.6
33
- }
32
+ priority: 0.6,
33
+ },
34
34
  },
35
35
  {
36
36
  uri: "sessions://codex",
@@ -40,8 +40,8 @@ export class ResourceProvider {
40
40
  mimeType: "application/json",
41
41
  annotations: {
42
42
  audience: ["user", "assistant"],
43
- priority: 0.6
44
- }
43
+ priority: 0.6,
44
+ },
45
45
  },
46
46
  {
47
47
  uri: "sessions://gemini",
@@ -51,8 +51,8 @@ export class ResourceProvider {
51
51
  mimeType: "application/json",
52
52
  annotations: {
53
53
  audience: ["user", "assistant"],
54
- priority: 0.6
55
- }
54
+ priority: 0.6,
55
+ },
56
56
  },
57
57
  {
58
58
  uri: "models://claude",
@@ -62,8 +62,8 @@ export class ResourceProvider {
62
62
  mimeType: "application/json",
63
63
  annotations: {
64
64
  audience: ["user", "assistant"],
65
- priority: 0.8
66
- }
65
+ priority: 0.8,
66
+ },
67
67
  },
68
68
  {
69
69
  uri: "models://codex",
@@ -73,8 +73,8 @@ export class ResourceProvider {
73
73
  mimeType: "application/json",
74
74
  annotations: {
75
75
  audience: ["user", "assistant"],
76
- priority: 0.8
77
- }
76
+ priority: 0.8,
77
+ },
78
78
  },
79
79
  {
80
80
  uri: "models://gemini",
@@ -84,8 +84,8 @@ export class ResourceProvider {
84
84
  mimeType: "application/json",
85
85
  annotations: {
86
86
  audience: ["user", "assistant"],
87
- priority: 0.8
88
- }
87
+ priority: 0.8,
88
+ },
89
89
  },
90
90
  {
91
91
  uri: "metrics://performance",
@@ -95,9 +95,9 @@ export class ResourceProvider {
95
95
  mimeType: "application/json",
96
96
  annotations: {
97
97
  audience: ["user", "assistant"],
98
- priority: 0.9
99
- }
100
- }
98
+ priority: 0.9,
99
+ },
100
+ },
101
101
  ];
102
102
  }
103
103
  // Read a specific resource by URI
@@ -110,19 +110,19 @@ export class ResourceProvider {
110
110
  mimeType: "application/json",
111
111
  text: JSON.stringify({
112
112
  total: sessions.length,
113
- sessions: sessions.map((s) => ({
113
+ sessions: sessions.map(s => ({
114
114
  id: s.id,
115
115
  cli: s.cli,
116
116
  description: s.description,
117
117
  createdAt: s.createdAt,
118
- lastUsedAt: s.lastUsedAt
118
+ lastUsedAt: s.lastUsedAt,
119
119
  })),
120
120
  activeSessions: {
121
121
  claude: (await this.sessionManager.getActiveSession("claude"))?.id || null,
122
122
  codex: (await this.sessionManager.getActiveSession("codex"))?.id || null,
123
- gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null
124
- }
125
- }, null, 2)
123
+ gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
124
+ },
125
+ }, null, 2),
126
126
  };
127
127
  }
128
128
  if (uri === "sessions://claude") {
@@ -134,8 +134,8 @@ export class ResourceProvider {
134
134
  cli: "claude",
135
135
  total: sessions.length,
136
136
  sessions,
137
- activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null
138
- }, null, 2)
137
+ activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null,
138
+ }, null, 2),
139
139
  };
140
140
  }
141
141
  if (uri === "sessions://codex") {
@@ -147,8 +147,8 @@ export class ResourceProvider {
147
147
  cli: "codex",
148
148
  total: sessions.length,
149
149
  sessions,
150
- activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null
151
- }, null, 2)
150
+ activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null,
151
+ }, null, 2),
152
152
  };
153
153
  }
154
154
  if (uri === "sessions://gemini") {
@@ -160,8 +160,8 @@ export class ResourceProvider {
160
160
  cli: "gemini",
161
161
  total: sessions.length,
162
162
  sessions,
163
- activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null
164
- }, null, 2)
163
+ activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
164
+ }, null, 2),
165
165
  };
166
166
  }
167
167
  // Model capability resources
@@ -170,7 +170,7 @@ export class ResourceProvider {
170
170
  return {
171
171
  uri,
172
172
  mimeType: "application/json",
173
- text: JSON.stringify(cliInfo.claude, null, 2)
173
+ text: JSON.stringify(cliInfo.claude, null, 2),
174
174
  };
175
175
  }
176
176
  if (uri === "models://codex") {
@@ -178,7 +178,7 @@ export class ResourceProvider {
178
178
  return {
179
179
  uri,
180
180
  mimeType: "application/json",
181
- text: JSON.stringify(cliInfo.codex, null, 2)
181
+ text: JSON.stringify(cliInfo.codex, null, 2),
182
182
  };
183
183
  }
184
184
  if (uri === "models://gemini") {
@@ -186,14 +186,14 @@ export class ResourceProvider {
186
186
  return {
187
187
  uri,
188
188
  mimeType: "application/json",
189
- text: JSON.stringify(cliInfo.gemini, null, 2)
189
+ text: JSON.stringify(cliInfo.gemini, null, 2),
190
190
  };
191
191
  }
192
192
  if (uri === "metrics://performance") {
193
193
  return {
194
194
  uri,
195
195
  mimeType: "application/json",
196
- text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2)
196
+ text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2),
197
197
  };
198
198
  }
199
199
  return null;
package/dist/retry.js CHANGED
@@ -27,15 +27,17 @@ const isDefaultTransient = (error) => {
27
27
  return false;
28
28
  }
29
29
  // Shell command-related errors
30
- if (error.code === 124) { // wall-clock timeout (explicit, caller-set) — transient
30
+ if (error.code === 124) {
31
+ // wall-clock timeout (explicit, caller-set) — transient
31
32
  return true;
32
33
  }
33
34
  // Note: exit code 125 = idle timeout (stuck process) — intentionally non-transient
34
- if (error.code === 'ENOENT') { // command not found
35
+ if (error.code === "ENOENT") {
36
+ // command not found
35
37
  return false;
36
38
  }
37
39
  // Node.js network errors
38
- const transientErrorCodes = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'EPIPE'];
40
+ const transientErrorCodes = ["ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "EPIPE"];
39
41
  if (transientErrorCodes.includes(error.code)) {
40
42
  return true;
41
43
  }
@@ -142,5 +144,5 @@ export async function withRetry(operation, circuitBreaker, retryOptions, logger)
142
144
  await new Promise(resolve => setTimeout(resolve, delay));
143
145
  }
144
146
  }
145
- throw new Error('[Retry] Operation failed after all retry attempts.');
147
+ throw new Error("[Retry] Operation failed after all retry attempts.");
146
148
  }
@@ -1,17 +1,6 @@
1
- /**
2
- * Review Integrity Bypass Detection
3
- *
4
- * Detects when orchestrating agents neuter the multi-LLM review process by:
5
- * - Embedding tool-suppression language in review prompts
6
- * - Inlining full code instead of letting reviewers read files directly
7
- * - Setting allowedTools:[] to strip tool access from reviewers
8
- *
9
- * Two-gate design: violations only emitted when BOTH review context AND
10
- * a restriction are detected. This avoids false positives on non-review
11
- * prompts that happen to contain similar language.
12
- */
1
+ export type ReviewIntegrityViolationType = "empty_allowed_tools" | "critical_tools_disallowed" | "tool_suppression";
13
2
  export interface ReviewIntegrityViolation {
14
- type: "tool_suppression" | "inlined_code" | "empty_allowed_tools" | "critical_tools_disallowed";
3
+ type: ReviewIntegrityViolationType;
15
4
  score: number;
16
5
  detail: string;
17
6
  }
@@ -20,31 +9,10 @@ export interface ReviewIntegrityResult {
20
9
  violations: ReviewIntegrityViolation[];
21
10
  totalScore: number;
22
11
  }
23
- /**
24
- * Detect whether the prompt is a review/audit context.
25
- * Uses two-part detection: unambiguous phrases match alone,
26
- * ambiguous verbs (review, analyze, etc.) require a code anchor.
27
- * Normalizes Unicode before matching to prevent confusable bypasses.
28
- */
29
- export declare function isReviewContext(prompt: string): boolean;
30
- /**
31
- * Detect tool-suppression language in a prompt.
32
- * Returns the matched patterns for diagnostics.
33
- */
34
- export declare function detectToolSuppression(prompt: string): string[];
35
- /**
36
- * Detect inlined code blocks that look like full file dumps.
37
- * Two detection strategies:
38
- * 1. Any single code block with 200+ chars is flagged.
39
- * 2. Fallback: if total chars across ALL code blocks (even small ones)
40
- * exceeds 1000, flag to catch split-block bypass attempts.
41
- */
42
- export declare function detectInlinedCode(prompt: string): {
43
- count: number;
44
- totalChars: number;
45
- };
46
- export declare function checkReviewIntegrity(params: {
12
+ export interface ReviewIntegrityInput {
47
13
  prompt: string;
48
14
  allowedTools?: string[];
49
15
  disallowedTools?: string[];
50
- }): ReviewIntegrityResult;
16
+ }
17
+ export declare function isReviewContext(prompt: string): boolean;
18
+ export declare function checkReviewIntegrity(input: ReviewIntegrityInput): ReviewIntegrityResult;