llm-cli-gateway 2.14.0-rc.1 → 2.14.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/index.js CHANGED
@@ -291,9 +291,17 @@ function getJobStore(runtimeLogger = logger) {
291
291
  return jobStore;
292
292
  }
293
293
  function newAsyncJobManager(metrics, runtimeLogger, store = getJobStore(runtimeLogger), fr = getFlightRecorder(runtimeLogger)) {
294
+ const pc = getPersistenceConfig(runtimeLogger);
294
295
  return new AsyncJobManager(runtimeLogger, (cli, durationMs, success) => {
295
296
  metrics.recordRequest(cli, durationMs, success);
296
- }, store, fr, getLimitsConfig(runtimeLogger).jobs);
297
+ }, store, fr, getLimitsConfig(runtimeLogger).jobs, true, {
298
+ instanceHeartbeatMs: pc.instanceHeartbeatMs,
299
+ instanceLeaseTtlMs: pc.instanceLeaseTtlMs,
300
+ httpJobGraceMs: pc.httpJobGraceMs,
301
+ orphanSweepIntervalMs: pc.orphanSweepIntervalMs,
302
+ instanceGcMs: pc.instanceGcMs,
303
+ role: process.argv.includes("--transport=http") ? "http" : "stdio",
304
+ });
297
305
  }
298
306
  function getAsyncJobManager(runtimeLogger = logger) {
299
307
  asyncJobManager ??= newAsyncJobManager(performanceMetrics, runtimeLogger);
@@ -573,7 +581,7 @@ async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, f
573
581
  }
574
582
  const deferralAvailable = runtime.persistence.backend !== "none" &&
575
583
  runtime.persistence.asyncJobsEnabled &&
576
- runtime.asyncJobManager.hasStore();
584
+ runtime.asyncJobManager.canAdmitDurableJobs();
577
585
  if (SYNC_DEADLINE_MS === 0 || !deferralAvailable) {
578
586
  const command = providerCommandName(cli);
579
587
  let slot;
@@ -662,7 +670,7 @@ async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolv
662
670
  };
663
671
  const deferralAvailable = runtime.persistence.backend !== "none" &&
664
672
  runtime.persistence.asyncJobsEnabled &&
665
- runtime.asyncJobManager.hasStore();
673
+ runtime.asyncJobManager.canAdmitDurableJobs();
666
674
  if (SYNC_DEADLINE_MS === 0 || !deferralAvailable || forceInline) {
667
675
  let slot;
668
676
  try {
@@ -8282,6 +8290,15 @@ function registerHealthResource(server) {
8282
8290
  async function shutdown(signal) {
8283
8291
  logger.info(`Received ${signal}, shutting down gracefully...`);
8284
8292
  try {
8293
+ if (asyncJobManager) {
8294
+ try {
8295
+ await asyncJobManager.dispose({ timeoutMs: 5000 });
8296
+ logger.info("Async job manager disposed");
8297
+ }
8298
+ catch (err) {
8299
+ logger.error("Async job manager dispose failed", err);
8300
+ }
8301
+ }
8285
8302
  await killAllProcessGroups();
8286
8303
  logger.info("All process groups terminated");
8287
8304
  if (activeHttpGateway) {
@@ -1,6 +1,7 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { PersistenceConfig } from "./config.js";
3
- export type JobStoreStatus = "running" | "completed" | "failed" | "canceled" | "orphaned";
3
+ export type JobStoreStatus = "queued" | "running" | "completed" | "failed" | "canceled" | "orphaned";
4
+ export type JobStoreActiveStatus = Extract<JobStoreStatus, "queued" | "running">;
4
5
  export type JobTransport = "process" | "http";
5
6
  export interface JobRecord {
6
7
  id: string;
@@ -23,11 +24,26 @@ export interface JobRecord {
23
24
  transport: JobTransport;
24
25
  httpStatus: number | null;
25
26
  payloadJson: string | null;
27
+ ownerInstance: string | null;
28
+ leaseDeadline: number | null;
26
29
  }
27
30
  export declare function resolveJobStoreDbPath(): string | null;
28
31
  export declare function resolveJobRetentionMs(): number;
29
32
  export declare function resolveDedupWindowMs(): number;
30
33
  export declare function computeRequestKey(cli: string, args: string[], extra?: string): string;
34
+ export interface GatewayInstanceMeta {
35
+ instanceId: string;
36
+ role: string | null;
37
+ hostname: string | null;
38
+ pid: number | null;
39
+ }
40
+ export interface SweepCandidate {
41
+ id: string;
42
+ pid: number | null;
43
+ transport: JobTransport;
44
+ ownerInstance: string | null;
45
+ hostname: string | null;
46
+ }
31
47
  export interface JobStore {
32
48
  recordStart(input: {
33
49
  id: string;
@@ -39,13 +55,23 @@ export interface JobStore {
39
55
  startedAt: string;
40
56
  pid: number | null;
41
57
  ownerPrincipal?: string | null;
58
+ ownerInstance?: string | null;
42
59
  transport?: JobTransport;
43
60
  payloadJson?: string | null;
44
61
  }): void;
62
+ markRunning(id: string, opts: {
63
+ pid: number | null;
64
+ }): boolean;
65
+ registerInstance(meta: GatewayInstanceMeta): void;
66
+ heartbeat(instanceId: string): void;
67
+ deregisterInstance(instanceId: string): void;
68
+ selectStaleProcessCandidates(leaseTtlMs: number, httpJobGraceMs: number): SweepCandidate[];
69
+ recoverStaleJobs(leaseTtlMs: number, httpJobGraceMs: number, liveConfirmedIds?: string[]): OrphanedJobSnapshot[];
70
+ gcInstances(instanceGcMs: number): number;
45
71
  recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
46
72
  recordComplete(input: {
47
73
  id: string;
48
- status: Exclude<JobStoreStatus, "running">;
74
+ status: Exclude<JobStoreStatus, "running" | "queued">;
49
75
  exitCode: number | null;
50
76
  stdout: string;
51
77
  stderr: string;
@@ -117,6 +143,7 @@ export declare class SqliteJobStore implements JobStore, ValidationRunStore {
117
143
  private db;
118
144
  private retentionMs;
119
145
  private dedupWindowMs;
146
+ private leaseTtlMs;
120
147
  private insertStmt;
121
148
  private updateOutputStmt;
122
149
  private updateCompleteStmt;
@@ -125,9 +152,19 @@ export declare class SqliteJobStore implements JobStore, ValidationRunStore {
125
152
  private selectRunningOrphansStmt;
126
153
  private markOrphanedStmt;
127
154
  private deleteExpiredStmt;
155
+ private markRunningStmt;
156
+ private registerInstanceStmt;
157
+ private heartbeatInstanceStmt;
158
+ private heartbeatJobsStmt;
159
+ private deregisterInstanceStmt;
160
+ private selectStaleCandidatesStmt;
161
+ private orphanExpiredStmt;
162
+ private advanceLeaseStmt;
163
+ private gcInstancesStmt;
128
164
  constructor(dbPath: string, logger?: Logger, options?: {
129
165
  retentionMs?: number;
130
166
  dedupWindowMs?: number;
167
+ leaseTtlMs?: number;
131
168
  });
132
169
  recordStart(input: {
133
170
  id: string;
@@ -139,13 +176,23 @@ export declare class SqliteJobStore implements JobStore, ValidationRunStore {
139
176
  startedAt: string;
140
177
  pid: number | null;
141
178
  ownerPrincipal?: string | null;
179
+ ownerInstance?: string | null;
142
180
  transport?: JobTransport;
143
181
  payloadJson?: string | null;
144
182
  }): void;
183
+ markRunning(id: string, opts: {
184
+ pid: number | null;
185
+ }): boolean;
186
+ registerInstance(meta: GatewayInstanceMeta): void;
187
+ heartbeat(instanceId: string): void;
188
+ deregisterInstance(instanceId: string): void;
189
+ selectStaleProcessCandidates(_leaseTtlMs: number, _httpJobGraceMs: number): SweepCandidate[];
190
+ recoverStaleJobs(leaseTtlMs: number, httpJobGraceMs: number, liveConfirmedIds?: string[]): OrphanedJobSnapshot[];
191
+ gcInstances(instanceGcMs: number): number;
145
192
  recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
146
193
  recordComplete(input: {
147
194
  id: string;
148
- status: Exclude<JobStoreStatus, "running">;
195
+ status: Exclude<JobStoreStatus, "running" | "queued">;
149
196
  exitCode: number | null;
150
197
  stdout: string;
151
198
  stderr: string;
@@ -176,9 +223,11 @@ export declare class MemoryJobStore implements JobStore {
176
223
  private rows;
177
224
  private retentionMs;
178
225
  private dedupWindowMs;
226
+ private leaseTtlMs;
179
227
  constructor(options?: {
180
228
  retentionMs?: number;
181
229
  dedupWindowMs?: number;
230
+ leaseTtlMs?: number;
182
231
  });
183
232
  recordStart(input: {
184
233
  id: string;
@@ -190,13 +239,23 @@ export declare class MemoryJobStore implements JobStore {
190
239
  startedAt: string;
191
240
  pid: number | null;
192
241
  ownerPrincipal?: string | null;
242
+ ownerInstance?: string | null;
193
243
  transport?: JobTransport;
194
244
  payloadJson?: string | null;
195
245
  }): void;
246
+ markRunning(id: string, opts: {
247
+ pid: number | null;
248
+ }): boolean;
249
+ registerInstance(_meta: GatewayInstanceMeta): void;
250
+ heartbeat(instanceId: string): void;
251
+ deregisterInstance(_instanceId: string): void;
252
+ selectStaleProcessCandidates(_leaseTtlMs: number, _httpJobGraceMs: number): SweepCandidate[];
253
+ recoverStaleJobs(_leaseTtlMs: number, _httpJobGraceMs: number, _liveConfirmedIds?: string[]): OrphanedJobSnapshot[];
254
+ gcInstances(_instanceGcMs: number): number;
196
255
  recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
197
256
  recordComplete(input: {
198
257
  id: string;
199
- status: Exclude<JobStoreStatus, "running">;
258
+ status: Exclude<JobStoreStatus, "running" | "queued">;
200
259
  exitCode: number | null;
201
260
  stdout: string;
202
261
  stderr: string;
@@ -223,6 +282,7 @@ export declare class PostgresJobStore implements JobStore, ValidationRunStore {
223
282
  constructor(dsn: string, logger?: Logger, options?: {
224
283
  retentionMs?: number;
225
284
  dedupWindowMs?: number;
285
+ leaseTtlMs?: number;
226
286
  });
227
287
  private syncCall;
228
288
  recordStart(input: {
@@ -235,13 +295,23 @@ export declare class PostgresJobStore implements JobStore, ValidationRunStore {
235
295
  startedAt: string;
236
296
  pid: number | null;
237
297
  ownerPrincipal?: string | null;
298
+ ownerInstance?: string | null;
238
299
  transport?: JobTransport;
239
300
  payloadJson?: string | null;
240
301
  }): void;
302
+ markRunning(id: string, opts: {
303
+ pid: number | null;
304
+ }): boolean;
305
+ registerInstance(meta: GatewayInstanceMeta): void;
306
+ heartbeat(instanceId: string): void;
307
+ deregisterInstance(instanceId: string): void;
308
+ selectStaleProcessCandidates(leaseTtlMs: number, httpJobGraceMs: number): SweepCandidate[];
309
+ recoverStaleJobs(leaseTtlMs: number, httpJobGraceMs: number, liveConfirmedIds?: string[]): OrphanedJobSnapshot[];
310
+ gcInstances(instanceGcMs: number): number;
241
311
  recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
242
312
  recordComplete(input: {
243
313
  id: string;
244
- status: Exclude<JobStoreStatus, "running">;
314
+ status: Exclude<JobStoreStatus, "running" | "queued">;
245
315
  exitCode: number | null;
246
316
  stdout: string;
247
317
  stderr: string;