@@ -927,6 +927,7 @@ function describeCodexGoal(goal: OrchestrationV2ProviderGoal): string {
927927
928928// Codex starts the next goal turn milliseconds after the last one completes.
929929const CODEX_GOAL_CONTINUATION_GRACE = "5 seconds" ;
930+ const CODEX_RATE_LIMITS_READ_TIMEOUT = "3 seconds" ;
930931
931932const providerTurnsForThread = (
932933 providerTurns : ReadonlyArray < OrchestrationV2ProviderTurn > ,
@@ -4429,34 +4430,37 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
44294430 } ) . pipe ( Effect . orDie ) ,
44304431 ) ;
44314432
4432- yield * client . handleServerNotification ( "account/rateLimits/updated" , ( payload ) =>
4433- Effect . gen ( function * ( ) {
4434- const previous = yield * Ref . getAndUpdate ( rateLimitSnapshot , ( previous ) =>
4435- mergeCodexRateLimits ( previous , payload . rateLimits ) ,
4433+ // Untraced: rate-limit notifications repeat on every token tick.
4434+ const applyRateLimits = Effect . fnUntraced ( function * ( rateLimits : CodexRateLimitSnapshot ) {
4435+ const previous = yield * Ref . getAndUpdate ( rateLimitSnapshot , ( previous ) =>
4436+ mergeCodexRateLimits ( previous , rateLimits ) ,
4437+ ) ;
4438+ const resetAt = codexUsageLimitResetAt ( yield * Ref . get ( rateLimitSnapshot ) ) ;
4439+ for ( const item of ( yield * Ref . get ( limitedTurnItems ) ) . values ( ) ) {
4440+ if ( resetAt === null || item . failure . resetAt != null ) continue ;
4441+ const updated = {
4442+ ...item ,
4443+ updatedAt : yield * DateTime . now ,
4444+ failure : { ...item . failure , resetAt } ,
4445+ } ;
4446+ yield * Ref . update ( limitedTurnItems , ( current ) =>
4447+ new Map ( current ) . set ( item . providerThreadId ! , updated ) ,
44364448 ) ;
4437- const resetAt = codexUsageLimitResetAt ( yield * Ref . get ( rateLimitSnapshot ) ) ;
4438- for ( const item of ( yield * Ref . get ( limitedTurnItems ) ) . values ( ) ) {
4439- if ( resetAt === null || item . failure . resetAt != null ) continue ;
4440- const updated = {
4441- ...item ,
4442- updatedAt : yield * DateTime . now ,
4443- failure : { ...item . failure , resetAt } ,
4444- } ;
4445- yield * Ref . update ( limitedTurnItems , ( current ) =>
4446- new Map ( current ) . set ( item . providerThreadId ! , updated ) ,
4447- ) ;
4448- yield * emitProviderEvent ( {
4449- type : "turn_item.updated" ,
4450- driver : CODEX_PROVIDER ,
4451- turnItem : updated ,
4452- } ) ;
4453- }
4454- const update = codexRateLimitsToUpdate ( payload . rateLimits , previous ) ;
4455- if ( update && adapterOptions . onUsageLimits ) {
4456- const checkedAt = DateTime . formatIso ( yield * DateTime . now ) ;
4457- yield * adapterOptions . onUsageLimits ( { ...update , checkedAt } ) ;
4458- }
4459- } ) . pipe ( Effect . orDie ) ,
4449+ yield * emitProviderEvent ( {
4450+ type : "turn_item.updated" ,
4451+ driver : CODEX_PROVIDER ,
4452+ turnItem : updated ,
4453+ } ) ;
4454+ }
4455+ const update = codexRateLimitsToUpdate ( rateLimits , previous ) ;
4456+ if ( update && adapterOptions . onUsageLimits ) {
4457+ const checkedAt = DateTime . formatIso ( yield * DateTime . now ) ;
4458+ yield * adapterOptions . onUsageLimits ( { ...update , checkedAt } ) ;
4459+ }
4460+ } ) ;
4461+
4462+ yield * client . handleServerNotification ( "account/rateLimits/updated" , ( payload ) =>
4463+ applyRateLimits ( payload . rateLimits ) . pipe ( Effect . orDie ) ,
44604464 ) ;
44614465
44624466 yield * client . handleServerNotification ( "thread/tokenUsage/updated" , ( payload ) =>
@@ -5808,7 +5812,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
58085812 } ,
58095813 ) ;
58105814
5811- const emitRootTerminal = Effect . fnUntraced ( function * (
5815+ const publishRootTerminal = Effect . fnUntraced ( function * (
58125816 context : ActiveCodexTurnContext ,
58135817 event : CodexRootTerminalEvent ,
58145818 ) {
@@ -5825,23 +5829,6 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
58255829 }
58265830 : event ;
58275831 yield * emitProviderEvent ( current ) ;
5828- const continued = continuedRuns . get ( context . providerTurnId ) ?? [ context ] ;
5829- if ( current . status !== "completed" ) {
5830- yield * Ref . update (
5831- terminalizedNonCompletedNativeTurns ,
5832- ( nativeTurns ) =>
5833- new Set ( [ ...nativeTurns , ...continued . map ( ( turn ) => turn . nativeTurnId ) ] ) ,
5834- ) ;
5835- }
5836- for ( const turn of continued ) {
5837- continuedRuns . delete ( turn . providerTurnId ) ;
5838- }
5839- const nativeThreadId = context . providerThread . nativeThreadRef ?. nativeId ;
5840- const rootProviderThread =
5841- nativeThreadId == null ? undefined : rootProviderThreads . get ( nativeThreadId ) ;
5842- if ( nativeThreadId != null && rootProviderThread !== undefined ) {
5843- rootProviderThreads . set ( nativeThreadId , { ...rootProviderThread , status : "idle" } ) ;
5844- }
58455832 if ( current . status === "failed" && current . failure . class === "usage_limit" ) {
58465833 const item = makeProviderFailureTurnItem ( {
58475834 driver : CODEX_PROVIDER ,
@@ -5860,6 +5847,52 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
58605847 }
58615848 } ) ;
58625849
5850+ const emitRootTerminal = Effect . fnUntraced ( function * (
5851+ context : ActiveCodexTurnContext ,
5852+ event : CodexRootTerminalEvent ,
5853+ ) {
5854+ // Runs stop ingesting at a failed terminal, so a later reset time is never stored. A session
5855+ // that starts limited gets no rateLimits/updated, so read them before publishing. Forked:
5856+ // notification handlers run on the reader fiber, which must stay free for the reply.
5857+ if (
5858+ event . status === "failed" &&
5859+ event . failure . class === "usage_limit" &&
5860+ event . failure . resetAt == null &&
5861+ codexUsageLimitResetAt ( yield * Ref . get ( rateLimitSnapshot ) ) === null
5862+ ) {
5863+ yield * client . request ( "account/rateLimits/read" , null ) . pipe (
5864+ Effect . timeout ( CODEX_RATE_LIMITS_READ_TIMEOUT ) ,
5865+ Effect . flatMap ( ( response ) =>
5866+ applyRateLimits ( response . rateLimitsByLimitId ?. codex ?? response . rateLimits ) ,
5867+ ) ,
5868+ Effect . catch ( ( cause ) =>
5869+ Effect . logDebug ( "orchestration-v2.codex-rate-limits-read-failed" , { cause } ) ,
5870+ ) ,
5871+ Effect . andThen ( publishRootTerminal ( context , event ) ) ,
5872+ Effect . forkIn ( scope ) ,
5873+ ) ;
5874+ } else {
5875+ yield * publishRootTerminal ( context , event ) ;
5876+ }
5877+ const continued = continuedRuns . get ( context . providerTurnId ) ?? [ context ] ;
5878+ if ( event . status !== "completed" ) {
5879+ yield * Ref . update (
5880+ terminalizedNonCompletedNativeTurns ,
5881+ ( nativeTurns ) =>
5882+ new Set ( [ ...nativeTurns , ...continued . map ( ( turn ) => turn . nativeTurnId ) ] ) ,
5883+ ) ;
5884+ }
5885+ for ( const turn of continued ) {
5886+ continuedRuns . delete ( turn . providerTurnId ) ;
5887+ }
5888+ const nativeThreadId = context . providerThread . nativeThreadRef ?. nativeId ;
5889+ const rootProviderThread =
5890+ nativeThreadId == null ? undefined : rootProviderThreads . get ( nativeThreadId ) ;
5891+ if ( nativeThreadId != null && rootProviderThread !== undefined ) {
5892+ rootProviderThreads . set ( nativeThreadId , { ...rootProviderThread , status : "idle" } ) ;
5893+ }
5894+ } ) ;
5895+
58635896 const emitOrDeferRootTerminal = Effect . fn ( "CodexAdapterV2.emitOrDeferRootTerminal" ) (
58645897 function * ( input : {
58655898 readonly context : ActiveCodexTurnContext ;
0 commit comments