codex-copilot-dx 0.5.6 → 0.6.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/src/doctor.mjs CHANGED
@@ -3,11 +3,17 @@ import path from "node:path";
3
3
  import os from "node:os";
4
4
  import net from "node:net";
5
5
  import { githubTokenPath, readGithubTokenMetadata, validateGithubToken } from "./auth.mjs";
6
+ import {
7
+ AUTH_PROFILE_CLAUDE,
8
+ AUTH_PROFILE_CODEX,
9
+ readAuthProfileCredentials,
10
+ } from "./auth-profile.mjs";
6
11
  import { claudeDesktopPaths } from "./claude-desktop-config.mjs";
7
12
  import { status } from "./status.mjs";
8
13
  import { buildHeaders, DEFAULT_API_BASE, FALLBACK_VSCODE_VERSION } from "./copilot.mjs";
9
14
  import { adapterBaseUrl, checkRunningAdapter } from "./running-adapter.mjs";
10
15
  import { CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
16
+ import { loadModelCache } from "./model-cache.mjs";
11
17
 
12
18
  function localGatewayBaseUrl(host, port) {
13
19
  const safeHost = String(host || "127.0.0.1");
@@ -58,8 +64,9 @@ function modelEndpoints(model) {
58
64
  return Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
59
65
  }
60
66
 
61
- export function selectCompatibilityModels(models) {
67
+ export function selectCompatibilityModels(models, { claudeModels = models } = {}) {
62
68
  const data = copilotModelData(models).filter((model) => model?.model_picker_enabled !== false);
69
+ const claudeData = copilotModelData(claudeModels).filter((model) => model?.model_picker_enabled !== false);
63
70
  const responsesCandidates = data.filter((model) => {
64
71
  const id = String(model?.id || "");
65
72
  const endpoints = modelEndpoints(model);
@@ -67,7 +74,7 @@ export function selectCompatibilityModels(models) {
67
74
  });
68
75
  const responsesOnly = [...responsesCandidates].reverse()
69
76
  .find((model) => !modelEndpoints(model).includes("/chat/completions"));
70
- const claude = data.find((model) => {
77
+ const claude = claudeData.find((model) => {
71
78
  const id = String(model?.id || "");
72
79
  const vendor = String(model?.vendor || "").toLowerCase();
73
80
  return (id.startsWith("claude-") || vendor === "anthropic")
@@ -138,6 +145,8 @@ export async function inspectAdapterCompatibility({
138
145
  port = 2026,
139
146
  fetchImpl = fetch,
140
147
  timeoutMs = 120000,
148
+ claudeMode = "inherited",
149
+ claudeModels,
141
150
  } = {}) {
142
151
  const baseUrl = localGatewayBaseUrl(connectHost(host), port);
143
152
  const checks = [];
@@ -150,7 +159,9 @@ export async function inspectAdapterCompatibility({
150
159
  });
151
160
  if (!models) return checks;
152
161
 
153
- const { responsesModel, claudeModel } = selectCompatibilityModels(models);
162
+ const { responsesModel, claudeModel } = selectCompatibilityModels(models, {
163
+ claudeModels: claudeMode === "isolated" ? (claudeModels || { data: [] }) : models,
164
+ });
154
165
  if (!responsesModel) {
155
166
  checks.push({ kind: "err", message: "Compatibility Responses check failed: no GPT model advertises /responses" });
156
167
  return checks;
@@ -197,7 +208,10 @@ export async function inspectAdapterCompatibility({
197
208
  });
198
209
 
199
210
  if (!claudeModel) {
200
- checks.push({ kind: "warn", message: "Anthropic stream compatibility skipped because no Claude chat model was advertised" });
211
+ const reason = claudeMode === "isolated"
212
+ ? "the isolated Claude model cache is unavailable or advertises no Claude chat model; restart ccdx to refresh it"
213
+ : "no Claude chat model was advertised";
214
+ checks.push({ kind: "warn", message: `Anthropic stream compatibility was not verified because ${reason}` });
201
215
  return checks;
202
216
  }
203
217
 
@@ -227,26 +241,86 @@ export function inspectGitHubToken({ home = os.homedir() } = {}) {
227
241
  return [{ kind: "ok", message: `GitHub token found${account}` }];
228
242
  }
229
243
 
244
+ export function inspectAuthProfiles({ home = os.homedir() } = {}) {
245
+ const checks = [
246
+ ...inspectGitHubToken({ home }),
247
+ { kind: "ok", message: "Codex authentication profile uses the legacy path" },
248
+ ];
249
+ try {
250
+ const claude = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
251
+ if (!claude.configured) {
252
+ checks.push({ kind: "ok", message: "Claude authentication profile is not isolated and inherits Codex" });
253
+ } else if (!claude.valid) {
254
+ checks.push({ kind: "err", message: `Claude isolated authentication profile is invalid: ${claude.reason}` });
255
+ } else {
256
+ const account = claude.identity?.login ? ` for ${claude.identity.login}` : "";
257
+ checks.push({ kind: "ok", message: `Claude isolated authentication profile is configured${account}` });
258
+ }
259
+ } catch {
260
+ checks.push({ kind: "err", message: "Claude isolated authentication profile could not be inspected" });
261
+ }
262
+ return checks;
263
+ }
264
+
265
+ function checkedDoctorProfile(profile = AUTH_PROFILE_CODEX) {
266
+ const value = String(profile || AUTH_PROFILE_CODEX).trim().toLowerCase();
267
+ if (![AUTH_PROFILE_CODEX, AUTH_PROFILE_CLAUDE, "all"].includes(value)) {
268
+ throw new Error(`Doctor profile must be codex, claude, or all: ${profile}`);
269
+ }
270
+ return value;
271
+ }
272
+
273
+ function onlineMessage(profile, message, inherited = false) {
274
+ if (profile === AUTH_PROFILE_CODEX) return message;
275
+ return `${inherited ? "Claude inherited Codex" : "Claude profile"}: ${message}`;
276
+ }
277
+
230
278
  export async function inspectGitHubTokenOnline({
231
279
  home = os.homedir(),
280
+ profile = AUTH_PROFILE_CODEX,
232
281
  fetchImpl = fetch,
233
282
  timeoutMs = 10000,
234
283
  } = {}) {
235
- const token = readText(githubTokenPath(home));
236
- if (!token.ok || !token.text.trim()) {
237
- return [{ kind: "warn", message: "Online Copilot check skipped because the GitHub token is missing" }];
284
+ const requestedProfile = checkedDoctorProfile(profile);
285
+ if (requestedProfile === "all") throw new Error("inspectGitHubTokenOnline accepts one profile at a time");
286
+ let inherited = false;
287
+ let credentials;
288
+ try {
289
+ credentials = readAuthProfileCredentials(requestedProfile, { home });
290
+ if (requestedProfile === AUTH_PROFILE_CLAUDE && !credentials.configured) {
291
+ inherited = true;
292
+ credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
293
+ }
294
+ } catch {
295
+ return [{ kind: "warn", message: onlineMessage(requestedProfile, "Online Copilot check failed while reading the local credential", inherited) }];
296
+ }
297
+
298
+ const checks = [];
299
+ if (inherited) {
300
+ checks.push({ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" });
301
+ }
302
+ if (!credentials.valid) {
303
+ const reason = requestedProfile === AUTH_PROFILE_CODEX
304
+ ? "the GitHub token is missing"
305
+ : credentials.reason;
306
+ checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check skipped because ${reason}`, inherited) });
307
+ return checks;
238
308
  }
239
309
 
240
310
  const controller = new AbortController();
241
311
  const timer = setTimeout(() => controller.abort(), timeoutMs);
242
312
  try {
243
- const validation = await validateGithubToken(token.text.trim(), {
313
+ const validation = await validateGithubToken(credentials.token, {
244
314
  fetchImpl,
245
315
  signal: controller.signal,
246
316
  });
247
317
  if (!validation.ok) {
248
318
  const statusText = validation.status ? ` (HTTP ${validation.status})` : "";
249
- return [{ kind: validation.transient ? "warn" : "err", message: `GitHub Copilot authentication failed: ${validation.reason}${statusText}` }];
319
+ checks.push({
320
+ kind: validation.transient ? "warn" : "err",
321
+ message: onlineMessage(requestedProfile, `GitHub Copilot authentication failed: ${validation.reason}${statusText}`, inherited),
322
+ });
323
+ return checks;
250
324
  }
251
325
 
252
326
  const tokenData = validation.copilotTokenData;
@@ -259,25 +333,60 @@ export async function inspectGitHubTokenOnline({
259
333
  });
260
334
  const modelResp = await fetchImpl(`${apiBase}/models`, { headers, signal: controller.signal });
261
335
  if (!modelResp.ok) {
262
- return [
263
- { kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
264
- { kind: "err", message: `Copilot models endpoint returned HTTP ${modelResp.status}` },
265
- ];
336
+ checks.push(
337
+ { kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
338
+ { kind: "err", message: onlineMessage(requestedProfile, `Copilot models endpoint returned HTTP ${modelResp.status}`, inherited) },
339
+ );
340
+ return checks;
266
341
  }
267
342
  const models = await modelResp.json();
268
343
  const data = Array.isArray(models) ? models : models?.data;
269
- return [
270
- { kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
271
- { kind: "ok", message: `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models` },
272
- ];
344
+ checks.push(
345
+ { kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
346
+ { kind: "ok", message: onlineMessage(requestedProfile, `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models`, inherited) },
347
+ );
348
+ return checks;
273
349
  } catch (e) {
274
- const reason = e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e.message;
275
- return [{ kind: "warn", message: `Online Copilot check failed: ${reason}` }];
350
+ const errorCode = e?.cause?.code || e?.code || "";
351
+ const reason = e?.name === "AbortError"
352
+ ? `timed out after ${timeoutMs}ms`
353
+ : `request failed${errorCode ? ` (${errorCode})` : ""}`;
354
+ checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check failed: ${reason}`, inherited) });
355
+ return checks;
276
356
  } finally {
277
357
  clearTimeout(timer);
278
358
  }
279
359
  }
280
360
 
361
+ export async function inspectAuthProfilesOnline({
362
+ home = os.homedir(),
363
+ profile = AUTH_PROFILE_CODEX,
364
+ fetchImpl = fetch,
365
+ timeoutMs = 10000,
366
+ } = {}) {
367
+ const selected = checkedDoctorProfile(profile);
368
+ if (selected !== "all") {
369
+ return inspectGitHubTokenOnline({ home, profile: selected, fetchImpl, timeoutMs });
370
+ }
371
+
372
+ let claudeConfigured = false;
373
+ let claudeReadFailed = false;
374
+ try {
375
+ claudeConfigured = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home }).configured;
376
+ } catch {
377
+ claudeReadFailed = true;
378
+ }
379
+ const [codexChecks, claudeChecks] = await Promise.all([
380
+ inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CODEX, fetchImpl, timeoutMs }),
381
+ claudeReadFailed
382
+ ? Promise.resolve([{ kind: "err", message: "Claude isolated authentication profile could not be inspected" }])
383
+ : claudeConfigured
384
+ ? inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CLAUDE, fetchImpl, timeoutMs })
385
+ : Promise.resolve([{ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" }]),
386
+ ]);
387
+ return [...codexChecks, ...claudeChecks];
388
+ }
389
+
281
390
  export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
282
391
  const filePath = path.join(home, ".codex", "config.toml");
283
392
  const expectedAnthropicBaseUrl = adapterBaseUrl(host, port);
@@ -402,6 +511,7 @@ export async function collectDoctorChecks({
402
511
  checkAdapterListeningFn = checkAdapterListening,
403
512
  checkRunningAdapterFn = checkRunningAdapter,
404
513
  online = false,
514
+ profile = AUTH_PROFILE_CODEX,
405
515
  compat = false,
406
516
  fetchImpl = fetch,
407
517
  onlineTimeoutMs = 10000,
@@ -409,14 +519,19 @@ export async function collectDoctorChecks({
409
519
  inspectAdapterCompatibilityFn = inspectAdapterCompatibility,
410
520
  } = {}) {
411
521
  const checks = [
412
- ...inspectGitHubToken({ home }),
522
+ ...inspectAuthProfiles({ home }),
413
523
  ...inspectCodexConfig({ home, host, port }),
414
524
  ...inspectClaudeCodeConfig({ home, host, port }),
415
525
  ...inspectClaudeAppConfig({ home, platform, env, host, port }),
416
526
  ];
417
527
 
418
528
  if (online) {
419
- checks.push(...await inspectGitHubTokenOnline({ home, fetchImpl, timeoutMs: onlineTimeoutMs }));
529
+ checks.push(...await inspectAuthProfilesOnline({
530
+ home,
531
+ profile,
532
+ fetchImpl,
533
+ timeoutMs: onlineTimeoutMs,
534
+ }));
420
535
  }
421
536
 
422
537
  let running = null;
@@ -445,7 +560,33 @@ export async function collectDoctorChecks({
445
560
  if (!running?.ok) {
446
561
  checks.push({ kind: "err", message: "Compatibility checks require a running, version-compatible codex-copilot-dx adapter" });
447
562
  } else {
448
- checks.push(...await inspectAdapterCompatibilityFn({ host, port, fetchImpl, timeoutMs: compatTimeoutMs }));
563
+ let claudeMode = "inherited";
564
+ let claudeModels;
565
+ try {
566
+ const claudeCredentials = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
567
+ if (claudeCredentials.configured) {
568
+ claudeMode = "isolated";
569
+ if (claudeCredentials.valid) {
570
+ claudeModels = loadModelCache({
571
+ home,
572
+ profile: AUTH_PROFILE_CLAUDE,
573
+ credentialFingerprint: claudeCredentials.metadata?.token_fingerprint,
574
+ });
575
+ }
576
+ }
577
+ } catch {
578
+ // A configured-but-unreadable optional profile must not borrow the
579
+ // Codex catalog. Compatibility will report the Claude path unverified.
580
+ claudeMode = "isolated";
581
+ }
582
+ checks.push(...await inspectAdapterCompatibilityFn({
583
+ host,
584
+ port,
585
+ fetchImpl,
586
+ timeoutMs: compatTimeoutMs,
587
+ claudeMode,
588
+ claudeModels,
589
+ }));
449
590
  }
450
591
  }
451
592
 
@@ -454,7 +595,12 @@ export async function collectDoctorChecks({
454
595
 
455
596
  export async function runDoctor(options = {}) {
456
597
  const log = options.log || console.log;
457
- const flags = [options.online ? "--online" : "", options.compat ? "--compat" : ""].filter(Boolean);
598
+ const profile = checkedDoctorProfile(options.profile);
599
+ const flags = [
600
+ options.online ? "--online" : "",
601
+ options.compat ? "--compat" : "",
602
+ profile !== AUTH_PROFILE_CODEX ? `--profile ${profile}` : "",
603
+ ].filter(Boolean);
458
604
  log(`${options.commandName || "ccdx"} doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
459
605
  const checks = await collectDoctorChecks(options);
460
606
  for (const check of checks) log(status(check.kind, check.message));
@@ -3,9 +3,14 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  const DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
6
+ const MODEL_CACHE_PROFILES = new Set(["codex", "claude"]);
6
7
 
7
- export function modelCachePath(home = os.homedir()) {
8
- return path.join(home, ".local", "share", "codex-copilot-dx", "models.json");
8
+ export function modelCachePath(home = os.homedir(), profile = "codex") {
9
+ if (!MODEL_CACHE_PROFILES.has(profile)) throw new Error(`Unknown model cache profile: ${profile}`);
10
+ const root = path.join(home, ".local", "share", "codex-copilot-dx");
11
+ return profile === "codex"
12
+ ? path.join(root, "models.json")
13
+ : path.join(root, "profiles", profile, "models.json");
9
14
  }
10
15
 
11
16
  function modelData(models) {
@@ -20,13 +25,17 @@ export function isValidModelList(models) {
20
25
 
21
26
  export function loadModelCache({
22
27
  home = os.homedir(),
28
+ profile = "codex",
29
+ credentialFingerprint = "",
23
30
  maxAgeMs = DEFAULT_MAX_AGE_MS,
24
31
  now = Date.now,
25
32
  } = {}) {
26
33
  try {
27
- const parsed = JSON.parse(fs.readFileSync(modelCachePath(home), "utf8"));
34
+ const parsed = JSON.parse(fs.readFileSync(modelCachePath(home, profile), "utf8"));
28
35
  const savedAt = Date.parse(parsed.saved_at);
29
36
  if (!Number.isFinite(savedAt) || now() - savedAt > maxAgeMs) return null;
37
+ const expectedFingerprint = String(credentialFingerprint || "").trim();
38
+ if (expectedFingerprint && parsed.credential_fingerprint !== expectedFingerprint) return null;
30
39
  if (!isValidModelList(parsed.models)) return null;
31
40
  return parsed.models;
32
41
  } catch {
@@ -34,13 +43,20 @@ export function loadModelCache({
34
43
  }
35
44
  }
36
45
 
37
- export function saveModelCache(models, { home = os.homedir() } = {}) {
46
+ export function saveModelCache(models, {
47
+ home = os.homedir(),
48
+ profile = "codex",
49
+ credentialFingerprint = "",
50
+ } = {}) {
38
51
  if (!isValidModelList(models)) return false;
39
- const filePath = modelCachePath(home);
52
+ const filePath = modelCachePath(home, profile);
40
53
  const tempPath = `${filePath}.${process.pid}.tmp`;
54
+ const payload = { saved_at: new Date().toISOString(), models };
55
+ const fingerprint = String(credentialFingerprint || "").trim();
56
+ if (fingerprint) payload.credential_fingerprint = fingerprint;
41
57
  fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
42
58
  try {
43
- fs.writeFileSync(tempPath, `${JSON.stringify({ saved_at: new Date().toISOString(), models })}\n`, { mode: 0o600 });
59
+ fs.writeFileSync(tempPath, `${JSON.stringify(payload)}\n`, { mode: 0o600 });
44
60
  fs.renameSync(tempPath, filePath);
45
61
  fs.chmodSync(filePath, 0o600);
46
62
  return true;
@@ -14,6 +14,10 @@ const ROUTE_NAMES = Object.freeze([
14
14
  "models",
15
15
  "messages",
16
16
  "messages_count_tokens",
17
+ "pm_models",
18
+ "pm_chat_completions",
19
+ "pm_responses",
20
+ "pm_embeddings",
17
21
  "not_found",
18
22
  ]);
19
23
 
@@ -97,6 +101,10 @@ export function classifyAdapterRoute(method, pathname) {
97
101
  if (method === "GET" && pathname === "/v1/models") return "models";
98
102
  if (method === "POST" && pathname === "/v1/messages") return "messages";
99
103
  if (method === "POST" && pathname === "/v1/messages/count_tokens") return "messages_count_tokens";
104
+ if (method === "GET" && pathname === "/pm-ccdx/models") return "pm_models";
105
+ if (method === "POST" && pathname === "/pm-ccdx/chat/completions") return "pm_chat_completions";
106
+ if (method === "POST" && pathname === "/pm-ccdx/responses") return "pm_responses";
107
+ if (method === "POST" && pathname === "/pm-ccdx/embeddings") return "pm_embeddings";
100
108
  return "not_found";
101
109
  }
102
110
 
@@ -118,8 +126,50 @@ function modelRegistryStatus(modelRegistry) {
118
126
  };
119
127
  }
120
128
 
121
- export function runtimeStatusPayload({ metrics, streamPerformance, admission, modelRegistry } = {}) {
129
+ function finiteRuntimeNumber(value, fallback = 0) {
130
+ const number = Number(value);
131
+ return Number.isFinite(number) ? number : fallback;
132
+ }
133
+
134
+ function safeClientRuntimeStatus(client) {
135
+ const runtime = typeof client?.runtimeStatus === "function"
136
+ ? client.runtimeStatus()
137
+ : copilotRuntimeStatus();
138
+ return {
139
+ token_cached: runtime?.token_cached === true,
140
+ token_expires_in_ms: runtime?.token_expires_in_ms === null
141
+ ? null
142
+ : finiteRuntimeNumber(runtime?.token_expires_in_ms, null),
143
+ token_refresh_in_flight: runtime?.token_refresh_in_flight === true,
144
+ token_refresh_backoff_ms: finiteRuntimeNumber(runtime?.token_refresh_backoff_ms),
145
+ account_bound: runtime?.account_bound === true,
146
+ upstream_host: typeof runtime?.upstream_host === "string" ? runtime.upstream_host : "unknown",
147
+ model_endpoint_cache_entries: finiteRuntimeNumber(runtime?.model_endpoint_cache_entries),
148
+ model_list_flights: finiteRuntimeNumber(runtime?.model_list_flights),
149
+ };
150
+ }
151
+
152
+ export function runtimeStatusPayload({
153
+ metrics,
154
+ streamPerformance,
155
+ admission,
156
+ modelRegistry,
157
+ codexClient,
158
+ claudeClient,
159
+ codexModelRegistry,
160
+ claudeModelRegistry,
161
+ claudeMode = "inherited",
162
+ } = {}) {
122
163
  const memory = process.memoryUsage();
164
+ const resolvedClaudeMode = claudeMode === "isolated" ? "isolated" : "inherited";
165
+ const codexRuntime = safeClientRuntimeStatus(codexClient);
166
+ const codexModels = modelRegistryStatus(codexModelRegistry || modelRegistry);
167
+ const claudeRuntime = resolvedClaudeMode === "isolated"
168
+ ? safeClientRuntimeStatus(claudeClient)
169
+ : codexRuntime;
170
+ const claudeModels = resolvedClaudeMode === "isolated"
171
+ ? modelRegistryStatus(claudeModelRegistry)
172
+ : codexModels;
123
173
  return {
124
174
  ...adapterHealthPayload(),
125
175
  uptime_ms: Math.round(process.uptime() * 1000),
@@ -135,8 +185,16 @@ export function runtimeStatusPayload({ metrics, streamPerformance, admission, mo
135
185
  admission: admission?.diagnostics?.() || admission?.stats?.() || null,
136
186
  response_history: responseHistoryStats(),
137
187
  image_optimization: imageOptimizationStats(),
138
- copilot: copilotRuntimeStatus(),
139
- models: modelRegistryStatus(modelRegistry),
188
+ copilot: codexRuntime,
189
+ models: codexModels,
190
+ profiles: {
191
+ codex: { mode: "legacy", client: codexRuntime, models: codexModels },
192
+ claude: { mode: resolvedClaudeMode, client: claudeRuntime, models: claudeModels },
193
+ },
194
+ routing: {
195
+ responses: "codex",
196
+ messages: resolvedClaudeMode === "isolated" ? "claude" : "codex",
197
+ },
140
198
  limits: {
141
199
  max_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxBodyBytes,
142
200
  max_decoded_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxDecodedBodyBytes,