11import { describe , expect , it , vi } from "vitest" ;
2- import {
3- HealthScoreTracker ,
4- TokenBucketTracker ,
5- exponentialBackoff ,
6- selectHybridAccount ,
7- } from "../lib/rotation.js" ;
82import { getTopCandidates } from "../lib/parallel-probe.js" ;
93import { createCodexHeaders } from "../lib/request/fetch-helpers.js" ;
104import {
115 clearRateLimitBackoffState ,
126 getRateLimitBackoffWithReason ,
137} from "../lib/request/rate-limit-backoff.js" ;
148import { transformRequestBody } from "../lib/request/request-transformer.js" ;
9+ import {
10+ exponentialBackoff ,
11+ HealthScoreTracker ,
12+ selectHybridAccount ,
13+ TokenBucketTracker ,
14+ } from "../lib/rotation.js" ;
1515import type { RequestBody } from "../lib/types.js" ;
16+ import pkg from "../package.json" with { type : "json" } ;
1617
1718describe ( "public api contract" , ( ) => {
1819 it ( "keeps root plugin exports aligned" , async ( ) => {
@@ -26,37 +27,108 @@ describe("public api contract", () => {
2627 const rotation = await import ( "../lib/rotation.js" ) ;
2728 const parallelProbe = await import ( "../lib/parallel-probe.js" ) ;
2829 const fetchHelpers = await import ( "../lib/request/fetch-helpers.js" ) ;
29- const rateLimitBackoff = await import ( "../lib/request/rate-limit-backoff.js" ) ;
30- const requestTransformer = await import ( "../lib/request/request-transformer.js" ) ;
31- const required : ReadonlyArray <
32- readonly [ string , Record < string , unknown > ]
33- > = [
34- [ "selectHybridAccount" , rotation ] ,
35- [ "exponentialBackoff" , rotation ] ,
36- [ "getTopCandidates" , parallelProbe ] ,
37- [ "createCodexHeaders" , fetchHelpers ] ,
38- [ "getRateLimitBackoffWithReason" , rateLimitBackoff ] ,
39- [ "transformRequestBody" , requestTransformer ] ,
40- ] ;
30+ const rateLimitBackoff = await import (
31+ "../lib/request/rate-limit-backoff.js"
32+ ) ;
33+ const requestTransformer = await import (
34+ "../lib/request/request-transformer.js"
35+ ) ;
36+ const required : ReadonlyArray < readonly [ string , Record < string , unknown > ] > =
37+ [
38+ [ "selectHybridAccount" , rotation ] ,
39+ [ "exponentialBackoff" , rotation ] ,
40+ [ "getTopCandidates" , parallelProbe ] ,
41+ [ "createCodexHeaders" , fetchHelpers ] ,
42+ [ "getRateLimitBackoffWithReason" , rateLimitBackoff ] ,
43+ [ "transformRequestBody" , requestTransformer ] ,
44+ ] ;
4145 for ( const [ name , mod ] of required ) {
4246 expect ( name in mod , `missing export: ${ name } ` ) . toBe ( true ) ;
4347 expect ( typeof mod [ name ] , `${ name } should be a function` ) . toBe ( "function" ) ;
4448 }
4549 } ) ;
4650
51+ it ( "declares the supported package subpath exports" , async ( ) => {
52+ expect ( pkg . exports ) . toEqual ( {
53+ "." : {
54+ types : "./dist/index.d.ts" ,
55+ import : "./dist/index.js" ,
56+ default : "./dist/index.js" ,
57+ } ,
58+ "./auth" : {
59+ types : "./dist/lib/auth/index.d.ts" ,
60+ import : "./dist/lib/auth/index.js" ,
61+ default : "./dist/lib/auth/index.js" ,
62+ } ,
63+ "./storage" : {
64+ types : "./dist/lib/storage.d.ts" ,
65+ import : "./dist/lib/storage.js" ,
66+ default : "./dist/lib/storage.js" ,
67+ } ,
68+ "./config" : {
69+ types : "./dist/lib/config.d.ts" ,
70+ import : "./dist/lib/config.js" ,
71+ default : "./dist/lib/config.js" ,
72+ } ,
73+ "./request" : {
74+ types : "./dist/lib/request/index.d.ts" ,
75+ import : "./dist/lib/request/index.js" ,
76+ default : "./dist/lib/request/index.js" ,
77+ } ,
78+ "./cli" : {
79+ types : "./dist/lib/codex-cli/index.d.ts" ,
80+ import : "./dist/lib/codex-cli/index.js" ,
81+ default : "./dist/lib/codex-cli/index.js" ,
82+ } ,
83+ "./package.json" : "./package.json" ,
84+ } ) ;
85+ } ) ;
86+
87+ it ( "keeps the supported subpath entry barrels aligned" , async ( ) => {
88+ const auth = await import ( "../lib/auth/index.js" ) ;
89+ const storage = await import ( "../lib/storage.js" ) ;
90+ const config = await import ( "../lib/config.js" ) ;
91+ const request = await import ( "../lib/request/index.js" ) ;
92+ const cli = await import ( "../lib/codex-cli/index.js" ) ;
93+
94+ expect ( typeof auth . exchangeAuthorizationCode ) . toBe ( "function" ) ;
95+ expect ( typeof storage . loadAccounts ) . toBe ( "function" ) ;
96+ expect ( typeof config . loadPluginConfig ) . toBe ( "function" ) ;
97+ expect ( typeof request . createCodexHeaders ) . toBe ( "function" ) ;
98+ expect ( typeof request . transformRequestBody ) . toBe ( "function" ) ;
99+ expect ( "handleResponse" in request ) . toBe ( false ) ;
100+ expect ( "withStreamFailover" in request ) . toBe ( false ) ;
101+ expect ( typeof cli . loadCodexCliState ) . toBe ( "function" ) ;
102+ } ) ;
103+
47104 it ( "keeps positional and options-object overload behavior aligned" , async ( ) => {
48105 const healthTracker = new HealthScoreTracker ( ) ;
49106 const tokenTracker = new TokenBucketTracker ( ) ;
50- const accounts = [ { index : 0 , isAvailable : true , lastUsed : 1_709_280_000_000 } ] ;
107+ const accounts = [
108+ { index : 0 , isAvailable : true , lastUsed : 1_709_280_000_000 } ,
109+ ] ;
51110
52- const selectedPositional = selectHybridAccount ( accounts , healthTracker , tokenTracker ) ;
53- const selectedNamed = selectHybridAccount ( { accounts, healthTracker, tokenTracker } ) ;
111+ const selectedPositional = selectHybridAccount (
112+ accounts ,
113+ healthTracker ,
114+ tokenTracker ,
115+ ) ;
116+ const selectedNamed = selectHybridAccount ( {
117+ accounts,
118+ healthTracker,
119+ tokenTracker,
120+ } ) ;
54121 expect ( selectedNamed ?. index ) . toBe ( selectedPositional ?. index ) ;
55122
56123 const randomSpy = vi . spyOn ( Math , "random" ) . mockReturnValue ( 0.5 ) ;
57124 try {
58125 const backoffPositional = exponentialBackoff ( 3 , 1000 , 60000 , 0 ) ;
59- const backoffNamed = exponentialBackoff ( { attempt : 3 , baseMs : 1000 , maxMs : 60000 , jitterFactor : 0 } ) ;
126+ const backoffNamed = exponentialBackoff ( {
127+ attempt : 3 ,
128+ baseMs : 1000 ,
129+ maxMs : 60000 ,
130+ jitterFactor : 0 ,
131+ } ) ;
60132 expect ( backoffNamed ) . toBe ( backoffPositional ) ;
61133 } finally {
62134 randomSpy . mockRestore ( ) ;
@@ -82,7 +154,9 @@ describe("public api contract", () => {
82154 1 ,
83155 ) ;
84156 const topNamed = getTopCandidates ( {
85- accountManager : manager as unknown as Parameters < typeof getTopCandidates > [ 0 ] ,
157+ accountManager : manager as unknown as Parameters <
158+ typeof getTopCandidates
159+ > [ 0 ] ,
86160 modelFamily : "codex" ,
87161 model : null ,
88162 maxCandidates : 1 ,
@@ -99,11 +173,22 @@ describe("public api contract", () => {
99173 accessToken : "token" ,
100174 opts : { model : "gpt-5" , promptCacheKey : "session-compat" } ,
101175 } ) ;
102- expect ( headersNamed . get ( "Authorization" ) ) . toBe ( headersPositional . get ( "Authorization" ) ) ;
103- expect ( headersNamed . get ( "conversation_id" ) ) . toBe ( headersPositional . get ( "conversation_id" ) ) ;
104- expect ( headersNamed . get ( "session_id" ) ) . toBe ( headersPositional . get ( "session_id" ) ) ;
176+ expect ( headersNamed . get ( "Authorization" ) ) . toBe (
177+ headersPositional . get ( "Authorization" ) ,
178+ ) ;
179+ expect ( headersNamed . get ( "conversation_id" ) ) . toBe (
180+ headersPositional . get ( "conversation_id" ) ,
181+ ) ;
182+ expect ( headersNamed . get ( "session_id" ) ) . toBe (
183+ headersPositional . get ( "session_id" ) ,
184+ ) ;
105185
106- const ratePositional = getRateLimitBackoffWithReason ( 1 , "compat" , 1000 , "tokens" ) ;
186+ const ratePositional = getRateLimitBackoffWithReason (
187+ 1 ,
188+ "compat" ,
189+ 1000 ,
190+ "tokens" ,
191+ ) ;
107192 clearRateLimitBackoffState ( ) ;
108193 const rateNamed = getRateLimitBackoffWithReason ( {
109194 accountIndex : 1 ,
0 commit comments