llm-cli-gateway 1.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/CHANGELOG.md +541 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/approval-manager.d.ts +43 -0
- package/dist/approval-manager.js +156 -0
- package/dist/async-job-manager.d.ts +57 -0
- package/dist/async-job-manager.js +334 -0
- package/dist/claude-mcp-config.d.ts +8 -0
- package/dist/claude-mcp-config.js +161 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +56 -0
- package/dist/db.d.ts +48 -0
- package/dist/db.js +170 -0
- package/dist/executor.d.ts +30 -0
- package/dist/executor.js +315 -0
- package/dist/health.d.ts +20 -0
- package/dist/health.js +32 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +1503 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +5 -0
- package/dist/metrics.d.ts +23 -0
- package/dist/metrics.js +57 -0
- package/dist/migrate-sessions.d.ts +12 -0
- package/dist/migrate-sessions.js +145 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +100 -0
- package/dist/model-registry.d.ts +10 -0
- package/dist/model-registry.js +346 -0
- package/dist/optimizer.d.ts +3 -0
- package/dist/optimizer.js +183 -0
- package/dist/process-monitor.d.ts +54 -0
- package/dist/process-monitor.js +146 -0
- package/dist/request-helpers.d.ts +25 -0
- package/dist/request-helpers.js +32 -0
- package/dist/resources.d.ts +26 -0
- package/dist/resources.js +201 -0
- package/dist/retry.d.ts +72 -0
- package/dist/retry.js +146 -0
- package/dist/review-integrity.d.ts +50 -0
- package/dist/review-integrity.js +283 -0
- package/dist/session-manager-pg.d.ts +76 -0
- package/dist/session-manager-pg.js +383 -0
- package/dist/session-manager.d.ts +62 -0
- package/dist/session-manager.js +223 -0
- package/dist/stream-json-parser.d.ts +35 -0
- package/dist/stream-json-parser.js +94 -0
- package/package.json +90 -0
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type CliType } from "./session-manager.js";
|
|
2
|
+
export interface ToolMetricsSnapshot {
|
|
3
|
+
requestCount: number;
|
|
4
|
+
successCount: number;
|
|
5
|
+
failureCount: number;
|
|
6
|
+
averageResponseTimeMs: number;
|
|
7
|
+
successRate: number;
|
|
8
|
+
failureRate: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PerformanceMetricsSnapshot {
|
|
11
|
+
totalRequests: number;
|
|
12
|
+
totalSuccesses: number;
|
|
13
|
+
totalFailures: number;
|
|
14
|
+
overallSuccessRate: number;
|
|
15
|
+
overallFailureRate: number;
|
|
16
|
+
byTool: Record<CliType, ToolMetricsSnapshot>;
|
|
17
|
+
generatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class PerformanceMetrics {
|
|
20
|
+
private metrics;
|
|
21
|
+
recordRequest(cli: CliType, durationMs: number, success: boolean): void;
|
|
22
|
+
snapshot(): PerformanceMetricsSnapshot;
|
|
23
|
+
}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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 }]));
|
|
3
|
+
export class PerformanceMetrics {
|
|
4
|
+
metrics = createEmptyMetrics();
|
|
5
|
+
recordRequest(cli, durationMs, success) {
|
|
6
|
+
const metrics = this.metrics[cli];
|
|
7
|
+
metrics.requestCount += 1;
|
|
8
|
+
const normalizedDurationMs = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
|
|
9
|
+
metrics.totalResponseTimeMs += normalizedDurationMs;
|
|
10
|
+
if (success) {
|
|
11
|
+
metrics.successCount += 1;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
metrics.failureCount += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
snapshot() {
|
|
18
|
+
const byTool = {};
|
|
19
|
+
let totalRequests = 0;
|
|
20
|
+
let totalSuccesses = 0;
|
|
21
|
+
let totalFailures = 0;
|
|
22
|
+
for (const cli of CLI_TYPES) {
|
|
23
|
+
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;
|
|
33
|
+
byTool[cli] = {
|
|
34
|
+
requestCount: metrics.requestCount,
|
|
35
|
+
successCount: metrics.successCount,
|
|
36
|
+
failureCount: metrics.failureCount,
|
|
37
|
+
averageResponseTimeMs,
|
|
38
|
+
successRate,
|
|
39
|
+
failureRate
|
|
40
|
+
};
|
|
41
|
+
totalRequests += metrics.requestCount;
|
|
42
|
+
totalSuccesses += metrics.successCount;
|
|
43
|
+
totalFailures += metrics.failureCount;
|
|
44
|
+
}
|
|
45
|
+
const overallSuccessRate = totalRequests > 0 ? totalSuccesses / totalRequests : 0;
|
|
46
|
+
const overallFailureRate = totalRequests > 0 ? totalFailures / totalRequests : 0;
|
|
47
|
+
return {
|
|
48
|
+
totalRequests,
|
|
49
|
+
totalSuccesses,
|
|
50
|
+
totalFailures,
|
|
51
|
+
overallSuccessRate,
|
|
52
|
+
overallFailureRate,
|
|
53
|
+
byTool,
|
|
54
|
+
generatedAt: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { PostgreSQLSessionManager } from "./session-manager-pg.js";
|
|
3
|
+
interface MigrationResult {
|
|
4
|
+
migrated: number;
|
|
5
|
+
failed: number;
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Migrate sessions from file-based storage to PostgreSQL
|
|
10
|
+
*/
|
|
11
|
+
export declare function migrateFromFile(filePath: string, pgManager: PostgreSQLSessionManager): Promise<MigrationResult>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { PostgreSQLSessionManager } from "./session-manager-pg.js";
|
|
6
|
+
import { loadConfig } from "./config.js";
|
|
7
|
+
import { createDatabaseConnection } from "./db.js";
|
|
8
|
+
// Simple console logger for migration script
|
|
9
|
+
const logger = {
|
|
10
|
+
info: (message, meta) => console.error(`[INFO] ${message}`, meta || ""),
|
|
11
|
+
error: (message, meta) => console.error(`[ERROR] ${message}`, meta || ""),
|
|
12
|
+
debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || "")
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Migrate sessions from file-based storage to PostgreSQL
|
|
16
|
+
*/
|
|
17
|
+
export async function migrateFromFile(filePath, pgManager) {
|
|
18
|
+
const result = {
|
|
19
|
+
migrated: 0,
|
|
20
|
+
failed: 0,
|
|
21
|
+
errors: []
|
|
22
|
+
};
|
|
23
|
+
// Read file-based sessions
|
|
24
|
+
let fileData;
|
|
25
|
+
try {
|
|
26
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
27
|
+
fileData = JSON.parse(fileContent);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Failed to read sessions file: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
+
}
|
|
32
|
+
console.error(`Found ${Object.keys(fileData.sessions).length} sessions to migrate`);
|
|
33
|
+
// Migrate sessions
|
|
34
|
+
for (const [id, session] of Object.entries(fileData.sessions)) {
|
|
35
|
+
try {
|
|
36
|
+
await pgManager.createSession(session.cli, session.description, session.id);
|
|
37
|
+
// Migrate metadata if present
|
|
38
|
+
if (session.metadata) {
|
|
39
|
+
await pgManager.updateSessionMetadata(id, session.metadata);
|
|
40
|
+
}
|
|
41
|
+
result.migrated++;
|
|
42
|
+
console.error(`✓ Migrated session ${id} (${session.cli})`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
result.failed++;
|
|
46
|
+
const errorMsg = `Failed to migrate session ${id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
47
|
+
result.errors.push(errorMsg);
|
|
48
|
+
console.error(`✗ ${errorMsg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Restore active sessions
|
|
52
|
+
console.error("\nRestoring active sessions...");
|
|
53
|
+
for (const [cli, sessionId] of Object.entries(fileData.activeSession)) {
|
|
54
|
+
if (sessionId) {
|
|
55
|
+
try {
|
|
56
|
+
await pgManager.setActiveSession(cli, sessionId);
|
|
57
|
+
console.error(`✓ Set active session for ${cli}: ${sessionId}`);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const errorMsg = `Failed to set active session for ${cli}: ${error instanceof Error ? error.message : String(error)}`;
|
|
61
|
+
result.errors.push(errorMsg);
|
|
62
|
+
console.error(`✗ ${errorMsg}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Main CLI entry point
|
|
70
|
+
*/
|
|
71
|
+
async function main() {
|
|
72
|
+
const args = process.argv.slice(2);
|
|
73
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
74
|
+
console.error(`
|
|
75
|
+
Usage: node dist/migrate-sessions.js --from <sessions.json>
|
|
76
|
+
|
|
77
|
+
Migrate sessions from file-based storage to PostgreSQL.
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--from <path> Path to sessions.json file (default: ~/.llm-cli-gateway/sessions.json)
|
|
81
|
+
--help, -h Show this help message
|
|
82
|
+
|
|
83
|
+
Environment Variables:
|
|
84
|
+
DATABASE_URL PostgreSQL connection string (required)
|
|
85
|
+
REDIS_URL Redis connection string (required)
|
|
86
|
+
`);
|
|
87
|
+
process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 1);
|
|
88
|
+
}
|
|
89
|
+
// Parse arguments
|
|
90
|
+
let filePath = join(homedir(), ".llm-cli-gateway", "sessions.json");
|
|
91
|
+
for (let i = 0; i < args.length; i++) {
|
|
92
|
+
if (args[i] === "--from" && args[i + 1]) {
|
|
93
|
+
filePath = args[i + 1];
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.error(`Migration Configuration:`);
|
|
98
|
+
console.error(` Source: ${filePath}`);
|
|
99
|
+
console.error(` DATABASE_URL: ${process.env.DATABASE_URL ? "[set]" : "[not set]"}`);
|
|
100
|
+
console.error(` REDIS_URL: ${process.env.REDIS_URL ? "[set]" : "[not set]"}`);
|
|
101
|
+
console.error("");
|
|
102
|
+
// Load config
|
|
103
|
+
const config = loadConfig();
|
|
104
|
+
if (!config.database || !config.redis) {
|
|
105
|
+
console.error("ERROR: DATABASE_URL and REDIS_URL must be set");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
// Connect to database
|
|
109
|
+
console.error("Connecting to database...");
|
|
110
|
+
const db = await createDatabaseConnection(config, logger);
|
|
111
|
+
const pgManager = new PostgreSQLSessionManager(db.getPool(), db.getRedis(), config.cacheTtl, logger);
|
|
112
|
+
console.error("✓ Connected to database\n");
|
|
113
|
+
try {
|
|
114
|
+
// Run migration
|
|
115
|
+
console.error("Starting migration...\n");
|
|
116
|
+
const result = await migrateFromFile(filePath, pgManager);
|
|
117
|
+
console.error("\n" + "=".repeat(50));
|
|
118
|
+
console.error("Migration Summary:");
|
|
119
|
+
console.error(` Migrated: ${result.migrated}`);
|
|
120
|
+
console.error(` Failed: ${result.failed}`);
|
|
121
|
+
if (result.errors.length > 0) {
|
|
122
|
+
console.error("\nErrors:");
|
|
123
|
+
result.errors.forEach(err => console.error(` - ${err}`));
|
|
124
|
+
}
|
|
125
|
+
if (result.failed === 0) {
|
|
126
|
+
console.error("\n✓ Migration completed successfully!");
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.error("\n⚠ Migration completed with errors");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error("\nERROR:", error instanceof Error ? error.message : String(error));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await db.disconnect();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Run if executed directly
|
|
143
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
144
|
+
main();
|
|
145
|
+
}
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
import { readFileSync, readdirSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
/**
|
|
9
|
+
* Load all migration files from migrations directory
|
|
10
|
+
*/
|
|
11
|
+
function loadMigrations() {
|
|
12
|
+
const migrationsDir = join(__dirname, "..", "migrations");
|
|
13
|
+
const files = readdirSync(migrationsDir)
|
|
14
|
+
.filter(f => f.endsWith(".sql"))
|
|
15
|
+
.sort(); // Ensures migrations run in order
|
|
16
|
+
return files.map(file => {
|
|
17
|
+
const match = file.match(/^(\d+)_(.+)\.sql$/);
|
|
18
|
+
if (!match) {
|
|
19
|
+
throw new Error(`Invalid migration filename: ${file}`);
|
|
20
|
+
}
|
|
21
|
+
const version = parseInt(match[1], 10);
|
|
22
|
+
const name = match[2];
|
|
23
|
+
const sql = readFileSync(join(migrationsDir, file), "utf-8");
|
|
24
|
+
return { version, name, sql };
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get list of applied migrations
|
|
29
|
+
*/
|
|
30
|
+
async function getAppliedMigrations(pool) {
|
|
31
|
+
// First, ensure schema_migrations table exists
|
|
32
|
+
await pool.query(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
34
|
+
version INTEGER PRIMARY KEY,
|
|
35
|
+
name VARCHAR(255) NOT NULL,
|
|
36
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
37
|
+
)
|
|
38
|
+
`);
|
|
39
|
+
const result = await pool.query("SELECT version FROM schema_migrations ORDER BY version");
|
|
40
|
+
return result.rows.map(row => row.version);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run a single migration
|
|
44
|
+
*/
|
|
45
|
+
async function runMigration(pool, migration) {
|
|
46
|
+
console.error(`Running migration ${migration.version}: ${migration.name}...`);
|
|
47
|
+
const client = await pool.connect();
|
|
48
|
+
try {
|
|
49
|
+
await client.query("BEGIN");
|
|
50
|
+
await client.query(migration.sql);
|
|
51
|
+
await client.query("COMMIT");
|
|
52
|
+
console.error(`✓ Migration ${migration.version} completed`);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
await client.query("ROLLBACK");
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
client.release();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Main migration runner
|
|
64
|
+
*/
|
|
65
|
+
async function main() {
|
|
66
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
67
|
+
if (!databaseUrl) {
|
|
68
|
+
console.error("ERROR: DATABASE_URL environment variable not set");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const pool = new Pool({ connectionString: databaseUrl });
|
|
72
|
+
try {
|
|
73
|
+
// Load migrations
|
|
74
|
+
const migrations = loadMigrations();
|
|
75
|
+
console.error(`Found ${migrations.length} migration(s)`);
|
|
76
|
+
// Get applied migrations
|
|
77
|
+
const applied = await getAppliedMigrations(pool);
|
|
78
|
+
console.error(`${applied.length} migration(s) already applied`);
|
|
79
|
+
// Filter pending migrations
|
|
80
|
+
const pending = migrations.filter(m => !applied.includes(m.version));
|
|
81
|
+
if (pending.length === 0) {
|
|
82
|
+
console.error("✓ All migrations up to date");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
console.error(`Running ${pending.length} pending migration(s)...`);
|
|
86
|
+
// Run pending migrations
|
|
87
|
+
for (const migration of pending) {
|
|
88
|
+
await runMigration(pool, migration);
|
|
89
|
+
}
|
|
90
|
+
console.error("✓ All migrations completed successfully");
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error("ERROR:", error instanceof Error ? error.message : String(error));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await pool.end();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
main();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliType } from "./session-manager.js";
|
|
2
|
+
export interface CliInfo {
|
|
3
|
+
description: string;
|
|
4
|
+
models: Record<string, string>;
|
|
5
|
+
defaultModel?: string;
|
|
6
|
+
modelOrder?: string[];
|
|
7
|
+
}
|
|
8
|
+
export type CliInfoMap = Record<CliType, CliInfo>;
|
|
9
|
+
export declare function getCliInfo(forceRefresh?: boolean): CliInfoMap;
|
|
10
|
+
export declare function resolveModelAlias(cli: CliType, model: string | undefined, info: CliInfoMap): string | undefined;
|