llm-cli-gateway 2.13.2 → 2.14.0-rc.1

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +53 -26
  3. package/dist/acp/client.d.ts +29 -1
  4. package/dist/acp/client.js +78 -4
  5. package/dist/acp/errors.d.ts +9 -1
  6. package/dist/acp/errors.js +19 -0
  7. package/dist/acp/event-normalizer.d.ts +12 -0
  8. package/dist/acp/event-normalizer.js +16 -0
  9. package/dist/acp/flight-redaction.d.ts +3 -0
  10. package/dist/acp/flight-redaction.js +3 -0
  11. package/dist/acp/permission-bridge.js +11 -5
  12. package/dist/acp/process-manager.d.ts +2 -1
  13. package/dist/acp/process-manager.js +43 -4
  14. package/dist/acp/provider-registry.js +19 -11
  15. package/dist/acp/runtime.d.ts +8 -0
  16. package/dist/acp/runtime.js +47 -4
  17. package/dist/acp/types.d.ts +3083 -55
  18. package/dist/acp/types.js +242 -5
  19. package/dist/async-job-manager.d.ts +2 -0
  20. package/dist/async-job-manager.js +11 -0
  21. package/dist/codex-json-parser.d.ts +1 -0
  22. package/dist/codex-json-parser.js +6 -0
  23. package/dist/config.d.ts +8 -0
  24. package/dist/config.js +47 -0
  25. package/dist/flight-recorder.d.ts +2 -0
  26. package/dist/flight-recorder.js +20 -0
  27. package/dist/gemini-json-parser.d.ts +2 -0
  28. package/dist/gemini-json-parser.js +45 -8
  29. package/dist/grok-json-parser.d.ts +14 -0
  30. package/dist/grok-json-parser.js +156 -0
  31. package/dist/index.d.ts +47 -2
  32. package/dist/index.js +484 -119
  33. package/dist/job-store.d.ts +45 -7
  34. package/dist/job-store.js +138 -17
  35. package/dist/model-registry.d.ts +1 -0
  36. package/dist/model-registry.js +46 -0
  37. package/dist/oauth.js +2 -2
  38. package/dist/postgres-job-store-worker.d.ts +1 -0
  39. package/dist/postgres-job-store-worker.js +327 -0
  40. package/dist/pricing.d.ts +3 -2
  41. package/dist/provider-acp-capabilities.d.ts +52 -0
  42. package/dist/provider-acp-capabilities.js +101 -0
  43. package/dist/provider-admin-tools.d.ts +100 -0
  44. package/dist/provider-admin-tools.js +572 -0
  45. package/dist/provider-capability-cache.d.ts +46 -0
  46. package/dist/provider-capability-cache.js +248 -0
  47. package/dist/provider-capability-discovery.d.ts +85 -0
  48. package/dist/provider-capability-discovery.js +461 -0
  49. package/dist/provider-capability-resolver.d.ts +29 -0
  50. package/dist/provider-capability-resolver.js +92 -0
  51. package/dist/provider-definition-assertions.d.ts +9 -0
  52. package/dist/provider-definition-assertions.js +147 -0
  53. package/dist/provider-definitions.d.ts +127 -0
  54. package/dist/provider-definitions.js +758 -0
  55. package/dist/provider-help-parser.d.ts +34 -0
  56. package/dist/provider-help-parser.js +203 -0
  57. package/dist/provider-model-discovery.d.ts +30 -0
  58. package/dist/provider-model-discovery.js +229 -0
  59. package/dist/provider-output-metadata.d.ts +7 -0
  60. package/dist/provider-output-metadata.js +68 -0
  61. package/dist/provider-schema-builder.d.ts +22 -0
  62. package/dist/provider-schema-builder.js +55 -0
  63. package/dist/provider-surface-generator.d.ts +98 -0
  64. package/dist/provider-surface-generator.js +140 -0
  65. package/dist/provider-tool-capabilities.d.ts +3 -2
  66. package/dist/provider-tool-capabilities.js +77 -62
  67. package/dist/request-helpers.d.ts +37 -4
  68. package/dist/request-helpers.js +134 -0
  69. package/dist/resources.d.ts +6 -1
  70. package/dist/resources.js +64 -192
  71. package/dist/session-manager.js +18 -11
  72. package/dist/stream-json-parser.d.ts +1 -0
  73. package/dist/stream-json-parser.js +3 -0
  74. package/dist/upstream-contracts.d.ts +17 -1
  75. package/dist/upstream-contracts.js +209 -36
  76. package/npm-shrinkwrap.json +2 -2
  77. package/package.json +8 -3
package/dist/acp/types.js CHANGED
@@ -57,10 +57,60 @@ export const InitializeRequestSchema = z
57
57
  _meta: MetaSchema,
58
58
  })
59
59
  .passthrough();
60
+ const SessionSubCapabilitySchema = z.object({ _meta: MetaSchema }).passthrough().nullish();
61
+ export const PromptCapabilitiesSchema = z
62
+ .object({
63
+ image: z.boolean().optional(),
64
+ audio: z.boolean().optional(),
65
+ embeddedContext: z.boolean().optional(),
66
+ _meta: MetaSchema,
67
+ })
68
+ .passthrough();
69
+ export const McpCapabilitiesSchema = z
70
+ .object({
71
+ http: z.boolean().optional(),
72
+ sse: z.boolean().optional(),
73
+ _meta: MetaSchema,
74
+ })
75
+ .passthrough();
76
+ export const SessionCapabilitiesSchema = z
77
+ .object({
78
+ resume: SessionSubCapabilitySchema,
79
+ list: SessionSubCapabilitySchema,
80
+ close: SessionSubCapabilitySchema,
81
+ delete: SessionSubCapabilitySchema,
82
+ additionalDirectories: SessionSubCapabilitySchema,
83
+ _meta: MetaSchema,
84
+ })
85
+ .passthrough();
86
+ export const AgentAuthCapabilitiesSchema = z
87
+ .object({
88
+ logout: z.object({ _meta: MetaSchema }).passthrough().nullish(),
89
+ _meta: MetaSchema,
90
+ })
91
+ .passthrough();
92
+ export const AgentCapabilitiesSchema = z
93
+ .object({
94
+ loadSession: z.boolean().optional(),
95
+ promptCapabilities: PromptCapabilitiesSchema.optional(),
96
+ mcpCapabilities: McpCapabilitiesSchema.optional(),
97
+ sessionCapabilities: SessionCapabilitiesSchema.optional(),
98
+ auth: AgentAuthCapabilitiesSchema.optional(),
99
+ _meta: MetaSchema,
100
+ })
101
+ .passthrough();
102
+ export const AuthMethodSchema = z
103
+ .object({
104
+ id: z.string().optional(),
105
+ name: z.string().optional(),
106
+ description: z.string().nullish(),
107
+ _meta: MetaSchema,
108
+ })
109
+ .passthrough();
60
110
  export const InitializeResponseSchema = z
61
111
  .object({
62
112
  protocolVersion: ProtocolVersionSchema,
63
- agentCapabilities: z.record(z.unknown()).optional(),
113
+ agentCapabilities: AgentCapabilitiesSchema.optional(),
64
114
  agentInfo: z
65
115
  .object({
66
116
  name: z.string().optional(),
@@ -68,10 +118,51 @@ export const InitializeResponseSchema = z
68
118
  })
69
119
  .passthrough()
70
120
  .optional(),
71
- authMethods: z.array(z.record(z.unknown())).optional(),
121
+ authMethods: z.array(AuthMethodSchema).optional(),
72
122
  _meta: MetaSchema,
73
123
  })
74
124
  .passthrough();
125
+ export const BASELINE_ACP_METHODS = [
126
+ "session/new",
127
+ "session/prompt",
128
+ "session/cancel",
129
+ "session/update",
130
+ ];
131
+ function subCapabilityPresent(value) {
132
+ return value !== null && value !== undefined && typeof value === "object";
133
+ }
134
+ export function deriveAcpMethodAvailability(init) {
135
+ const methods = new Set(BASELINE_ACP_METHODS);
136
+ const caps = init.agentCapabilities;
137
+ if (caps?.loadSession === true) {
138
+ methods.add("session/load");
139
+ }
140
+ const sc = caps?.sessionCapabilities;
141
+ if (sc) {
142
+ if (subCapabilityPresent(sc.resume))
143
+ methods.add("session/resume");
144
+ if (subCapabilityPresent(sc.list))
145
+ methods.add("session/list");
146
+ if (subCapabilityPresent(sc.close))
147
+ methods.add("session/close");
148
+ if (subCapabilityPresent(sc.delete))
149
+ methods.add("session/delete");
150
+ }
151
+ if (Array.isArray(init.authMethods) && init.authMethods.length > 0) {
152
+ methods.add("authenticate");
153
+ }
154
+ return methods;
155
+ }
156
+ export function sessionResponseMethods(response) {
157
+ const methods = new Set();
158
+ if (response.modes !== null && response.modes !== undefined) {
159
+ methods.add("session/set_mode");
160
+ }
161
+ if (response.configOptions !== null && response.configOptions !== undefined) {
162
+ methods.add("session/set_config_option");
163
+ }
164
+ return methods;
165
+ }
75
166
  export const McpServerSchema = z.record(z.unknown());
76
167
  export const SessionNewRequestSchema = z
77
168
  .object({
@@ -83,7 +174,8 @@ export const SessionNewRequestSchema = z
83
174
  export const SessionNewResponseSchema = z
84
175
  .object({
85
176
  sessionId: SessionIdSchema,
86
- modes: z.record(z.unknown()).optional(),
177
+ modes: z.record(z.unknown()).nullish(),
178
+ configOptions: z.array(z.record(z.unknown())).nullish(),
87
179
  _meta: MetaSchema,
88
180
  })
89
181
  .passthrough();
@@ -97,7 +189,124 @@ export const SessionLoadRequestSchema = z
97
189
  .passthrough();
98
190
  export const SessionLoadResponseSchema = z
99
191
  .object({
100
- modes: z.record(z.unknown()).optional(),
192
+ modes: z.record(z.unknown()).nullish(),
193
+ configOptions: z.array(z.record(z.unknown())).nullish(),
194
+ _meta: MetaSchema,
195
+ })
196
+ .passthrough();
197
+ export const SessionResumeRequestSchema = z
198
+ .object({
199
+ sessionId: SessionIdSchema,
200
+ cwd: z.string().min(1),
201
+ mcpServers: z.array(McpServerSchema).default([]),
202
+ _meta: MetaSchema,
203
+ })
204
+ .passthrough();
205
+ export const SessionResumeResponseSchema = z
206
+ .object({
207
+ modes: z.record(z.unknown()).nullish(),
208
+ configOptions: z.array(z.record(z.unknown())).nullish(),
209
+ _meta: MetaSchema,
210
+ })
211
+ .passthrough();
212
+ export const SessionModeSchema = z
213
+ .object({
214
+ id: z.string().min(1),
215
+ name: z.string().min(1),
216
+ description: z.string().nullish(),
217
+ _meta: MetaSchema,
218
+ })
219
+ .passthrough();
220
+ export const SessionModeStateSchema = z
221
+ .object({
222
+ currentModeId: z.string().min(1),
223
+ availableModes: z.array(SessionModeSchema),
224
+ _meta: MetaSchema,
225
+ })
226
+ .passthrough();
227
+ export const SessionConfigSelectValueSchema = z
228
+ .object({
229
+ value: z.string().min(1),
230
+ label: z.string().nullish(),
231
+ description: z.string().nullish(),
232
+ _meta: MetaSchema,
233
+ })
234
+ .passthrough();
235
+ export const SessionConfigOptionSchema = z.discriminatedUnion("type", [
236
+ z
237
+ .object({
238
+ type: z.literal("select"),
239
+ id: z.string().min(1),
240
+ name: z.string().min(1),
241
+ description: z.string().nullish(),
242
+ category: z.string().nullish(),
243
+ currentValue: z.string().min(1),
244
+ options: z.array(SessionConfigSelectValueSchema),
245
+ _meta: MetaSchema,
246
+ })
247
+ .passthrough(),
248
+ z
249
+ .object({
250
+ type: z.literal("boolean"),
251
+ id: z.string().min(1),
252
+ name: z.string().min(1),
253
+ description: z.string().nullish(),
254
+ category: z.string().nullish(),
255
+ currentValue: z.boolean(),
256
+ _meta: MetaSchema,
257
+ })
258
+ .passthrough(),
259
+ ]);
260
+ export const SessionInfoSchema = z
261
+ .object({
262
+ sessionId: SessionIdSchema,
263
+ cwd: z.string().min(1),
264
+ additionalDirectories: z.array(z.string()).optional(),
265
+ title: z.string().nullish(),
266
+ updatedAt: z.string().nullish(),
267
+ _meta: MetaSchema,
268
+ })
269
+ .passthrough();
270
+ export const ListSessionsRequestSchema = z
271
+ .object({
272
+ cursor: z.string().nullish(),
273
+ _meta: MetaSchema,
274
+ })
275
+ .passthrough();
276
+ export const ListSessionsResponseSchema = z
277
+ .object({
278
+ sessions: z.array(SessionInfoSchema),
279
+ nextCursor: z.string().nullish(),
280
+ _meta: MetaSchema,
281
+ })
282
+ .passthrough();
283
+ export const CloseSessionRequestSchema = z
284
+ .object({ sessionId: SessionIdSchema, _meta: MetaSchema })
285
+ .passthrough();
286
+ export const CloseSessionResponseSchema = z.object({ _meta: MetaSchema }).passthrough();
287
+ export const DeleteSessionRequestSchema = z
288
+ .object({ sessionId: SessionIdSchema, _meta: MetaSchema })
289
+ .passthrough();
290
+ export const DeleteSessionResponseSchema = z.object({ _meta: MetaSchema }).passthrough();
291
+ export const SetSessionModeRequestSchema = z
292
+ .object({
293
+ sessionId: SessionIdSchema,
294
+ modeId: z.string().min(1),
295
+ _meta: MetaSchema,
296
+ })
297
+ .passthrough();
298
+ export const SetSessionModeResponseSchema = z.object({ _meta: MetaSchema }).passthrough();
299
+ export const SetSessionConfigOptionRequestSchema = z
300
+ .object({
301
+ sessionId: SessionIdSchema,
302
+ configId: z.string().min(1),
303
+ value: z.string().min(1),
304
+ _meta: MetaSchema,
305
+ })
306
+ .passthrough();
307
+ export const SetSessionConfigOptionResponseSchema = z
308
+ .object({
309
+ configOptions: z.array(SessionConfigOptionSchema),
101
310
  _meta: MetaSchema,
102
311
  })
103
312
  .passthrough();
@@ -184,6 +393,13 @@ const UsageUpdate = z
184
393
  _meta: MetaSchema,
185
394
  })
186
395
  .passthrough();
396
+ const ConfigOptionUpdate = z
397
+ .object({
398
+ sessionUpdate: z.literal("config_option_update"),
399
+ configOptions: z.array(SessionConfigOptionSchema),
400
+ _meta: MetaSchema,
401
+ })
402
+ .passthrough();
187
403
  const KNOWN_UPDATE_SCHEMAS = {
188
404
  user_message_chunk: MessageChunkUpdate("user_message_chunk"),
189
405
  agent_message_chunk: MessageChunkUpdate("agent_message_chunk"),
@@ -193,6 +409,7 @@ const KNOWN_UPDATE_SCHEMAS = {
193
409
  plan: PlanUpdate,
194
410
  available_commands_update: AvailableCommandsUpdate,
195
411
  current_mode_update: CurrentModeUpdate,
412
+ config_option_update: ConfigOptionUpdate,
196
413
  usage_update: UsageUpdate,
197
414
  };
198
415
  export const SessionUpdateSchema = z
@@ -227,7 +444,6 @@ export const KNOWN_SESSION_UPDATE_VARIANTS = [
227
444
  "available_commands_update",
228
445
  "current_mode_update",
229
446
  "config_option_update",
230
- "session_info_update",
231
447
  "usage_update",
232
448
  ];
233
449
  export function isUnknownSessionUpdate(update) {
@@ -333,3 +549,24 @@ export function parseWriteTextFileRequest(value, provider) {
333
549
  provider,
334
550
  });
335
551
  }
552
+ export function parseSessionResumeResponse(value, provider) {
553
+ return parseAcp(SessionResumeResponseSchema, value, { method: "session/resume", provider });
554
+ }
555
+ export function parseListSessionsResponse(value, provider) {
556
+ return parseAcp(ListSessionsResponseSchema, value, { method: "session/list", provider });
557
+ }
558
+ export function parseCloseSessionResponse(value, provider) {
559
+ return parseAcp(CloseSessionResponseSchema, value, { method: "session/close", provider });
560
+ }
561
+ export function parseDeleteSessionResponse(value, provider) {
562
+ return parseAcp(DeleteSessionResponseSchema, value, { method: "session/delete", provider });
563
+ }
564
+ export function parseSetSessionModeResponse(value, provider) {
565
+ return parseAcp(SetSessionModeResponseSchema, value, { method: "session/set_mode", provider });
566
+ }
567
+ export function parseSetSessionConfigOptionResponse(value, provider) {
568
+ return parseAcp(SetSessionConfigOptionResponseSchema, value, {
569
+ method: "session/set_config_option",
570
+ provider,
571
+ });
572
+ }
@@ -69,6 +69,8 @@ export interface AsyncJobResult extends AsyncJobSnapshot {
69
69
  responseId?: string | null;
70
70
  model?: string;
71
71
  errorBody?: string;
72
+ providerSessionId?: string;
73
+ stopReason?: string;
72
74
  }
73
75
  export interface StartJobOptions {
74
76
  cwd?: string;
@@ -5,6 +5,7 @@ import { ProcessMonitor } from "./process-monitor.js";
5
5
  import { computeRequestKey, isValidationRunStore } from "./job-store.js";
6
6
  import { NoopFlightRecorder, } from "./flight-recorder.js";
7
7
  import { codexFrResponse } from "./codex-json-parser.js";
8
+ import { extractProviderOutputMetadata } from "./provider-output-metadata.js";
8
9
  import { getRequestContext, resolveOwnerPrincipal, principalCanAccess } from "./request-context.js";
9
10
  import { runApiRequest, ApiHttpError, } from "./api-provider.js";
10
11
  export function extractApiHttpStatus(error) {
@@ -681,6 +682,9 @@ export class AsyncJobManager {
681
682
  const errorMessage = isFailure
682
683
  ? (overrideErrorMessage ?? job.error ?? job.stderr ?? `Exit code ${exitCode}`)
683
684
  : undefined;
685
+ const providerMeta = finalStatus === "completed" && job.transport === "process"
686
+ ? extractProviderOutputMetadata(job.cli, job.stdout, job.outputFormat)
687
+ : undefined;
684
688
  try {
685
689
  this.flightRecorder.logComplete(job.correlationId, {
686
690
  response,
@@ -697,6 +701,8 @@ export class AsyncJobManager {
697
701
  cacheReadTokens: usage.cacheReadTokens,
698
702
  cacheCreationTokens: usage.cacheCreationTokens,
699
703
  costUsd: usage.costUsd,
704
+ providerSessionId: providerMeta?.sessionId,
705
+ stopReason: providerMeta?.stopReason,
700
706
  });
701
707
  job.flightRecorderComplete = true;
702
708
  job.flightRecorderEntry = undefined;
@@ -1136,12 +1142,17 @@ export class AsyncJobManager {
1136
1142
  }
1137
1143
  const stdout = truncateText(job.stdout, maxChars);
1138
1144
  const stderr = truncateText(job.stderr, maxChars);
1145
+ const providerMeta = job.transport === "process" && job.status === "completed"
1146
+ ? extractProviderOutputMetadata(job.cli, job.stdout, job.outputFormat)
1147
+ : undefined;
1139
1148
  return {
1140
1149
  ...this.snapshot(job),
1141
1150
  stdout: stdout.text,
1142
1151
  stderr: stderr.text,
1143
1152
  stdoutTruncated: stdout.truncated,
1144
1153
  stderrTruncated: stderr.truncated,
1154
+ ...(providerMeta?.sessionId ? { providerSessionId: providerMeta.sessionId } : {}),
1155
+ ...(providerMeta?.stopReason ? { stopReason: providerMeta.stopReason } : {}),
1145
1156
  ...(job.transport === "http"
1146
1157
  ? {
1147
1158
  apiUsage: job.apiUsage,
@@ -9,6 +9,7 @@ export interface CodexJsonParseResult {
9
9
  usage?: CodexUsage;
10
10
  error?: string;
11
11
  threadId?: string;
12
+ stopReason?: string;
12
13
  finalMessage?: string;
13
14
  sawEvent?: boolean;
14
15
  }
@@ -47,6 +47,12 @@ export function parseCodexJsonStream(stdout) {
47
47
  }
48
48
  result.usage = usage;
49
49
  }
50
+ if (typeof parsed.stop_reason === "string") {
51
+ result.stopReason = parsed.stop_reason;
52
+ }
53
+ else if (typeof parsed.reason === "string") {
54
+ result.stopReason = parsed.reason;
55
+ }
50
56
  break;
51
57
  }
52
58
  case "turn.failed": {
package/dist/config.d.ts CHANGED
@@ -155,6 +155,7 @@ export interface AcpConfig {
155
155
  promptTimeoutMs: number;
156
156
  allowWriteHostServices: boolean;
157
157
  allowTerminalHostServices: boolean;
158
+ allowMutatingSessionOps: boolean;
158
159
  fallbackToCliWhenUnhealthy: boolean;
159
160
  providers: Record<string, AcpProviderConfig>;
160
161
  sources: {
@@ -162,6 +163,13 @@ export interface AcpConfig {
162
163
  };
163
164
  }
164
165
  export declare function loadAcpConfig(logger?: Logger): AcpConfig;
166
+ export interface AdminConfig {
167
+ allowMutatingCliAdminOps: boolean;
168
+ sources: {
169
+ configFile: string | null;
170
+ };
171
+ }
172
+ export declare function loadAdminConfig(logger?: Logger): AdminConfig;
165
173
  export type RemoteOAuthConfigStatus = "absent" | "disabled" | "enabled" | "malformed";
166
174
  export interface RemoteOAuthConfigDiagnostics {
167
175
  config: RemoteOAuthConfig;
package/dist/config.js CHANGED
@@ -537,6 +537,7 @@ const AcpConfigSchema = z
537
537
  prompt_timeout_ms: z.number().int().positive().default(DEFAULT_ACP_PROMPT_TIMEOUT_MS),
538
538
  allow_write_host_services: z.boolean().default(false),
539
539
  allow_terminal_host_services: z.boolean().default(false),
540
+ allow_mutating_session_ops: z.boolean().default(false),
540
541
  fallback_to_cli_when_unhealthy: z.boolean().default(true),
541
542
  providers: z.record(z.string(), AcpProviderSchema).default({}),
542
543
  })
@@ -552,6 +553,7 @@ function defaultAcpConfig(sourcePath) {
552
553
  promptTimeoutMs: DEFAULT_ACP_PROMPT_TIMEOUT_MS,
553
554
  allowWriteHostServices: false,
554
555
  allowTerminalHostServices: false,
556
+ allowMutatingSessionOps: false,
555
557
  fallbackToCliWhenUnhealthy: true,
556
558
  providers: {},
557
559
  sources: { configFile: sourcePath },
@@ -608,11 +610,56 @@ export function loadAcpConfig(logger = noopLogger) {
608
610
  promptTimeoutMs: parsed.prompt_timeout_ms,
609
611
  allowWriteHostServices: parsed.allow_write_host_services,
610
612
  allowTerminalHostServices: parsed.allow_terminal_host_services,
613
+ allowMutatingSessionOps: parsed.allow_mutating_session_ops,
611
614
  fallbackToCliWhenUnhealthy: parsed.fallback_to_cli_when_unhealthy,
612
615
  providers,
613
616
  sources: { configFile: sourcePath },
614
617
  };
615
618
  }
619
+ const AdminConfigSchema = z
620
+ .object({
621
+ allow_mutating_cli_admin_ops: z.boolean().default(false),
622
+ })
623
+ .strict();
624
+ function defaultAdminConfig(sourcePath) {
625
+ return { allowMutatingCliAdminOps: false, sources: { configFile: sourcePath } };
626
+ }
627
+ function readAdminFile(configPath, logger) {
628
+ if (!existsSync(configPath)) {
629
+ return { raw: undefined, sourcePath: null };
630
+ }
631
+ try {
632
+ const require = createRequire(import.meta.url);
633
+ const TOML = require("smol-toml");
634
+ const text = readFileSync(configPath, "utf-8");
635
+ const parsed = TOML.parse(text);
636
+ return { raw: parsed?.admin, sourcePath: configPath };
637
+ }
638
+ catch (err) {
639
+ logger.error(`Failed to parse gateway config at ${configPath}; using admin defaults`, err);
640
+ return { raw: undefined, sourcePath: null };
641
+ }
642
+ }
643
+ export function loadAdminConfig(logger = noopLogger) {
644
+ const configPath = defaultGatewayConfigPath();
645
+ const { raw, sourcePath } = readAdminFile(configPath, logger);
646
+ if (raw === undefined) {
647
+ return defaultAdminConfig(sourcePath);
648
+ }
649
+ let parsed;
650
+ try {
651
+ parsed = AdminConfigSchema.parse(raw);
652
+ }
653
+ catch (err) {
654
+ throw new Error(`Invalid [admin] config: ${err instanceof Error ? err.message : String(err)}`, {
655
+ cause: err,
656
+ });
657
+ }
658
+ return {
659
+ allowMutatingCliAdminOps: parsed.allow_mutating_cli_admin_ops,
660
+ sources: { configFile: sourcePath },
661
+ };
662
+ }
616
663
  const OAuthRegistrationPolicySchema = z.enum(["static_clients", "shared_secret", "open_dev"]);
617
664
  const OAuthClientSchema = z
618
665
  .object({
@@ -30,6 +30,8 @@ export interface FlightLogResult {
30
30
  httpStatus?: number;
31
31
  errorMessage?: string;
32
32
  status: "completed" | "failed";
33
+ providerSessionId?: string;
34
+ stopReason?: string;
33
35
  }
34
36
  interface LoggerLike {
35
37
  info: (message: string, ...args: any[]) => void;
@@ -54,6 +54,16 @@ function ensureCacheControlTtlSecondsColumn(db) {
54
54
  db.exec("ALTER TABLE requests ADD COLUMN cache_control_ttl_seconds INTEGER");
55
55
  }
56
56
  }
57
+ function ensureMetadataProviderSessionColumns(db) {
58
+ const rows = db.prepare("PRAGMA table_info(gateway_metadata)").all();
59
+ const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
60
+ if (!names.has("provider_session_id")) {
61
+ db.exec("ALTER TABLE gateway_metadata ADD COLUMN provider_session_id TEXT");
62
+ }
63
+ if (!names.has("stop_reason")) {
64
+ db.exec("ALTER TABLE gateway_metadata ADD COLUMN stop_reason TEXT");
65
+ }
66
+ }
57
67
  export function resolveFlightRecorderDbPath() {
58
68
  const configured = process.env.LLM_GATEWAY_LOGS_DB;
59
69
  if (configured !== undefined) {
@@ -147,6 +157,8 @@ export class FlightRecorder {
147
157
  http_status INTEGER,
148
158
  error_message TEXT,
149
159
  async_job_id TEXT,
160
+ provider_session_id TEXT,
161
+ stop_reason TEXT,
150
162
  status TEXT NOT NULL DEFAULT 'started'
151
163
  );
152
164
 
@@ -180,6 +192,10 @@ export class FlightRecorder {
180
192
  this.db
181
193
  .prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(6, ?)")
182
194
  .run(new Date().toISOString());
195
+ ensureMetadataProviderSessionColumns(this.db);
196
+ this.db
197
+ .prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(7, ?)")
198
+ .run(new Date().toISOString());
183
199
  if (process.platform !== "win32") {
184
200
  try {
185
201
  chmodSync(dbPath, 0o600);
@@ -240,6 +256,8 @@ export class FlightRecorder {
240
256
  exit_code = @exit_code,
241
257
  http_status = @http_status,
242
258
  error_message = @error_message,
259
+ provider_session_id = @provider_session_id,
260
+ stop_reason = @stop_reason,
243
261
  status = @status
244
262
  WHERE request_id = @id AND status = 'started'
245
263
  `);
@@ -267,6 +285,8 @@ export class FlightRecorder {
267
285
  exit_code: result.exitCode,
268
286
  http_status: result.httpStatus ?? null,
269
287
  error_message: result.errorMessage ?? null,
288
+ provider_session_id: result.providerSessionId ?? null,
289
+ stop_reason: result.stopReason ?? null,
270
290
  status: result.status,
271
291
  });
272
292
  });
@@ -6,6 +6,8 @@ export interface GeminiUsage {
6
6
  export interface GeminiJsonParseResult {
7
7
  usage?: GeminiUsage;
8
8
  response?: string;
9
+ sessionId?: string;
10
+ stopReason?: string;
9
11
  }
10
12
  export declare function parseGeminiJson(stdout: string): GeminiJsonParseResult | null;
11
13
  export declare function parseGeminiStreamJson(stdout: string): GeminiJsonParseResult | null;
@@ -1,22 +1,47 @@
1
- export function parseGeminiJson(stdout) {
2
- const trimmed = stdout.trim();
3
- if (!trimmed) {
4
- return null;
5
- }
6
- let parsed;
1
+ function parseTolerantJsonObject(text) {
7
2
  try {
8
- parsed = JSON.parse(trimmed);
3
+ const v = JSON.parse(text);
4
+ if (v && typeof v === "object")
5
+ return v;
9
6
  }
10
7
  catch {
8
+ }
9
+ const start = text.indexOf("{");
10
+ const end = text.lastIndexOf("}");
11
+ if (start >= 0 && end > start) {
12
+ try {
13
+ const v = JSON.parse(text.slice(start, end + 1));
14
+ if (v && typeof v === "object")
15
+ return v;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ export function parseGeminiJson(stdout) {
24
+ const trimmed = stdout.trim();
25
+ if (!trimmed) {
11
26
  return null;
12
27
  }
13
- if (!parsed || typeof parsed !== "object") {
28
+ const parsed = parseTolerantJsonObject(trimmed);
29
+ if (!parsed) {
14
30
  return null;
15
31
  }
16
32
  const result = {};
17
33
  if (typeof parsed.response === "string") {
18
34
  result.response = parsed.response;
19
35
  }
36
+ if (typeof parsed.session_id === "string") {
37
+ result.sessionId = parsed.session_id;
38
+ }
39
+ else if (typeof parsed.sessionId === "string") {
40
+ result.sessionId = parsed.sessionId;
41
+ }
42
+ if (typeof parsed.status === "string") {
43
+ result.stopReason = parsed.status;
44
+ }
20
45
  const meta = parsed.usageMetadata;
21
46
  if (meta && typeof meta === "object") {
22
47
  const input = typeof meta.promptTokenCount === "number" ? meta.promptTokenCount : undefined;
@@ -56,12 +81,24 @@ export function parseGeminiStreamJson(stdout) {
56
81
  if (!event || typeof event !== "object")
57
82
  continue;
58
83
  sawAnyLine = true;
84
+ if (event.type === "init" && typeof event.session_id === "string") {
85
+ result.sessionId = event.session_id;
86
+ continue;
87
+ }
59
88
  if (event.type === "message" &&
60
89
  event.role === "assistant" &&
61
90
  typeof event.content === "string") {
91
+ if (event.delta === false || event.is_delta === false) {
92
+ assistantChunks.length = 0;
93
+ }
62
94
  assistantChunks.push(event.content);
63
95
  continue;
64
96
  }
97
+ if (event.type === "result") {
98
+ if (typeof event.status === "string") {
99
+ result.stopReason = event.status;
100
+ }
101
+ }
65
102
  if (event.type === "result" && event.stats && typeof event.stats === "object") {
66
103
  const stats = event.stats;
67
104
  const input = typeof stats.input_tokens === "number" ? stats.input_tokens : undefined;
@@ -0,0 +1,14 @@
1
+ export interface GrokJsonParseResult {
2
+ text?: string;
3
+ stopReason?: string;
4
+ sessionId?: string;
5
+ requestId?: string;
6
+ thought?: string;
7
+ error?: string;
8
+ sawEvent?: boolean;
9
+ usageAbsent: true;
10
+ }
11
+ export declare function parseGrokJson(stdout: string): GrokJsonParseResult | null;
12
+ export declare function parseGrokStreamingJson(stdout: string): GrokJsonParseResult | null;
13
+ export declare function parseGrokOutput(outputFormat: string | undefined, stdout: string): GrokJsonParseResult | null;
14
+ export declare function grokDisplayText(outputFormat: string | undefined, stdout: string): string;