@ai-devkit/agent-manager 0.28.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,343 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { CODEX_APP_SERVER_ARGS, parseUsage, probeCodexCapacity, resolveCodexAuthPath, toRateWindow } from '../../capacity/codex.js';
3
+ const checkedAt = '2026-08-20T10:00:00.000Z';
4
+ const context = {
5
+ installed: true,
6
+ checkedAt
7
+ };
8
+ function apiUsage(overrides = {}) {
9
+ return {
10
+ rate_limit: {
11
+ primary_window: {
12
+ used_percent: 20,
13
+ limit_window_seconds: 18_000,
14
+ reset_at: 1_787_220_000
15
+ },
16
+ secondary_window: {
17
+ used_percent: 60,
18
+ limit_window_seconds: 604_800,
19
+ reset_at: 1_787_824_800
20
+ },
21
+ ...overrides
22
+ },
23
+ credits: {
24
+ balance: 12.5
25
+ },
26
+ additional_rate_limits: [
27
+ {
28
+ limit_name: 'reviews',
29
+ rate_limit: {
30
+ primary_window: {
31
+ used_percent: 10,
32
+ limit_window_seconds: 3_600,
33
+ reset_at: 1_787_220_000
34
+ }
35
+ }
36
+ }
37
+ ]
38
+ };
39
+ }
40
+ describe('Codex auth resolution', ()=>{
41
+ it('uses CODEX_HOME before HOME', ()=>{
42
+ expect(resolveCodexAuthPath({
43
+ CODEX_HOME: '/custom/codex',
44
+ HOME: '/users/test'
45
+ })).toBe('/custom/codex/auth.json');
46
+ });
47
+ it('falls back to ~/.codex/auth.json', ()=>{
48
+ expect(resolveCodexAuthPath({
49
+ HOME: '/users/test'
50
+ })).toBe('/users/test/.codex/auth.json');
51
+ });
52
+ });
53
+ describe('Codex API usage mapping', ()=>{
54
+ it('converts an API window without treating missing data as zero', ()=>{
55
+ expect(toRateWindow({
56
+ used_percent: 25,
57
+ limit_window_seconds: 18_000,
58
+ reset_at: 1_787_220_000
59
+ }, 'session', 'Session')).toEqual({
60
+ id: 'session',
61
+ label: 'Session',
62
+ durationMinutes: 300,
63
+ usedPercent: 25,
64
+ resetsAt: '2026-08-20T10:00:00.000Z'
65
+ });
66
+ expect(toRateWindow({}, 'session', 'Session')).toMatchObject({
67
+ usedPercent: null
68
+ });
69
+ });
70
+ it('maps session, weekly, credits, and extra limits', ()=>{
71
+ const snapshot = parseUsage(apiUsage(), 'pat');
72
+ expect(snapshot).toMatchObject({
73
+ source: 'pat',
74
+ creditsRemaining: 12.5
75
+ });
76
+ expect(snapshot.windows).toEqual([
77
+ expect.objectContaining({
78
+ id: 'session',
79
+ durationMinutes: 300
80
+ }),
81
+ expect.objectContaining({
82
+ id: 'weekly',
83
+ durationMinutes: 10080
84
+ }),
85
+ expect.objectContaining({
86
+ id: 'reviews:primary',
87
+ durationMinutes: 60
88
+ })
89
+ ]);
90
+ });
91
+ it('represents missing limits as unavailable rather than zero', ()=>{
92
+ const snapshot = parseUsage({
93
+ credits: {}
94
+ }, 'oauth');
95
+ expect(snapshot.windows).toEqual([]);
96
+ expect(snapshot.creditsRemaining).toBeNull();
97
+ });
98
+ });
99
+ describe('tiered Codex probing', ()=>{
100
+ it('selects PAT, calls whoami then usage, and never invokes the CLI', async ()=>{
101
+ const fetch = vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
102
+ chatgpt_account_id: 'acct-1'
103
+ }), {
104
+ status: 200
105
+ })).mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), {
106
+ status: 200
107
+ }));
108
+ const rpc = vi.fn();
109
+ const result = await probeCodexCapacity({
110
+ ...context,
111
+ readFile: async ()=>JSON.stringify({
112
+ personal_access_token: 'pat-secret',
113
+ tokens: {
114
+ access_token: 'ignored-oauth',
115
+ account_id: 'ignored-account'
116
+ }
117
+ }),
118
+ fetch,
119
+ rpc
120
+ });
121
+ expect(fetch).toHaveBeenCalledTimes(2);
122
+ expect(fetch.mock.calls[0][0]).toBe('https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami');
123
+ expect(fetch.mock.calls[1][0]).toBe('https://chatgpt.com/backend-api/wham/usage');
124
+ expect(fetch.mock.calls[1][1].headers).toMatchObject({
125
+ Authorization: 'Bearer pat-secret',
126
+ 'ChatGPT-Account-Id': 'acct-1'
127
+ });
128
+ expect(rpc).not.toHaveBeenCalled();
129
+ expect(result).toMatchObject({
130
+ provider: 'codex',
131
+ available: 'yes',
132
+ creditsRemaining: 12.5,
133
+ authenticated: true
134
+ });
135
+ expect(result.windows.map((window)=>window.id)).toEqual([
136
+ 'session',
137
+ 'weekly',
138
+ 'reviews:primary'
139
+ ]);
140
+ });
141
+ it('selects a fresh OAuth token without calling whoami', async ()=>{
142
+ const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify(apiUsage()), {
143
+ status: 200
144
+ }));
145
+ const result = await probeCodexCapacity({
146
+ ...context,
147
+ readFile: async ()=>JSON.stringify({
148
+ tokens: {
149
+ access_token: 'oauth-secret',
150
+ account_id: 'acct-2',
151
+ expires_at: 1_800_000_000
152
+ }
153
+ }),
154
+ fetch,
155
+ now: ()=>new Date('2026-08-20T10:00:00.000Z')
156
+ });
157
+ expect(fetch).toHaveBeenCalledOnce();
158
+ expect(fetch.mock.calls[0][1].headers).toMatchObject({
159
+ Authorization: 'Bearer oauth-secret',
160
+ 'ChatGPT-Account-Id': 'acct-2'
161
+ });
162
+ expect(result.available).toBe('yes');
163
+ });
164
+ it.each([
165
+ [
166
+ 'missing auth file',
167
+ async ()=>{
168
+ throw Object.assign(new Error('missing'), {
169
+ code: 'ENOENT'
170
+ });
171
+ }
172
+ ],
173
+ [
174
+ 'stale OAuth token',
175
+ async ()=>JSON.stringify({
176
+ tokens: {
177
+ access_token: 'stale-secret',
178
+ account_id: 'acct',
179
+ expires_at: 1
180
+ }
181
+ })
182
+ ],
183
+ [
184
+ 'OAuth 401',
185
+ async ()=>JSON.stringify({
186
+ tokens: {
187
+ access_token: 'oauth-secret',
188
+ account_id: 'acct',
189
+ expires_at: 1_800_000_000
190
+ }
191
+ })
192
+ ]
193
+ ])('falls back to the CLI for %s', async (name, readFile)=>{
194
+ const fetch = vi.fn().mockResolvedValue(new Response('', {
195
+ status: name === 'OAuth 401' ? 401 : 200
196
+ }));
197
+ const rpc = vi.fn(async ()=>({
198
+ rateLimits: {
199
+ rateLimits: {
200
+ primary: {
201
+ usedPercent: 5,
202
+ windowDurationMins: 300,
203
+ resetsAt: null
204
+ }
205
+ }
206
+ },
207
+ account: {
208
+ account: {
209
+ type: 'chatgpt'
210
+ }
211
+ }
212
+ }));
213
+ const result = await probeCodexCapacity({
214
+ ...context,
215
+ readFile,
216
+ fetch,
217
+ rpc,
218
+ now: ()=>new Date(checkedAt)
219
+ });
220
+ expect(rpc).toHaveBeenCalledOnce();
221
+ expect(result.windows).toHaveLength(1);
222
+ });
223
+ it('falls back to CLI if PAT requests fail', async ()=>{
224
+ const rpc = vi.fn(async ()=>({
225
+ rateLimits: {},
226
+ account: {
227
+ account: null
228
+ }
229
+ }));
230
+ const result = await probeCodexCapacity({
231
+ ...context,
232
+ readFile: async ()=>JSON.stringify({
233
+ personal_access_token: 'pat-secret'
234
+ }),
235
+ fetch: vi.fn().mockRejectedValue(new Error('network failure pat-secret')),
236
+ rpc
237
+ });
238
+ expect(rpc).toHaveBeenCalledOnce();
239
+ expect(result.available).toBe('unknown');
240
+ });
241
+ it('tries fresh OAuth after a PAT request fails', async ()=>{
242
+ const fetch = vi.fn().mockRejectedValueOnce(new Error('PAT failed')).mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), {
243
+ status: 200
244
+ }));
245
+ const rpc = vi.fn();
246
+ const result = await probeCodexCapacity({
247
+ ...context,
248
+ readFile: async ()=>JSON.stringify({
249
+ personal_access_token: 'pat-secret',
250
+ tokens: {
251
+ access_token: 'oauth-secret',
252
+ account_id: 'acct',
253
+ expires_at: 1_800_000_000
254
+ }
255
+ }),
256
+ fetch,
257
+ rpc,
258
+ now: ()=>new Date(checkedAt)
259
+ });
260
+ expect(fetch).toHaveBeenCalledTimes(2);
261
+ expect(result.available).toBe('yes');
262
+ expect(rpc).not.toHaveBeenCalled();
263
+ });
264
+ it('uses hardened read-only app-server arguments and both account methods', async ()=>{
265
+ const rpc = vi.fn(async ()=>({
266
+ rateLimits: {
267
+ rateLimits: {
268
+ primary: {
269
+ usedPercent: 5,
270
+ windowDurationMins: 300,
271
+ resetsAt: null
272
+ }
273
+ }
274
+ },
275
+ account: {
276
+ account: {
277
+ type: 'chatgpt'
278
+ }
279
+ }
280
+ }));
281
+ await probeCodexCapacity({
282
+ ...context,
283
+ readFile: async ()=>'{}',
284
+ rpc
285
+ });
286
+ const messages = rpc.mock.calls[0][0];
287
+ expect(messages.map((message)=>message.method)).toEqual([
288
+ 'initialize',
289
+ 'initialized',
290
+ 'account/rateLimits/read',
291
+ 'account/read'
292
+ ]);
293
+ expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\/start/);
294
+ expect(CODEX_APP_SERVER_ARGS).toEqual([
295
+ '-s',
296
+ 'read-only',
297
+ '-a',
298
+ 'untrusted',
299
+ 'app-server'
300
+ ]);
301
+ });
302
+ it('uses account/read to distinguish logged-out CLI state', async ()=>{
303
+ const result = await probeCodexCapacity({
304
+ ...context,
305
+ readFile: async ()=>'{}',
306
+ rpc: async ()=>({
307
+ rateLimits: {},
308
+ account: {
309
+ account: null
310
+ }
311
+ })
312
+ });
313
+ expect(result).toMatchObject({
314
+ authenticated: false,
315
+ available: 'unknown'
316
+ });
317
+ });
318
+ it('never exposes tokens or raw auth content through failures', async ()=>{
319
+ const secrets = [
320
+ 'pat-secret-value',
321
+ 'oauth-secret-value',
322
+ 'refresh-secret-value'
323
+ ];
324
+ const result = await probeCodexCapacity({
325
+ ...context,
326
+ readFile: async ()=>JSON.stringify({
327
+ personal_access_token: secrets[0],
328
+ tokens: {
329
+ access_token: secrets[1],
330
+ refresh_token: secrets[2]
331
+ }
332
+ }),
333
+ fetch: vi.fn().mockRejectedValue(new Error(secrets.join(' '))),
334
+ rpc: async ()=>{
335
+ throw new Error(secrets.join(' '));
336
+ }
337
+ });
338
+ const output = JSON.stringify(result);
339
+ for (const secret of secrets)expect(output).not.toContain(secret);
340
+ });
341
+ });
342
+
343
+ //# sourceMappingURL=codex.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/codex.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport {\n CODEX_APP_SERVER_ARGS,\n parseUsage,\n probeCodexCapacity,\n resolveCodexAuthPath,\n toRateWindow\n} from '../../capacity/codex.js';\n\nconst checkedAt = '2026-08-20T10:00:00.000Z';\nconst context = { installed: true, checkedAt };\n\nfunction apiUsage(overrides: Record<string, unknown> = {}) {\n return {\n rate_limit: {\n primary_window: { used_percent: 20, limit_window_seconds: 18_000, reset_at: 1_787_220_000 },\n secondary_window: { used_percent: 60, limit_window_seconds: 604_800, reset_at: 1_787_824_800 },\n ...overrides\n },\n credits: { balance: 12.5 },\n additional_rate_limits: [{\n limit_name: 'reviews',\n rate_limit: {\n primary_window: { used_percent: 10, limit_window_seconds: 3_600, reset_at: 1_787_220_000 }\n }\n }]\n };\n}\n\ndescribe('Codex auth resolution', () => {\n it('uses CODEX_HOME before HOME', () => {\n expect(resolveCodexAuthPath({ CODEX_HOME: '/custom/codex', HOME: '/users/test' })).toBe('/custom/codex/auth.json');\n });\n\n it('falls back to ~/.codex/auth.json', () => {\n expect(resolveCodexAuthPath({ HOME: '/users/test' })).toBe('/users/test/.codex/auth.json');\n });\n});\n\ndescribe('Codex API usage mapping', () => {\n it('converts an API window without treating missing data as zero', () => {\n expect(toRateWindow({ used_percent: 25, limit_window_seconds: 18_000, reset_at: 1_787_220_000 }, 'session', 'Session')).toEqual({\n id: 'session', label: 'Session', durationMinutes: 300, usedPercent: 25,\n resetsAt: '2026-08-20T10:00:00.000Z'\n });\n expect(toRateWindow({}, 'session', 'Session')).toMatchObject({ usedPercent: null });\n });\n\n it('maps session, weekly, credits, and extra limits', () => {\n const snapshot = parseUsage(apiUsage(), 'pat');\n expect(snapshot).toMatchObject({ source: 'pat', creditsRemaining: 12.5 });\n expect(snapshot.windows).toEqual([\n expect.objectContaining({ id: 'session', durationMinutes: 300 }),\n expect.objectContaining({ id: 'weekly', durationMinutes: 10080 }),\n expect.objectContaining({ id: 'reviews:primary', durationMinutes: 60 })\n ]);\n });\n\n it('represents missing limits as unavailable rather than zero', () => {\n const snapshot = parseUsage({ credits: {} }, 'oauth');\n expect(snapshot.windows).toEqual([]);\n expect(snapshot.creditsRemaining).toBeNull();\n });\n});\n\ndescribe('tiered Codex probing', () => {\n it('selects PAT, calls whoami then usage, and never invokes the CLI', async () => {\n const fetch = vi.fn()\n .mockResolvedValueOnce(new Response(JSON.stringify({ chatgpt_account_id: 'acct-1' }), { status: 200 }))\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context, readFile: async () => JSON.stringify({\n personal_access_token: 'pat-secret',\n tokens: { access_token: 'ignored-oauth', account_id: 'ignored-account' }\n }), fetch, rpc\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(fetch.mock.calls[0][0]).toBe('https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami');\n expect(fetch.mock.calls[1][0]).toBe('https://chatgpt.com/backend-api/wham/usage');\n expect(fetch.mock.calls[1][1].headers).toMatchObject({ Authorization: 'Bearer pat-secret', 'ChatGPT-Account-Id': 'acct-1' });\n expect(rpc).not.toHaveBeenCalled();\n expect(result).toMatchObject({ provider: 'codex', available: 'yes', creditsRemaining: 12.5, authenticated: true });\n expect(result.windows.map(window => window.id)).toEqual(['session', 'weekly', 'reviews:primary']);\n });\n\n it('selects a fresh OAuth token without calling whoami', async () => {\n const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({ tokens: { access_token: 'oauth-secret', account_id: 'acct-2', expires_at: 1_800_000_000 } }),\n fetch,\n now: () => new Date('2026-08-20T10:00:00.000Z')\n });\n expect(fetch).toHaveBeenCalledOnce();\n expect(fetch.mock.calls[0][1].headers).toMatchObject({ Authorization: 'Bearer oauth-secret', 'ChatGPT-Account-Id': 'acct-2' });\n expect(result.available).toBe('yes');\n });\n\n it.each([\n ['missing auth file', async () => { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); }],\n ['stale OAuth token', async () => JSON.stringify({ tokens: { access_token: 'stale-secret', account_id: 'acct', expires_at: 1 } })],\n ['OAuth 401', async () => JSON.stringify({ tokens: { access_token: 'oauth-secret', account_id: 'acct', expires_at: 1_800_000_000 } })]\n ])('falls back to the CLI for %s', async (name, readFile) => {\n const fetch = vi.fn().mockResolvedValue(new Response('', { status: name === 'OAuth 401' ? 401 : 200 }));\n const rpc = vi.fn(async () => ({\n rateLimits: { rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } } },\n account: { account: { type: 'chatgpt' } }\n }));\n const result = await probeCodexCapacity({ ...context, readFile, fetch, rpc, now: () => new Date(checkedAt) });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.windows).toHaveLength(1);\n });\n\n it('falls back to CLI if PAT requests fail', async () => {\n const rpc = vi.fn(async () => ({ rateLimits: {}, account: { account: null } }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({ personal_access_token: 'pat-secret' }),\n fetch: vi.fn().mockRejectedValue(new Error('network failure pat-secret')),\n rpc\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.available).toBe('unknown');\n });\n\n it('tries fresh OAuth after a PAT request fails', async () => {\n const fetch = vi.fn()\n .mockRejectedValueOnce(new Error('PAT failed'))\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({\n personal_access_token: 'pat-secret',\n tokens: { access_token: 'oauth-secret', account_id: 'acct', expires_at: 1_800_000_000 }\n }),\n fetch,\n rpc,\n now: () => new Date(checkedAt)\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(result.available).toBe('yes');\n expect(rpc).not.toHaveBeenCalled();\n });\n\n it('uses hardened read-only app-server arguments and both account methods', async () => {\n const rpc = vi.fn(async () => ({\n rateLimits: { rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } } },\n account: { account: { type: 'chatgpt' } }\n }));\n await probeCodexCapacity({ ...context, readFile: async () => '{}', rpc });\n const messages = rpc.mock.calls[0][0];\n expect(messages.map(message => message.method)).toEqual([\n 'initialize', 'initialized', 'account/rateLimits/read', 'account/read'\n ]);\n expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\\/start/);\n expect(CODEX_APP_SERVER_ARGS).toEqual(['-s', 'read-only', '-a', 'untrusted', 'app-server']);\n });\n\n it('uses account/read to distinguish logged-out CLI state', async () => {\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => '{}',\n rpc: async () => ({ rateLimits: {}, account: { account: null } })\n });\n expect(result).toMatchObject({ authenticated: false, available: 'unknown' });\n });\n\n it('never exposes tokens or raw auth content through failures', async () => {\n const secrets = ['pat-secret-value', 'oauth-secret-value', 'refresh-secret-value'];\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({\n personal_access_token: secrets[0],\n tokens: { access_token: secrets[1], refresh_token: secrets[2] }\n }),\n fetch: vi.fn().mockRejectedValue(new Error(secrets.join(' '))),\n rpc: async () => { throw new Error(secrets.join(' ')); }\n });\n const output = JSON.stringify(result);\n for (const secret of secrets) expect(output).not.toContain(secret);\n });\n});\n"],"names":["describe","expect","it","vi","CODEX_APP_SERVER_ARGS","parseUsage","probeCodexCapacity","resolveCodexAuthPath","toRateWindow","checkedAt","context","installed","apiUsage","overrides","rate_limit","primary_window","used_percent","limit_window_seconds","reset_at","secondary_window","credits","balance","additional_rate_limits","limit_name","CODEX_HOME","HOME","toBe","toEqual","id","label","durationMinutes","usedPercent","resetsAt","toMatchObject","snapshot","source","creditsRemaining","windows","objectContaining","toBeNull","fetch","fn","mockResolvedValueOnce","Response","JSON","stringify","chatgpt_account_id","status","rpc","result","readFile","personal_access_token","tokens","access_token","account_id","toHaveBeenCalledTimes","mock","calls","headers","Authorization","not","toHaveBeenCalled","provider","available","authenticated","map","window","mockResolvedValue","expires_at","now","Date","toHaveBeenCalledOnce","each","Object","assign","Error","code","name","rateLimits","primary","windowDurationMins","account","type","toHaveLength","mockRejectedValue","mockRejectedValueOnce","messages","message","method","toMatch","secrets","refresh_token","join","output","secret","toContain"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getCodexCapacityReport } from '../../capacity/index.js';
3
+ const checkedAt = '2026-08-09T10:00:00.000Z';
4
+ describe('getCodexCapacityReport', ()=>{
5
+ it('checks Codex installation before probing', async ()=>{
6
+ const probe = vi.fn(async (context)=>({
7
+ provider: 'codex',
8
+ generatedAt: context.checkedAt,
9
+ authenticated: true,
10
+ available: 'yes',
11
+ windows: [],
12
+ creditsRemaining: null
13
+ }));
14
+ const report = await getCodexCapacityReport({
15
+ now: ()=>new Date(checkedAt),
16
+ path: '/usr/bin:/opt/bin',
17
+ access: async (target)=>{
18
+ if (target !== '/opt/bin/codex') throw new Error('missing');
19
+ },
20
+ probe
21
+ });
22
+ expect(probe).toHaveBeenCalledWith({
23
+ installed: true,
24
+ checkedAt
25
+ });
26
+ expect(report).toMatchObject({
27
+ provider: 'codex',
28
+ generatedAt: checkedAt,
29
+ available: 'yes'
30
+ });
31
+ });
32
+ it('redacts unexpected probe failures into a stable unknown result', async ()=>{
33
+ const report = await getCodexCapacityReport({
34
+ now: ()=>new Date(checkedAt),
35
+ path: '',
36
+ probe: async ()=>{
37
+ throw new Error('private provider response');
38
+ }
39
+ });
40
+ expect(report).toMatchObject({
41
+ provider: 'codex',
42
+ available: 'unknown',
43
+ authenticated: null,
44
+ windows: []
45
+ });
46
+ expect(JSON.stringify(report)).not.toContain('private provider response');
47
+ });
48
+ });
49
+
50
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/index.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport { getCodexCapacityReport } from '../../capacity/index.js';\n\nconst checkedAt = '2026-08-09T10:00:00.000Z';\n\ndescribe('getCodexCapacityReport', () => {\n it('checks Codex installation before probing', async () => {\n const probe = vi.fn(async context => ({\n provider: 'codex', generatedAt: context.checkedAt,\n authenticated: true, available: 'yes' as const, windows: [], creditsRemaining: null\n }));\n\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: '/usr/bin:/opt/bin',\n access: async target => {\n if (target !== '/opt/bin/codex') throw new Error('missing');\n },\n probe\n });\n\n expect(probe).toHaveBeenCalledWith({ installed: true, checkedAt });\n expect(report).toMatchObject({ provider: 'codex', generatedAt: checkedAt, available: 'yes' });\n });\n\n it('redacts unexpected probe failures into a stable unknown result', async () => {\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: '',\n probe: async () => { throw new Error('private provider response'); }\n });\n\n expect(report).toMatchObject({\n provider: 'codex', available: 'unknown', authenticated: null, windows: []\n });\n expect(JSON.stringify(report)).not.toContain('private provider response');\n });\n});\n"],"names":["describe","expect","it","vi","getCodexCapacityReport","checkedAt","probe","fn","context","provider","generatedAt","authenticated","available","windows","creditsRemaining","report","now","Date","path","access","target","Error","toHaveBeenCalledWith","installed","toMatchObject","JSON","stringify","not","toContain"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAClD,SAASC,sBAAsB,QAAQ,0BAA0B;AAEjE,MAAMC,YAAY;AAElBL,SAAS,0BAA0B;IACjCE,GAAG,4CAA4C;QAC7C,MAAMI,QAAQH,GAAGI,EAAE,CAAC,OAAMC,UAAY,CAAA;gBACpCC,UAAU;gBAASC,aAAaF,QAAQH,SAAS;gBACjDM,eAAe;gBAAMC,WAAW;gBAAgBC,SAAS,EAAE;gBAAEC,kBAAkB;YACjF,CAAA;QAEA,MAAMC,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNC,QAAQ,OAAMC;gBACZ,IAAIA,WAAW,kBAAkB,MAAM,IAAIC,MAAM;YACnD;YACAf;QACF;QAEAL,OAAOK,OAAOgB,oBAAoB,CAAC;YAAEC,WAAW;YAAMlB;QAAU;QAChEJ,OAAOc,QAAQS,aAAa,CAAC;YAAEf,UAAU;YAASC,aAAaL;YAAWO,WAAW;QAAM;IAC7F;IAEAV,GAAG,kEAAkE;QACnE,MAAMa,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNZ,OAAO;gBAAc,MAAM,IAAIe,MAAM;YAA8B;QACrE;QAEApB,OAAOc,QAAQS,aAAa,CAAC;YAC3Bf,UAAU;YAASG,WAAW;YAAWD,eAAe;YAAME,SAAS,EAAE;QAC3E;QACAZ,OAAOwB,KAAKC,SAAS,CAACX,SAASY,GAAG,CAACC,SAAS,CAAC;IAC/C;AACF"}
@@ -23,15 +23,24 @@ describe('Claude durable-agent fake-provider journey', ()=>{
23
23
  const capture = path.join(root, 'capture.jsonl');
24
24
  process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;
25
25
  const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));
26
+ const processInspector = {
27
+ getIdentity: (pid)=>({
28
+ pid,
29
+ startedAt: `process-${pid}`
30
+ })
31
+ };
26
32
  const repository = new DurableAgentRepository({
27
- dbPath: path.join(root, 'state', 'agents.db')
33
+ dbPath: path.join(root, 'state', 'agents.db'),
34
+ processInspector
28
35
  });
29
36
  const service = new ClaudePrintAgentService({
30
37
  repository,
31
38
  probe: new ClaudeCliProbe({
32
39
  executable
33
40
  }),
34
- runner: new ClaudePrintRunner(),
41
+ runner: new ClaudePrintRunner({
42
+ processInspector
43
+ }),
35
44
  executable
36
45
  });
37
46
  const created = await service.create({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/print/ClaudePrintAgent.integration.test.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport {\n ClaudeCliProbe,\n ClaudePrintAgentService,\n ClaudePrintRunner,\n DurableAgentRepository,\n} from '../../index.js';\n\nconst roots: string[] = [];\nconst originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n\nafterEach(() => {\n if (originalCapture === undefined) delete process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n else process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = originalCapture;\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\ndescribe('Claude durable-agent fake-provider journey', () => {\n it('creates without invocation, then starts and resumes the same session through stdin', async () => {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-integration-'));\n roots.push(root);\n const cwd = path.join(root, 'project');\n fs.mkdirSync(cwd);\n const capture = path.join(root, 'capture.jsonl');\n process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;\n const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));\n const repository = new DurableAgentRepository({ dbPath: path.join(root, 'state', 'agents.db') });\n const service = new ClaudePrintAgentService({\n repository,\n probe: new ClaudeCliProbe({ executable }),\n runner: new ClaudePrintRunner(),\n executable,\n });\n\n const created = await service.create({ name: 'reviewer', cwd });\n expect(fs.existsSync(capture)).toBe(false);\n\n await expect(service.send(created.id, 'first secret')).resolves.toMatchObject({ result: 'answer:first secret' });\n await expect(service.send(created.id, 'follow up')).resolves.toMatchObject({ result: 'answer:follow up' });\n\n const invocations = fs.readFileSync(capture, 'utf8').trim().split('\\n').map((line) => JSON.parse(line));\n expect(invocations[0]).toMatchObject({ prompt: 'first secret', cwd: fs.realpathSync(cwd) });\n expect(invocations[0].args).toContain('--session-id');\n expect(invocations[0].args).not.toContain('first secret');\n expect(invocations[1]).toMatchObject({ prompt: 'follow up', cwd: fs.realpathSync(cwd) });\n expect(invocations[1].args).toContain('--resume');\n expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);\n\n const persisted = await repository.getById(created.id);\n expect(persisted).toMatchObject({ state: 'ready', sessionHealth: 'healthy' });\n });\n});\n"],"names":["fs","os","path","fileURLToPath","afterEach","describe","expect","it","ClaudeCliProbe","ClaudePrintAgentService","ClaudePrintRunner","DurableAgentRepository","roots","originalCapture","process","env","AI_DEVKIT_FAKE_CLAUDE_CAPTURE","undefined","root","splice","rmSync","recursive","force","mkdtempSync","join","tmpdir","push","cwd","mkdirSync","capture","executable","URL","url","repository","dbPath","service","probe","runner","created","create","name","existsSync","toBe","send","id","resolves","toMatchObject","result","invocations","readFileSync","trim","split","map","line","JSON","parse","prompt","realpathSync","args","toContain","not","indexOf","providerSessionId","persisted","getById","state","sessionHealth"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/__tests__/print/ClaudePrintAgent.integration.test.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport {\n ClaudeCliProbe,\n ClaudePrintAgentService,\n ClaudePrintRunner,\n DurableAgentRepository,\n type ProcessInspector,\n} from '../../index.js';\n\nconst roots: string[] = [];\nconst originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n\nafterEach(() => {\n if (originalCapture === undefined) delete process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n else process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = originalCapture;\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\ndescribe('Claude durable-agent fake-provider journey', () => {\n it('creates without invocation, then starts and resumes the same session through stdin', async () => {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-integration-'));\n roots.push(root);\n const cwd = path.join(root, 'project');\n fs.mkdirSync(cwd);\n const capture = path.join(root, 'capture.jsonl');\n process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;\n const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));\n const processInspector: ProcessInspector = {\n getIdentity: (pid) => ({ pid, startedAt: `process-${pid}` }),\n };\n const repository = new DurableAgentRepository({\n dbPath: path.join(root, 'state', 'agents.db'),\n processInspector,\n });\n const service = new ClaudePrintAgentService({\n repository,\n probe: new ClaudeCliProbe({ executable }),\n runner: new ClaudePrintRunner({ processInspector }),\n executable,\n });\n\n const created = await service.create({ name: 'reviewer', cwd });\n expect(fs.existsSync(capture)).toBe(false);\n\n await expect(service.send(created.id, 'first secret')).resolves.toMatchObject({ result: 'answer:first secret' });\n await expect(service.send(created.id, 'follow up')).resolves.toMatchObject({ result: 'answer:follow up' });\n\n const invocations = fs.readFileSync(capture, 'utf8').trim().split('\\n').map((line) => JSON.parse(line));\n expect(invocations[0]).toMatchObject({ prompt: 'first secret', cwd: fs.realpathSync(cwd) });\n expect(invocations[0].args).toContain('--session-id');\n expect(invocations[0].args).not.toContain('first secret');\n expect(invocations[1]).toMatchObject({ prompt: 'follow up', cwd: fs.realpathSync(cwd) });\n expect(invocations[1].args).toContain('--resume');\n expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);\n\n const persisted = await repository.getById(created.id);\n expect(persisted).toMatchObject({ state: 'ready', sessionHealth: 'healthy' });\n });\n});\n"],"names":["fs","os","path","fileURLToPath","afterEach","describe","expect","it","ClaudeCliProbe","ClaudePrintAgentService","ClaudePrintRunner","DurableAgentRepository","roots","originalCapture","process","env","AI_DEVKIT_FAKE_CLAUDE_CAPTURE","undefined","root","splice","rmSync","recursive","force","mkdtempSync","join","tmpdir","push","cwd","mkdirSync","capture","executable","URL","url","processInspector","getIdentity","pid","startedAt","repository","dbPath","service","probe","runner","created","create","name","existsSync","toBe","send","id","resolves","toMatchObject","result","invocations","readFileSync","trim","split","map","line","JSON","parse","prompt","realpathSync","args","toContain","not","indexOf","providerSessionId","persisted","getById","state","sessionHealth"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { CapacityReport, CapacityWindow } from './types.js';
2
+ type CodexUsageSource = 'pat' | 'oauth' | 'cli';
3
+ type UsageSnapshot = {
4
+ windows: CapacityWindow[];
5
+ creditsRemaining: number | null;
6
+ source: CodexUsageSource;
7
+ };
8
+ type UnknownRecord = Record<string, unknown>;
9
+ type RpcMessage = {
10
+ id?: number;
11
+ method: string;
12
+ params?: UnknownRecord;
13
+ };
14
+ type CliResponses = {
15
+ rateLimits: unknown;
16
+ account: unknown;
17
+ };
18
+ type CodexRpc = (messages: RpcMessage[]) => Promise<CliResponses>;
19
+ export declare const CODEX_APP_SERVER_ARGS: readonly ["-s", "read-only", "-a", "untrusted", "app-server"];
20
+ type CodexProbeOptions = {
21
+ installed: boolean;
22
+ checkedAt: string;
23
+ readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
24
+ fetch?: typeof globalThis.fetch;
25
+ rpc?: CodexRpc;
26
+ timeoutMs?: number;
27
+ env?: NodeJS.ProcessEnv;
28
+ now?: () => Date;
29
+ };
30
+ export declare function resolveCodexAuthPath(env?: NodeJS.ProcessEnv): string;
31
+ export declare function toRateWindow(value: unknown, id: string, label: string): CapacityWindow | null;
32
+ export declare function parseUsage(raw: unknown, source: 'pat' | 'oauth'): UsageSnapshot;
33
+ export declare function parseCliUsage(raw: unknown): UsageSnapshot;
34
+ export declare function probeCodexCapacity(options: CodexProbeOptions): Promise<CapacityReport>;
35
+ export {};
36
+ //# sourceMappingURL=codex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/capacity/codex.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjE,KAAK,gBAAgB,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;AAChD,KAAK,aAAa,GAAG;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE9G,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC7C,KAAK,UAAU,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,CAAC;AAC1E,KAAK,YAAY,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAC9D,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAElE,eAAO,MAAM,qBAAqB,+DAAgE,CAAC;AAEnG,KAAK,iBAAiB,GAAG;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AA8BF,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGjF;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,cAAc,GAAG,IAAI,CAYvB;AAgBD,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,aAAa,CAa/E;AAwBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAUzD;AAiJD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoC5F"}