Skip to content

Commit ec7664e

Browse files
committed
fix(server): preserve providerOptions across runtime mode changes and session recovery
Runtime mode toggles and server restarts silently dropped the custom codex binary/home path, falling back to the default binary. Cache providerOptions per thread in ProviderCommandReactor and persist it in the session binding's runtimePayload so recovery can restore it. Closes #486
1 parent 7e06dc4 commit ec7664e

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

apps/server/src/orchestration/Layers/ProviderCommandReactor.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ const make = Effect.gen(function* () {
145145
),
146146
);
147147

148+
const threadProviderOptions = new Map<string, ProviderStartOptions>();
149+
148150
const appendProviderFailureActivity = (input: {
149151
readonly threadId: ThreadId;
150152
readonly kind:
@@ -336,6 +338,9 @@ const make = Effect.gen(function* () {
336338
if (!thread) {
337339
return;
338340
}
341+
if (input.providerOptions !== undefined) {
342+
threadProviderOptions.set(input.threadId, input.providerOptions);
343+
}
339344
yield* ensureSessionForThread(input.threadId, input.createdAt, {
340345
...(input.provider !== undefined ? { provider: input.provider } : {}),
341346
...(input.model !== undefined ? { model: input.model } : {}),
@@ -633,7 +638,10 @@ const make = Effect.gen(function* () {
633638
if (!thread?.session || thread.session.status === "stopped") {
634639
return;
635640
}
636-
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt);
641+
const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId);
642+
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, {
643+
...(cachedProviderOptions !== undefined ? { providerOptions: cachedProviderOptions } : {}),
644+
});
637645
return;
638646
}
639647
case "thread.turn-start-requested":

apps/server/src/provider/Layers/ProviderService.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,30 @@ function toRuntimeStatus(session: ProviderSession): "starting" | "running" | "st
8686
}
8787
}
8888

89-
function toRuntimePayloadFromSession(session: ProviderSession): Record<string, unknown> {
89+
function toRuntimePayloadFromSession(
90+
session: ProviderSession,
91+
extra?: { readonly providerOptions?: unknown },
92+
): Record<string, unknown> {
9093
return {
9194
cwd: session.cwd ?? null,
9295
model: session.model ?? null,
9396
activeTurnId: session.activeTurnId ?? null,
9497
lastError: session.lastError ?? null,
98+
...(extra?.providerOptions !== undefined ? { providerOptions: extra.providerOptions } : {}),
9599
};
96100
}
97101

102+
function readPersistedProviderOptions(
103+
runtimePayload: ProviderRuntimeBinding["runtimePayload"],
104+
): Record<string, unknown> | undefined {
105+
if (!runtimePayload || typeof runtimePayload !== "object" || Array.isArray(runtimePayload)) {
106+
return undefined;
107+
}
108+
const raw = "providerOptions" in runtimePayload ? runtimePayload.providerOptions : undefined;
109+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
110+
return raw as Record<string, unknown>;
111+
}
112+
98113
function readPersistedCwd(
99114
runtimePayload: ProviderRuntimeBinding["runtimePayload"],
100115
): string | undefined {
@@ -137,14 +152,15 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
137152
const upsertSessionBinding = (
138153
session: ProviderSession,
139154
threadId: ThreadId,
155+
extra?: { readonly providerOptions?: unknown },
140156
) =>
141157
directory.upsert({
142158
threadId,
143159
provider: session.provider,
144160
runtimeMode: session.runtimeMode,
145161
status: toRuntimeStatus(session),
146162
...(session.resumeCursor !== undefined ? { resumeCursor: session.resumeCursor } : {}),
147-
runtimePayload: toRuntimePayloadFromSession(session),
163+
runtimePayload: toRuntimePayloadFromSession(session, extra),
148164
});
149165

150166
const providers = yield* registry.listProviders();
@@ -197,11 +213,13 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
197213
}
198214

199215
const persistedCwd = readPersistedCwd(input.binding.runtimePayload);
216+
const persistedProviderOptions = readPersistedProviderOptions(input.binding.runtimePayload);
200217

201218
const resumed = yield* adapter.startSession({
202219
threadId: input.binding.threadId,
203220
provider: input.binding.provider,
204221
...(persistedCwd ? { cwd: persistedCwd } : {}),
222+
...(persistedProviderOptions ? { providerOptions: persistedProviderOptions } : {}),
205223
...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}),
206224
runtimeMode: input.binding.runtimeMode ?? "full-access",
207225
});
@@ -273,7 +291,9 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
273291
);
274292
}
275293

276-
yield* upsertSessionBinding(session, threadId);
294+
yield* upsertSessionBinding(session, threadId, {
295+
...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
296+
});
277297
yield* analytics.record("provider.session.started", {
278298
provider: session.provider,
279299
runtimeMode: input.runtimeMode,

0 commit comments

Comments
 (0)