@roamcode.ai/server 1.0.0 → 1.0.2

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 (99) hide show
  1. package/dist/index.d.ts +2406 -82
  2. package/dist/index.js +8324 -44
  3. package/dist/managed-update-helper.js +631 -26
  4. package/dist/mcp-send.js +76 -90
  5. package/dist/start.d.ts +591 -9
  6. package/dist/start.js +7629 -353
  7. package/package.json +3 -3
  8. package/dist/auth.d.ts +0 -63
  9. package/dist/auth.js +0 -133
  10. package/dist/claude-auth-service.d.ts +0 -76
  11. package/dist/claude-auth-service.js +0 -217
  12. package/dist/claude-latest-service.d.ts +0 -34
  13. package/dist/claude-latest-service.js +0 -61
  14. package/dist/config.d.ts +0 -78
  15. package/dist/config.js +0 -59
  16. package/dist/data-dir.d.ts +0 -42
  17. package/dist/data-dir.js +0 -70
  18. package/dist/diag.d.ts +0 -43
  19. package/dist/diag.js +0 -83
  20. package/dist/fs-service.d.ts +0 -90
  21. package/dist/fs-service.js +0 -290
  22. package/dist/managed-runtime.d.ts +0 -51
  23. package/dist/managed-runtime.js +0 -411
  24. package/dist/managed-update-helper.d.ts +0 -2
  25. package/dist/mcp-send.d.ts +0 -33
  26. package/dist/origin-check.d.ts +0 -37
  27. package/dist/origin-check.js +0 -101
  28. package/dist/pane-status.d.ts +0 -61
  29. package/dist/pane-status.js +0 -145
  30. package/dist/providers/claude-metadata-service.d.ts +0 -58
  31. package/dist/providers/claude-metadata-service.js +0 -352
  32. package/dist/providers/claude-provider.d.ts +0 -11
  33. package/dist/providers/claude-provider.js +0 -166
  34. package/dist/providers/codex-activity.d.ts +0 -21
  35. package/dist/providers/codex-activity.js +0 -122
  36. package/dist/providers/codex-app-server-client.d.ts +0 -90
  37. package/dist/providers/codex-app-server-client.js +0 -485
  38. package/dist/providers/codex-latest-service.d.ts +0 -50
  39. package/dist/providers/codex-latest-service.js +0 -174
  40. package/dist/providers/codex-metadata-service.d.ts +0 -161
  41. package/dist/providers/codex-metadata-service.js +0 -686
  42. package/dist/providers/codex-profile-client.d.ts +0 -16
  43. package/dist/providers/codex-profile-client.js +0 -52
  44. package/dist/providers/codex-profile-security.d.ts +0 -23
  45. package/dist/providers/codex-profile-security.js +0 -161
  46. package/dist/providers/codex-provider.d.ts +0 -15
  47. package/dist/providers/codex-provider.js +0 -174
  48. package/dist/providers/codex-thread-coordinator.d.ts +0 -18
  49. package/dist/providers/codex-thread-coordinator.js +0 -93
  50. package/dist/providers/codex-thread-persistence.d.ts +0 -9
  51. package/dist/providers/codex-thread-persistence.js +0 -45
  52. package/dist/providers/codex-thread-resolver.d.ts +0 -59
  53. package/dist/providers/codex-thread-resolver.js +0 -322
  54. package/dist/providers/options.d.ts +0 -7
  55. package/dist/providers/options.js +0 -155
  56. package/dist/providers/provider-artifacts.d.ts +0 -3
  57. package/dist/providers/provider-artifacts.js +0 -30
  58. package/dist/providers/registry.d.ts +0 -7
  59. package/dist/providers/registry.js +0 -23
  60. package/dist/providers/types.d.ts +0 -95
  61. package/dist/providers/types.js +0 -8
  62. package/dist/push-dispatch.d.ts +0 -81
  63. package/dist/push-dispatch.js +0 -100
  64. package/dist/push-store.d.ts +0 -25
  65. package/dist/push-store.js +0 -79
  66. package/dist/rate-limit.d.ts +0 -52
  67. package/dist/rate-limit.js +0 -72
  68. package/dist/server-config.d.ts +0 -60
  69. package/dist/server-config.js +0 -77
  70. package/dist/service-install.d.ts +0 -60
  71. package/dist/service-install.js +0 -221
  72. package/dist/session-defaults.d.ts +0 -26
  73. package/dist/session-defaults.js +0 -60
  74. package/dist/session-store.d.ts +0 -81
  75. package/dist/session-store.js +0 -654
  76. package/dist/static-routes.d.ts +0 -101
  77. package/dist/static-routes.js +0 -188
  78. package/dist/terminal-capability.d.ts +0 -5
  79. package/dist/terminal-capability.js +0 -27
  80. package/dist/terminal-manager.d.ts +0 -224
  81. package/dist/terminal-manager.js +0 -917
  82. package/dist/terminal-process.d.ts +0 -85
  83. package/dist/terminal-process.js +0 -238
  84. package/dist/terminal-shared.d.ts +0 -36
  85. package/dist/terminal-shared.js +0 -43
  86. package/dist/tmux-list.d.ts +0 -11
  87. package/dist/tmux-list.js +0 -39
  88. package/dist/transport.d.ts +0 -123
  89. package/dist/transport.js +0 -1559
  90. package/dist/updater.d.ts +0 -161
  91. package/dist/updater.js +0 -451
  92. package/dist/usage-service.d.ts +0 -118
  93. package/dist/usage-service.js +0 -173
  94. package/dist/vapid.d.ts +0 -17
  95. package/dist/vapid.js +0 -31
  96. package/dist/web-push-send.d.ts +0 -20
  97. package/dist/web-push-send.js +0 -21
  98. package/dist/ws-ticket.d.ts +0 -47
  99. package/dist/ws-ticket.js +0 -62
@@ -1,686 +0,0 @@
1
- import { isAbsolute } from "node:path";
2
- import { z } from "zod";
3
- import { captureLaunchConfigFingerprint, listSecureProfileNames, resolveSecureCodexHome, sameLaunchConfigFingerprint, } from "./codex-profile-security.js";
4
- import { ProviderError } from "./types.js";
5
- import { isCodexProfileClientLifecycle } from "./codex-profile-client.js";
6
- export const CODEX_METADATA_ERROR_CODE = "CODEX_METADATA_UNAVAILABLE";
7
- export const CODEX_MODEL_CATALOG_ERROR_CODE = "CODEX_MODEL_CATALOG_UNAVAILABLE";
8
- export const OSS_PROVIDER_DEFERRED = "OSS_PROVIDER_DEFERRED";
9
- const DEFAULT_MODEL_CACHE_TTL_MS = 60_000;
10
- const DEFAULT_CAPABILITY_CACHE_TTL_MS = 60_000;
11
- const DEFAULT_CAPABILITY_TIMEOUT_MS = 15_000;
12
- const DEFAULT_LOGIN_TTL_MS = 10 * 60_000;
13
- const DEFAULT_MAX_MODEL_PAGES = 20;
14
- const DEFAULT_MAX_MODEL_ITEMS = 500;
15
- const MODEL_PAGE_SIZE = 100;
16
- const MAX_PROFILE_NAME = 128;
17
- const MAX_LOGIN_ID = 256;
18
- const MAX_USER_CODE = 256;
19
- const MAX_URL = 2_048;
20
- const MAX_CURSOR = 2_048;
21
- const CODEX_BASELINE_REASONING = new Set(["minimal", "low", "medium", "high", "xhigh"]);
22
- export class CodexMetadataServiceError extends Error {
23
- code = CODEX_METADATA_ERROR_CODE;
24
- constructor(message = "Codex metadata is unavailable") {
25
- super(message);
26
- this.name = "CodexMetadataServiceError";
27
- }
28
- }
29
- export class CodexModelCatalogError extends Error {
30
- code = CODEX_MODEL_CATALOG_ERROR_CODE;
31
- constructor() {
32
- super("Codex model catalog is unavailable");
33
- this.name = "CodexModelCatalogError";
34
- }
35
- }
36
- export class CodexProfileCapabilityError extends Error {
37
- code = OSS_PROVIDER_DEFERRED;
38
- constructor() {
39
- super("This Codex profile uses a provider that is not supported yet");
40
- this.name = "CodexProfileCapabilityError";
41
- }
42
- }
43
- const BoundedToken = z
44
- .string()
45
- .min(1)
46
- .max(256)
47
- .refine((value) => !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(value));
48
- const ProfileToken = z
49
- .string()
50
- .min(1)
51
- .max(MAX_PROFILE_NAME)
52
- .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
53
- const CwdToken = z
54
- .string()
55
- .min(1)
56
- .max(4_096)
57
- .refine((value) => isAbsolute(value) && !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(value));
58
- const AccountResponseSchema = z.object({
59
- account: z
60
- .union([
61
- z.object({ type: z.literal("apiKey") }),
62
- z.object({ type: z.literal("chatgpt"), email: z.string().nullable(), planType: z.string().min(1).max(128) }),
63
- z.object({ type: z.literal("amazonBedrock") }),
64
- ])
65
- .nullable(),
66
- requiresOpenaiAuth: z.boolean(),
67
- });
68
- const DeviceLoginResponseSchema = z.object({
69
- type: z.literal("chatgptDeviceCode"),
70
- loginId: z
71
- .string()
72
- .min(1)
73
- .max(MAX_LOGIN_ID)
74
- .refine((value) => !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(value)),
75
- userCode: z
76
- .string()
77
- .min(1)
78
- .max(MAX_USER_CODE)
79
- .refine((value) => !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(value)),
80
- verificationUrl: z
81
- .string()
82
- .url()
83
- .max(MAX_URL)
84
- .refine((value) => new URL(value).protocol === "https:"),
85
- });
86
- const LoginCompletedSchema = z.object({
87
- loginId: z.string().min(1).max(MAX_LOGIN_ID).nullable().optional(),
88
- success: z.boolean(),
89
- error: z.string().nullable().optional(),
90
- });
91
- const CancelLoginResponseSchema = z.object({ status: z.enum(["canceled", "notFound"]) });
92
- const ReasoningOptionSchema = z.object({
93
- reasoningEffort: BoundedToken,
94
- description: z.string().max(4_096),
95
- });
96
- const ModelSchema = z.object({
97
- id: BoundedToken,
98
- model: BoundedToken,
99
- displayName: z.string().min(1).max(512),
100
- description: z.string().max(8_192),
101
- hidden: z.boolean(),
102
- isDefault: z.boolean(),
103
- supportedReasoningEfforts: z.array(ReasoningOptionSchema).min(1).max(32),
104
- defaultReasoningEffort: BoundedToken,
105
- });
106
- const ModelListResponseSchema = z.object({
107
- data: z.array(ModelSchema).max(DEFAULT_MAX_MODEL_ITEMS),
108
- nextCursor: z.string().min(1).max(MAX_CURSOR).nullable().optional(),
109
- });
110
- const RateLimitWindowSchema = z.object({
111
- usedPercent: z.number().int().safe(),
112
- resetsAt: z.number().int().safe().nonnegative().nullable().optional(),
113
- windowDurationMins: z.number().int().safe().nonnegative().nullable().optional(),
114
- });
115
- const CreditsSchema = z.object({
116
- hasCredits: z.boolean(),
117
- unlimited: z.boolean(),
118
- balance: z.string().max(256).nullable().optional(),
119
- });
120
- const RateLimitSnapshotSchema = z.object({
121
- limitId: z.string().min(1).max(256).nullable().optional(),
122
- limitName: z.string().min(1).max(256).nullable().optional(),
123
- primary: RateLimitWindowSchema.nullable().optional(),
124
- secondary: RateLimitWindowSchema.nullable().optional(),
125
- credits: CreditsSchema.nullable().optional(),
126
- });
127
- const RateLimitsResponseSchema = z.object({
128
- rateLimits: RateLimitSnapshotSchema,
129
- rateLimitsByLimitId: z.record(z.string().min(1).max(256), RateLimitSnapshotSchema).nullable().optional(),
130
- rateLimitResetCredits: z
131
- .object({
132
- availableCount: z.number().int().safe().nonnegative(),
133
- credits: z.array(z.unknown()).nullable().optional(),
134
- })
135
- .nullable()
136
- .optional(),
137
- });
138
- const ConfigReadResponseSchema = z.object({
139
- config: z.object({ model_provider: z.string().min(1).max(256).nullable().optional() }),
140
- origins: z.record(z.string(), z.unknown()),
141
- });
142
- function positiveInteger(value, fallback) {
143
- return value !== undefined && Number.isSafeInteger(value) && value > 0 ? value : fallback;
144
- }
145
- function cloneModels(models) {
146
- return models.map((model) => ({
147
- ...model,
148
- reasoningOptions: model.reasoningOptions.map((option) => ({ ...option })),
149
- supportedReasoningEfforts: [...model.supportedReasoningEfforts],
150
- }));
151
- }
152
- function metadataUnavailable() {
153
- return new CodexMetadataServiceError();
154
- }
155
- function catalogUnavailable() {
156
- return new CodexModelCatalogError();
157
- }
158
- function safeMilliseconds(value, multiplier) {
159
- const result = value * multiplier;
160
- return Number.isSafeInteger(result) ? result : undefined;
161
- }
162
- export class CodexMetadataService {
163
- rpc;
164
- now;
165
- loginTtlMs;
166
- modelCacheTtlMs;
167
- capabilityCacheTtlMs;
168
- capabilityTimeoutMs;
169
- maxModelPages;
170
- maxModelItems;
171
- codexHome;
172
- profileClient;
173
- activeLogins = new Map();
174
- earlyLoginCompletions = new Map();
175
- retiredLoginIds = new Map();
176
- loginOutcomes = new Map();
177
- activeStartGeneration;
178
- nextStartGeneration = 1;
179
- startingLogin;
180
- modelCache;
181
- modelRequest;
182
- lastUsageDiagnostic;
183
- lastCapabilityDiagnostic;
184
- capabilityCache;
185
- capabilityRequest;
186
- unsubscribeNotification;
187
- disposed = false;
188
- constructor(rpc, options = {}) {
189
- this.rpc = rpc;
190
- this.now = options.now ?? (() => Date.now());
191
- this.loginTtlMs = positiveInteger(options.loginTtlMs, DEFAULT_LOGIN_TTL_MS);
192
- this.modelCacheTtlMs = positiveInteger(options.modelCacheTtlMs, DEFAULT_MODEL_CACHE_TTL_MS);
193
- this.capabilityCacheTtlMs = positiveInteger(options.capabilityCacheTtlMs, DEFAULT_CAPABILITY_CACHE_TTL_MS);
194
- this.capabilityTimeoutMs = positiveInteger(options.capabilityTimeoutMs, DEFAULT_CAPABILITY_TIMEOUT_MS);
195
- this.maxModelPages = positiveInteger(options.maxModelPages, DEFAULT_MAX_MODEL_PAGES);
196
- this.maxModelItems = positiveInteger(options.maxModelItems, DEFAULT_MAX_MODEL_ITEMS);
197
- this.codexHome = options.codexHome;
198
- this.profileClient = options.profileClient;
199
- this.unsubscribeNotification = this.rpc.onNotification((notification) => this.onNotification(notification));
200
- }
201
- get diagnostics() {
202
- return {
203
- ...(this.lastUsageDiagnostic ? { usage: { ...this.lastUsageDiagnostic } } : {}),
204
- ...(this.lastCapabilityDiagnostic ? { capabilities: { ...this.lastCapabilityDiagnostic } } : {}),
205
- };
206
- }
207
- async probeCapabilities(cwd, readThreadInventory) {
208
- if (this.disposed || !CwdToken.safeParse(cwd).success || typeof readThreadInventory !== "function")
209
- return false;
210
- const now = this.now();
211
- if (this.capabilityCache?.cwd === cwd && now - this.capabilityCache.at < this.capabilityCacheTtlMs) {
212
- return this.capabilityCache.available;
213
- }
214
- if (this.capabilityRequest?.cwd === cwd)
215
- return this.capabilityRequest.request;
216
- const request = this.loadCapabilities(cwd, readThreadInventory);
217
- this.capabilityRequest = { cwd, request };
218
- try {
219
- const available = await request;
220
- this.capabilityCache = { at: this.now(), cwd, available };
221
- return available;
222
- }
223
- finally {
224
- if (this.capabilityRequest?.request === request)
225
- this.capabilityRequest = undefined;
226
- }
227
- }
228
- async loadCapabilities(cwd, readThreadInventory) {
229
- let timer;
230
- try {
231
- const [, , usage, , threads] = await Promise.race([
232
- Promise.all([
233
- this.getAccount(),
234
- this.getModels(),
235
- this.getUsage(),
236
- this.rpc.request("config/read", { cwd, includeLayers: false }, ConfigReadResponseSchema),
237
- readThreadInventory(),
238
- ]),
239
- new Promise((_, reject) => {
240
- timer = setTimeout(() => reject(metadataUnavailable()), this.capabilityTimeoutMs);
241
- timer.unref?.();
242
- }),
243
- ]);
244
- if (usage === null || !Array.isArray(threads))
245
- throw metadataUnavailable();
246
- this.lastCapabilityDiagnostic = undefined;
247
- return true;
248
- }
249
- catch {
250
- this.lastCapabilityDiagnostic = {
251
- code: CODEX_METADATA_ERROR_CODE,
252
- message: "Codex metadata capabilities are unavailable",
253
- };
254
- return false;
255
- }
256
- finally {
257
- if (timer)
258
- clearTimeout(timer);
259
- }
260
- }
261
- async getAccount() {
262
- let response;
263
- try {
264
- response = await this.rpc.request("account/read", { refreshToken: false }, AccountResponseSchema);
265
- }
266
- catch {
267
- throw metadataUnavailable();
268
- }
269
- if (response.account?.type === "apiKey")
270
- return { authenticated: true, authMethod: "apiKey" };
271
- if (response.account?.type === "chatgpt") {
272
- return { authenticated: true, authMethod: "chatgpt", plan: response.account.planType };
273
- }
274
- return { authenticated: false, authMethod: "none" };
275
- }
276
- startDeviceLogin() {
277
- if (this.disposed)
278
- return Promise.reject(metadataUnavailable());
279
- if (this.startingLogin)
280
- return this.startingLogin;
281
- const existing = this.activeLogins.values().next().value;
282
- if (existing)
283
- return Promise.resolve(existing.view);
284
- const generation = this.nextStartGeneration++;
285
- this.activeStartGeneration = generation;
286
- const request = this.rpc
287
- .request("account/login/start", { type: "chatgptDeviceCode" }, DeviceLoginResponseSchema)
288
- .then((response) => this.installLogin(response, generation))
289
- .catch(() => {
290
- throw metadataUnavailable();
291
- });
292
- this.startingLogin = request;
293
- void request.then(() => {
294
- if (this.startingLogin === request)
295
- this.finishStartGeneration(generation);
296
- }, () => {
297
- if (this.startingLogin === request)
298
- this.finishStartGeneration(generation);
299
- });
300
- return request;
301
- }
302
- cancelLogin(loginId) {
303
- if (!DeviceLoginResponseSchema.shape.loginId.safeParse(loginId).success) {
304
- return Promise.reject(metadataUnavailable());
305
- }
306
- const active = this.activeLogins.get(loginId);
307
- if (!active || active.settled)
308
- return Promise.resolve({ status: "notFound" });
309
- if (active.cancelPromise)
310
- return active.cancelPromise;
311
- const request = this.rpc
312
- .request("account/login/cancel", { loginId }, CancelLoginResponseSchema)
313
- .then((response) => {
314
- if (response.status === "canceled" || response.status === "notFound") {
315
- this.settleLogin(loginId, { status: "canceled" });
316
- }
317
- return response;
318
- })
319
- .catch(() => {
320
- if (active.cancelPromise === request)
321
- active.cancelPromise = undefined;
322
- throw metadataUnavailable();
323
- });
324
- active.cancelPromise = request;
325
- return request;
326
- }
327
- getLoginStatus(loginId) {
328
- if (!DeviceLoginResponseSchema.shape.loginId.safeParse(loginId).success)
329
- throw metadataUnavailable();
330
- const active = this.activeLogins.get(loginId);
331
- if (active && !active.settled)
332
- return { status: "pending" };
333
- this.pruneLoginOutcomes();
334
- return this.loginOutcomes.get(loginId)?.completion ?? { status: "notFound" };
335
- }
336
- dispose() {
337
- if (this.disposed)
338
- return;
339
- this.disposed = true;
340
- this.unsubscribeNotification();
341
- this.earlyLoginCompletions.clear();
342
- for (const loginId of [...this.activeLogins.keys()]) {
343
- this.settleLogin(loginId, { status: "canceled" });
344
- void this.rpc.request("account/login/cancel", { loginId }, CancelLoginResponseSchema).catch(() => { });
345
- }
346
- }
347
- async getModels(force = false) {
348
- const now = this.now();
349
- if (!force && this.modelCache && now - this.modelCache.at < this.modelCacheTtlMs) {
350
- return cloneModels(this.modelCache.models);
351
- }
352
- if (this.modelRequest)
353
- return cloneModels(await this.modelRequest);
354
- const request = this.loadModels();
355
- this.modelRequest = request;
356
- try {
357
- const models = await request;
358
- this.modelCache = { at: this.now(), models: cloneModels(models) };
359
- return cloneModels(models);
360
- }
361
- finally {
362
- if (this.modelRequest === request)
363
- this.modelRequest = undefined;
364
- }
365
- }
366
- async validateModelSelection(model, reasoningEffort) {
367
- const models = await this.getModels();
368
- const selected = models.find((candidate) => candidate.value === model);
369
- // Codex intentionally accepts bounded custom model identifiers not advertised by the current catalog.
370
- // The parser remains the trust boundary for token shape/length. With a healthy catalog, custom models
371
- // may use only the stable baseline; newly advertised additive values require a known model association.
372
- if (!selected) {
373
- if (reasoningEffort === undefined || CODEX_BASELINE_REASONING.has(reasoningEffort))
374
- return;
375
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Invalid Codex custom model reasoning selection");
376
- }
377
- if (reasoningEffort !== undefined && !selected.supportedReasoningEfforts.includes(reasoningEffort)) {
378
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Invalid Codex model and reasoning selection");
379
- }
380
- }
381
- async getUsage() {
382
- try {
383
- const response = await this.rpc.request("account/rateLimits/read", {}, RateLimitsResponseSchema);
384
- const bars = [];
385
- this.appendSnapshotBars(bars, response.rateLimits);
386
- const primaryId = response.rateLimits.limitId;
387
- for (const [id, snapshot] of Object.entries(response.rateLimitsByLimitId ?? {})) {
388
- if (id === primaryId || snapshot.limitId === primaryId)
389
- continue;
390
- this.appendSnapshotBars(bars, { ...snapshot, limitId: snapshot.limitId ?? id });
391
- }
392
- const credits = response.rateLimits.credits;
393
- const resetCreditsAvailable = response.rateLimitResetCredits?.availableCount;
394
- const normalizedCredits = credits || resetCreditsAvailable !== undefined
395
- ? {
396
- hasCredits: credits?.hasCredits ?? false,
397
- unlimited: credits?.unlimited ?? false,
398
- ...(credits?.balance ? { balance: credits.balance } : {}),
399
- ...(resetCreditsAvailable !== undefined ? { resetCreditsAvailable } : {}),
400
- }
401
- : undefined;
402
- this.lastUsageDiagnostic = undefined;
403
- return {
404
- bars,
405
- ...(normalizedCredits ? { credits: normalizedCredits } : {}),
406
- fetchedAt: this.now(),
407
- };
408
- }
409
- catch {
410
- this.lastUsageDiagnostic = {
411
- code: CODEX_METADATA_ERROR_CODE,
412
- message: "Codex usage is unavailable",
413
- };
414
- return null;
415
- }
416
- }
417
- async listProfiles() {
418
- try {
419
- const home = await resolveSecureCodexHome(this.codexHome);
420
- if (!home)
421
- return [];
422
- return await listSecureProfileNames(home, (profile) => ProfileToken.safeParse(profile).success);
423
- }
424
- catch {
425
- return [];
426
- }
427
- }
428
- validateProfile = async (profile, cwd) => {
429
- if (!ProfileToken.safeParse(profile).success || !CwdToken.safeParse(cwd).success) {
430
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Invalid Codex profile selection");
431
- }
432
- const codexHome = await resolveSecureCodexHome(this.codexHome);
433
- const before = codexHome ? await captureLaunchConfigFingerprint(codexHome, profile) : undefined;
434
- if (!codexHome || !before) {
435
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Invalid Codex profile selection");
436
- }
437
- if (!isCodexProfileClientLifecycle(this.profileClient))
438
- throw new CodexProfileCapabilityError();
439
- const response = await this.readProfileConfig(profile, codexHome, cwd);
440
- const after = await captureLaunchConfigFingerprint(codexHome, profile);
441
- if (!sameLaunchConfigFingerprint(before, after) || response.config.model_provider !== "openai") {
442
- throw new CodexProfileCapabilityError();
443
- }
444
- return {
445
- profile,
446
- codexHome,
447
- assertUnchanged: async () => {
448
- const currentHome = await resolveSecureCodexHome(this.codexHome);
449
- if (currentHome !== codexHome)
450
- throw new CodexProfileCapabilityError();
451
- const finalResponse = await this.readProfileConfig(profile, codexHome, cwd);
452
- const current = await captureLaunchConfigFingerprint(codexHome, profile);
453
- if (!sameLaunchConfigFingerprint(before, current) || finalResponse.config.model_provider !== "openai") {
454
- throw new CodexProfileCapabilityError();
455
- }
456
- },
457
- };
458
- };
459
- async readProfileConfig(profile, codexHome, cwd) {
460
- try {
461
- return await this.profileClient.readConfig(profile, codexHome, cwd, ConfigReadResponseSchema);
462
- }
463
- catch {
464
- throw new CodexProfileCapabilityError();
465
- }
466
- }
467
- installLogin(response, generation) {
468
- if (this.disposed) {
469
- void this.rpc
470
- .request("account/login/cancel", { loginId: response.loginId }, CancelLoginResponseSchema)
471
- .catch(() => { });
472
- throw metadataUnavailable();
473
- }
474
- this.pruneRetiredLoginIds();
475
- if (this.retiredLoginIds.has(response.loginId))
476
- throw metadataUnavailable();
477
- const expiresAt = this.now() + this.loginTtlMs;
478
- let settle;
479
- const completion = new Promise((resolve) => {
480
- settle = resolve;
481
- });
482
- const view = {
483
- loginId: response.loginId,
484
- userCode: response.userCode,
485
- verificationUrl: response.verificationUrl,
486
- expiresAt,
487
- completion,
488
- };
489
- const timer = setTimeout(() => this.expireLogin(response.loginId), this.loginTtlMs);
490
- timer.unref?.();
491
- const active = { view, settle, timer, settled: false };
492
- this.activeLogins.set(response.loginId, active);
493
- const early = this.earlyLoginCompletions.get(response.loginId);
494
- if (early && early.generation === generation && this.now() - early.at <= this.loginTtlMs) {
495
- this.earlyLoginCompletions.delete(response.loginId);
496
- this.settleLogin(response.loginId, early.completion);
497
- }
498
- return view;
499
- }
500
- onNotification(notification) {
501
- if (notification.method !== "account/login/completed")
502
- return;
503
- const parsed = LoginCompletedSchema.safeParse(notification.params);
504
- if (!parsed.success || !parsed.data.loginId)
505
- return;
506
- const completion = parsed.data.success ? { status: "completed" } : { status: "failed" };
507
- if (this.activeLogins.has(parsed.data.loginId)) {
508
- this.settleLogin(parsed.data.loginId, completion);
509
- return;
510
- }
511
- if (this.activeStartGeneration === undefined)
512
- return;
513
- this.pruneEarlyLoginCompletions();
514
- this.pruneRetiredLoginIds();
515
- if (this.retiredLoginIds.has(parsed.data.loginId))
516
- return;
517
- if (this.earlyLoginCompletions.size >= 16) {
518
- const oldest = this.earlyLoginCompletions.keys().next().value;
519
- if (oldest) {
520
- this.earlyLoginCompletions.delete(oldest);
521
- this.retireLoginId(oldest);
522
- }
523
- }
524
- this.earlyLoginCompletions.set(parsed.data.loginId, {
525
- completion,
526
- generation: this.activeStartGeneration,
527
- at: this.now(),
528
- });
529
- }
530
- settleLogin(loginId, completion) {
531
- const active = this.activeLogins.get(loginId);
532
- if (!active || active.settled)
533
- return;
534
- active.settled = true;
535
- clearTimeout(active.timer);
536
- this.activeLogins.delete(loginId);
537
- this.recordLoginOutcome(loginId, completion);
538
- this.retireLoginId(loginId);
539
- active.settle(completion);
540
- }
541
- finishStartGeneration(generation) {
542
- this.startingLogin = undefined;
543
- if (this.activeStartGeneration === generation)
544
- this.activeStartGeneration = undefined;
545
- for (const [loginId, early] of this.earlyLoginCompletions) {
546
- if (early.generation !== generation)
547
- continue;
548
- this.earlyLoginCompletions.delete(loginId);
549
- this.retireLoginId(loginId);
550
- }
551
- }
552
- retireLoginId(loginId) {
553
- this.retiredLoginIds.delete(loginId);
554
- this.retiredLoginIds.set(loginId, this.now());
555
- this.pruneRetiredLoginIds();
556
- while (this.retiredLoginIds.size > 64) {
557
- const oldest = this.retiredLoginIds.keys().next().value;
558
- if (!oldest)
559
- break;
560
- this.retiredLoginIds.delete(oldest);
561
- }
562
- }
563
- pruneEarlyLoginCompletions() {
564
- for (const [loginId, early] of this.earlyLoginCompletions) {
565
- if (this.now() - early.at <= this.loginTtlMs)
566
- continue;
567
- this.earlyLoginCompletions.delete(loginId);
568
- this.retireLoginId(loginId);
569
- }
570
- }
571
- pruneRetiredLoginIds() {
572
- const oldestAllowed = this.now() - this.loginTtlMs;
573
- for (const [loginId, retiredAt] of this.retiredLoginIds) {
574
- if (retiredAt >= oldestAllowed)
575
- continue;
576
- this.retiredLoginIds.delete(loginId);
577
- }
578
- }
579
- recordLoginOutcome(loginId, completion) {
580
- this.loginOutcomes.delete(loginId);
581
- this.loginOutcomes.set(loginId, { completion, at: this.now() });
582
- this.pruneLoginOutcomes();
583
- while (this.loginOutcomes.size > 64) {
584
- const oldest = this.loginOutcomes.keys().next().value;
585
- if (!oldest)
586
- break;
587
- this.loginOutcomes.delete(oldest);
588
- }
589
- }
590
- pruneLoginOutcomes() {
591
- const oldestAllowed = this.now() - this.loginTtlMs;
592
- for (const [loginId, outcome] of this.loginOutcomes) {
593
- if (outcome.at >= oldestAllowed)
594
- continue;
595
- this.loginOutcomes.delete(loginId);
596
- }
597
- }
598
- expireLogin(loginId) {
599
- const active = this.activeLogins.get(loginId);
600
- if (!active || active.settled)
601
- return;
602
- this.settleLogin(loginId, { status: "expired" });
603
- void this.rpc.request("account/login/cancel", { loginId }, CancelLoginResponseSchema).catch(() => {
604
- // Expiry is already fail-closed locally; protocol diagnostics remain redacted by the client.
605
- });
606
- }
607
- async loadModels() {
608
- const seenCursors = new Set();
609
- const seenModels = new Set();
610
- const models = [];
611
- let advertisedItems = 0;
612
- let cursor = null;
613
- try {
614
- for (let page = 0; page < this.maxModelPages; page += 1) {
615
- const response = await this.rpc.request("model/list", { cursor, includeHidden: false, limit: MODEL_PAGE_SIZE }, ModelListResponseSchema);
616
- advertisedItems += response.data.length;
617
- if (advertisedItems > this.maxModelItems)
618
- throw catalogUnavailable();
619
- for (const advertised of response.data) {
620
- const efforts = advertised.supportedReasoningEfforts.map((option) => option.reasoningEffort);
621
- if (new Set(efforts).size !== efforts.length || !efforts.includes(advertised.defaultReasoningEffort)) {
622
- throw catalogUnavailable();
623
- }
624
- if (advertised.hidden || seenModels.has(advertised.model))
625
- continue;
626
- seenModels.add(advertised.model);
627
- models.push({
628
- value: advertised.model,
629
- id: advertised.id,
630
- displayName: advertised.displayName,
631
- description: advertised.description,
632
- isDefault: advertised.isDefault,
633
- reasoningOptions: advertised.supportedReasoningEfforts.map((option) => ({
634
- value: option.reasoningEffort,
635
- description: option.description,
636
- isDefault: option.reasoningEffort === advertised.defaultReasoningEffort,
637
- })),
638
- supportedReasoningEfforts: efforts,
639
- defaultReasoningEffort: advertised.defaultReasoningEffort,
640
- });
641
- }
642
- const next = response.nextCursor ?? null;
643
- if (next === null)
644
- return models;
645
- if (seenCursors.has(next))
646
- throw catalogUnavailable();
647
- seenCursors.add(next);
648
- cursor = next;
649
- }
650
- }
651
- catch {
652
- throw catalogUnavailable();
653
- }
654
- throw catalogUnavailable();
655
- }
656
- appendSnapshotBars(bars, snapshot) {
657
- const windows = [["primary", snapshot.primary], ["secondary", snapshot.secondary]].filter((entry) => Boolean(entry[1]));
658
- for (const [kind, window] of windows) {
659
- const baseId = snapshot.limitId;
660
- const id = baseId ? `${baseId}:${kind}` : kind;
661
- const rawBaseLabel = snapshot.limitName ?? snapshot.limitId;
662
- const baseLabel = rawBaseLabel ? `${rawBaseLabel[0].toUpperCase()}${rawBaseLabel.slice(1)}` : undefined;
663
- const windowLabel = kind === "primary" ? "Primary" : "Secondary";
664
- const label = baseLabel ? (windows.length === 1 ? baseLabel : `${baseLabel} ${windowLabel}`) : windowLabel;
665
- const resetsAt = window.resetsAt === null || window.resetsAt === undefined
666
- ? undefined
667
- : safeMilliseconds(window.resetsAt, 1_000);
668
- const windowDurationMs = window.windowDurationMins === null || window.windowDurationMins === undefined
669
- ? undefined
670
- : safeMilliseconds(window.windowDurationMins, 60_000);
671
- if ((window.resetsAt !== null && window.resetsAt !== undefined && resetsAt === undefined) ||
672
- (window.windowDurationMins !== null &&
673
- window.windowDurationMins !== undefined &&
674
- windowDurationMs === undefined)) {
675
- throw metadataUnavailable();
676
- }
677
- bars.push({
678
- id,
679
- label,
680
- percent: Math.max(0, Math.min(100, window.usedPercent)),
681
- ...(resetsAt !== undefined ? { resetsAt } : {}),
682
- ...(windowDurationMs !== undefined ? { windowDurationMs } : {}),
683
- });
684
- }
685
- }
686
- }