llm-cli-gateway 2.13.1 → 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.
- package/CHANGELOG.md +140 -0
- package/README.md +53 -26
- package/dist/acp/client.d.ts +29 -1
- package/dist/acp/client.js +78 -4
- package/dist/acp/errors.d.ts +9 -1
- package/dist/acp/errors.js +19 -0
- package/dist/acp/event-normalizer.d.ts +12 -0
- package/dist/acp/event-normalizer.js +16 -0
- package/dist/acp/flight-redaction.d.ts +3 -0
- package/dist/acp/flight-redaction.js +3 -0
- package/dist/acp/permission-bridge.js +11 -5
- package/dist/acp/process-manager.d.ts +2 -1
- package/dist/acp/process-manager.js +43 -4
- package/dist/acp/provider-registry.js +19 -11
- package/dist/acp/runtime.d.ts +8 -0
- package/dist/acp/runtime.js +47 -4
- package/dist/acp/types.d.ts +3083 -55
- package/dist/acp/types.js +242 -5
- package/dist/async-job-manager.d.ts +2 -0
- package/dist/async-job-manager.js +11 -0
- package/dist/codex-json-parser.d.ts +1 -0
- package/dist/codex-json-parser.js +6 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +105 -19
- package/dist/connector-setup.d.ts +39 -0
- package/dist/connector-setup.js +86 -0
- package/dist/doctor.d.ts +39 -1
- package/dist/doctor.js +182 -3
- package/dist/flight-recorder.d.ts +2 -0
- package/dist/flight-recorder.js +20 -0
- package/dist/gemini-json-parser.d.ts +2 -0
- package/dist/gemini-json-parser.js +45 -8
- package/dist/grok-json-parser.d.ts +14 -0
- package/dist/grok-json-parser.js +156 -0
- package/dist/http-transport.js +27 -3
- package/dist/index.d.ts +48 -2
- package/dist/index.js +633 -144
- package/dist/job-store.d.ts +45 -7
- package/dist/job-store.js +138 -17
- package/dist/model-registry.d.ts +1 -0
- package/dist/model-registry.js +46 -0
- package/dist/oauth.js +17 -16
- package/dist/postgres-job-store-worker.d.ts +1 -0
- package/dist/postgres-job-store-worker.js +327 -0
- package/dist/pricing.d.ts +3 -2
- package/dist/provider-acp-capabilities.d.ts +52 -0
- package/dist/provider-acp-capabilities.js +101 -0
- package/dist/provider-admin-tools.d.ts +100 -0
- package/dist/provider-admin-tools.js +572 -0
- package/dist/provider-capability-cache.d.ts +46 -0
- package/dist/provider-capability-cache.js +248 -0
- package/dist/provider-capability-discovery.d.ts +85 -0
- package/dist/provider-capability-discovery.js +461 -0
- package/dist/provider-capability-resolver.d.ts +29 -0
- package/dist/provider-capability-resolver.js +92 -0
- package/dist/provider-definition-assertions.d.ts +9 -0
- package/dist/provider-definition-assertions.js +147 -0
- package/dist/provider-definitions.d.ts +127 -0
- package/dist/provider-definitions.js +758 -0
- package/dist/provider-help-parser.d.ts +34 -0
- package/dist/provider-help-parser.js +203 -0
- package/dist/provider-model-discovery.d.ts +30 -0
- package/dist/provider-model-discovery.js +229 -0
- package/dist/provider-output-metadata.d.ts +7 -0
- package/dist/provider-output-metadata.js +68 -0
- package/dist/provider-schema-builder.d.ts +22 -0
- package/dist/provider-schema-builder.js +55 -0
- package/dist/provider-surface-generator.d.ts +98 -0
- package/dist/provider-surface-generator.js +140 -0
- package/dist/provider-tool-capabilities.d.ts +3 -2
- package/dist/provider-tool-capabilities.js +77 -62
- package/dist/remote-url.d.ts +28 -0
- package/dist/remote-url.js +61 -0
- package/dist/request-helpers.d.ts +37 -4
- package/dist/request-helpers.js +134 -0
- package/dist/resources.d.ts +6 -1
- package/dist/resources.js +67 -193
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.js +34 -8
- package/dist/stream-json-parser.d.ts +1 -0
- package/dist/stream-json-parser.js +3 -0
- package/dist/upstream-contracts.d.ts +17 -1
- package/dist/upstream-contracts.js +209 -36
- package/dist/workspace-registry.d.ts +9 -0
- package/dist/workspace-registry.js +24 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
- package/setup/status.schema.json +75 -0
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:
|
|
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(
|
|
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()).
|
|
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()).
|
|
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
|
+
}
|
|
@@ -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,
|
|
@@ -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,4 +163,19 @@ 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;
|
|
173
|
+
export type RemoteOAuthConfigStatus = "absent" | "disabled" | "enabled" | "malformed";
|
|
174
|
+
export interface RemoteOAuthConfigDiagnostics {
|
|
175
|
+
config: RemoteOAuthConfig;
|
|
176
|
+
status: RemoteOAuthConfigStatus;
|
|
177
|
+
configured: boolean;
|
|
178
|
+
issues: string[];
|
|
179
|
+
}
|
|
180
|
+
export declare function diagnoseRemoteOAuthConfig(logger?: Logger, env?: NodeJS.ProcessEnv): RemoteOAuthConfigDiagnostics;
|
|
165
181
|
export declare function loadRemoteOAuthConfig(logger?: Logger, env?: NodeJS.ProcessEnv): 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({
|
|
@@ -663,11 +710,7 @@ function disabledOAuthConfig(sourcePath = null, envOverrides = []) {
|
|
|
663
710
|
function isSafeRedirectUri(uri) {
|
|
664
711
|
return isHttpsOrLoopbackUrl(uri);
|
|
665
712
|
}
|
|
666
|
-
|
|
667
|
-
const configPath = defaultGatewayConfigPath();
|
|
668
|
-
const { parsed: configFile, sourcePath } = readGatewayTomlFile(configPath, logger, "OAuth");
|
|
669
|
-
const rawHttp = configFile?.http ?? {};
|
|
670
|
-
const rawOAuth = rawHttp.oauth ?? {};
|
|
713
|
+
function mergeOAuthEnvOverrides(rawOAuth, env) {
|
|
671
714
|
const envOverrides = [];
|
|
672
715
|
const merged = { ...rawOAuth };
|
|
673
716
|
if (env.LLM_GATEWAY_OAUTH_ENABLED !== undefined) {
|
|
@@ -695,43 +738,43 @@ export function loadRemoteOAuthConfig(logger = noopLogger, env = process.env) {
|
|
|
695
738
|
merged.require_consent = merged.require_consent ?? true;
|
|
696
739
|
envOverrides.push("LLM_GATEWAY_OAUTH_CONSENT_SECRET");
|
|
697
740
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
error: parsed.error.message,
|
|
702
|
-
});
|
|
703
|
-
return disabledOAuthConfig(sourcePath, envOverrides);
|
|
704
|
-
}
|
|
705
|
-
const data = parsed.data;
|
|
741
|
+
return { merged, envOverrides };
|
|
742
|
+
}
|
|
743
|
+
function validateParsedOAuthConfig(data, env, sourcePath, envOverrides, logger, issues) {
|
|
706
744
|
if (data.issuer !== "auto" && !isHttpsOrLoopbackUrl(data.issuer)) {
|
|
707
745
|
logWarn(logger, "Invalid [http.oauth].issuer; remote OAuth disabled");
|
|
708
|
-
|
|
746
|
+
issues.push("OAuth issuer must be an https:// URL (or a loopback URL for local testing).");
|
|
747
|
+
return null;
|
|
709
748
|
}
|
|
710
749
|
for (const client of data.clients) {
|
|
711
750
|
if (!data.allow_public_clients && !client.client_secret_hash) {
|
|
712
751
|
logWarn(logger, "OAuth client secret hash is required when public clients are disabled", {
|
|
713
752
|
client_id: client.client_id,
|
|
714
753
|
});
|
|
715
|
-
|
|
754
|
+
issues.push("An OAuth client is missing a client_secret_hash (required when public clients are disabled). Recreate it with `llm-cli-gateway oauth client add`.");
|
|
755
|
+
return null;
|
|
716
756
|
}
|
|
717
757
|
if (client.client_secret_hash && !isSecretHash(client.client_secret_hash)) {
|
|
718
758
|
logWarn(logger, "Invalid OAuth client secret hash; remote OAuth disabled", {
|
|
719
759
|
client_id: client.client_id,
|
|
720
760
|
});
|
|
721
|
-
|
|
761
|
+
issues.push("An OAuth client secret hash is not a valid scrypt hash. Rotate it with `llm-cli-gateway oauth client rotate`.");
|
|
762
|
+
return null;
|
|
722
763
|
}
|
|
723
764
|
if (client.allowed_redirect_uris.length === 0 ||
|
|
724
765
|
client.allowed_redirect_uris.some(uri => !isSafeRedirectUri(uri))) {
|
|
725
766
|
logWarn(logger, "Invalid OAuth client redirect URI; remote OAuth disabled", {
|
|
726
767
|
client_id: client.client_id,
|
|
727
768
|
});
|
|
728
|
-
|
|
769
|
+
issues.push("An OAuth client has a missing or non-https/loopback redirect URI. Re-add the client with a valid --redirect-uri.");
|
|
770
|
+
return null;
|
|
729
771
|
}
|
|
730
772
|
}
|
|
731
773
|
if (data.shared_secret?.enabled) {
|
|
732
774
|
if (!data.shared_secret.secret_hash || !isSecretHash(data.shared_secret.secret_hash)) {
|
|
733
775
|
logWarn(logger, "Invalid [http.oauth.shared_secret] secret_hash; remote OAuth disabled");
|
|
734
|
-
|
|
776
|
+
issues.push("The OAuth shared-secret hash is missing or invalid. Reset it with `llm-cli-gateway oauth shared-secret set`.");
|
|
777
|
+
return null;
|
|
735
778
|
}
|
|
736
779
|
}
|
|
737
780
|
if (data.registration_policy === "open_dev" && env.LLM_GATEWAY_OAUTH_OPEN_DEV !== "1") {
|
|
@@ -740,7 +783,8 @@ export function loadRemoteOAuthConfig(logger = noopLogger, env = process.env) {
|
|
|
740
783
|
if (data.require_consent) {
|
|
741
784
|
if (!data.consent_secret_hash || !isSecretHash(data.consent_secret_hash)) {
|
|
742
785
|
logWarn(logger, "[http.oauth].require_consent is set but consent_secret_hash is missing/invalid; remote OAuth disabled");
|
|
743
|
-
|
|
786
|
+
issues.push("require_consent is set but the consent secret hash is missing or invalid. Set it with `llm-cli-gateway oauth shared-secret set` or LLM_GATEWAY_OAUTH_CONSENT_SECRET.");
|
|
787
|
+
return null;
|
|
744
788
|
}
|
|
745
789
|
}
|
|
746
790
|
return {
|
|
@@ -769,3 +813,45 @@ export function loadRemoteOAuthConfig(logger = noopLogger, env = process.env) {
|
|
|
769
813
|
sources: { configFile: sourcePath, envOverrides },
|
|
770
814
|
};
|
|
771
815
|
}
|
|
816
|
+
export function diagnoseRemoteOAuthConfig(logger = noopLogger, env = process.env) {
|
|
817
|
+
const configPath = defaultGatewayConfigPath();
|
|
818
|
+
const { parsed: configFile, sourcePath } = readGatewayTomlFile(configPath, logger, "OAuth");
|
|
819
|
+
const rawHttp = configFile?.http ?? {};
|
|
820
|
+
const rawOAuth = rawHttp.oauth ?? {};
|
|
821
|
+
const { merged, envOverrides } = mergeOAuthEnvOverrides(rawOAuth, env);
|
|
822
|
+
const configured = Object.keys(rawOAuth).length > 0 || envOverrides.length > 0;
|
|
823
|
+
const parsed = OAuthConfigSchema.safeParse(merged);
|
|
824
|
+
if (!parsed.success) {
|
|
825
|
+
logWarn(logger, "Invalid [http.oauth] config; remote OAuth disabled", {
|
|
826
|
+
error: parsed.error.message,
|
|
827
|
+
});
|
|
828
|
+
return {
|
|
829
|
+
config: disabledOAuthConfig(sourcePath, envOverrides),
|
|
830
|
+
status: "malformed",
|
|
831
|
+
configured: true,
|
|
832
|
+
issues: [
|
|
833
|
+
"The [http.oauth] config is invalid (schema validation failed); remote OAuth is disabled.",
|
|
834
|
+
],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const data = parsed.data;
|
|
838
|
+
const issues = [];
|
|
839
|
+
const built = validateParsedOAuthConfig(data, env, sourcePath, envOverrides, logger, issues);
|
|
840
|
+
if (!built) {
|
|
841
|
+
return {
|
|
842
|
+
config: disabledOAuthConfig(sourcePath, envOverrides),
|
|
843
|
+
status: data.enabled ? "malformed" : configured ? "disabled" : "absent",
|
|
844
|
+
configured,
|
|
845
|
+
issues: data.enabled ? issues : [],
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
const status = built.enabled
|
|
849
|
+
? "enabled"
|
|
850
|
+
: configured
|
|
851
|
+
? "disabled"
|
|
852
|
+
: "absent";
|
|
853
|
+
return { config: built, status, configured, issues: [] };
|
|
854
|
+
}
|
|
855
|
+
export function loadRemoteOAuthConfig(logger = noopLogger, env = process.env) {
|
|
856
|
+
return diagnoseRemoteOAuthConfig(logger, env).config;
|
|
857
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type RemoteHttpOAuthReadiness } from "./doctor.js";
|
|
2
|
+
import type { RemoteOAuthConfig } from "./auth.js";
|
|
3
|
+
export declare const CONNECTOR_SETUP_SECRET_WARNING = "Never paste gateway bearer tokens, OAuth client secrets, OAuth access tokens, consent/shared secrets, tunnel tokens, or provider credentials into a remote chat transcript. Only the fields in this packet are safe to paste into a connector UI.";
|
|
4
|
+
export interface ConnectorSetupOptions {
|
|
5
|
+
clientId?: string;
|
|
6
|
+
includeLegacyNoAuth?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ConnectorSetupPacket {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
schema: "remote-connector-setup.v1";
|
|
11
|
+
ready: boolean;
|
|
12
|
+
stage: RemoteHttpOAuthReadiness["stage"];
|
|
13
|
+
auth_mode: RemoteHttpOAuthReadiness["auth_mode"];
|
|
14
|
+
connector: {
|
|
15
|
+
mcp_url: string | null;
|
|
16
|
+
authorization_url: string | null;
|
|
17
|
+
token_url: string | null;
|
|
18
|
+
client_id: string | null;
|
|
19
|
+
client_secret_required: boolean;
|
|
20
|
+
client_secret_source: string | null;
|
|
21
|
+
};
|
|
22
|
+
workspace: RemoteHttpOAuthReadiness["workspace"];
|
|
23
|
+
next_actions: string[];
|
|
24
|
+
warnings: string[];
|
|
25
|
+
legacy_no_auth?: {
|
|
26
|
+
deprecated: true;
|
|
27
|
+
connector_url: string | null;
|
|
28
|
+
note: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function buildConnectorSetupPacket(input: {
|
|
32
|
+
readiness: RemoteHttpOAuthReadiness;
|
|
33
|
+
oauth: RemoteOAuthConfig;
|
|
34
|
+
options?: ConnectorSetupOptions;
|
|
35
|
+
legacyNoAuthUrl?: string | null;
|
|
36
|
+
}): ConnectorSetupPacket;
|
|
37
|
+
export declare function legacyNoAuthConnectorUrl(env?: NodeJS.ProcessEnv): string | null;
|
|
38
|
+
export declare function gatherConnectorSetupPacket(options?: ConnectorSetupOptions, env?: NodeJS.ProcessEnv): ConnectorSetupPacket;
|
|
39
|
+
export declare function renderConnectorSetupSummary(packet: ConnectorSetupPacket): string;
|