| ... | @@ -228,28 +228,25 @@ function appendTurnItem( | ... | @@ -228,28 +228,25 @@ function appendTurnItem( |
| 228 | resolveTurnSnapshot(context, turnId).items.push(item); | 228 | resolveTurnSnapshot(context, turnId).items.push(item); |
| 229 | } | 229 | } |
| 230 | | 230 | |
| 231 | function ensureSessionContext( | 231 | const ensureSessionContext = Effect.fn("ensureSessionContext")(function* ( |
| 232 | sessions: ReadonlyMap<ThreadId, OpenCodeSessionContext>, | 232 | sessions: ReadonlyMap<ThreadId, OpenCodeSessionContext>, |
| 233 | threadId: ThreadId, | 233 | threadId: ThreadId, |
| 234 | ): OpenCodeSessionContext { | 234 | ) { |
| 235 | const session = sessions.get(threadId); | 235 | const session = sessions.get(threadId); |
| 236 | if (!session) { | 236 | if (!session) { |
| 237 | throw new ProviderAdapterSessionNotFoundError({ | 237 | return yield* new ProviderAdapterSessionNotFoundError({ |
| 238 | provider: PROVIDER, | 238 | provider: PROVIDER, |
| 239 | threadId, | 239 | threadId, |
| 240 | }); | 240 | }); |
| 241 | } | 241 | } |
| 242 | // `ensureSessionContext` is a sync gate used from both sync helpers and | 242 | if (yield* Ref.get(session.stopped)) { |
| 243 | // Effect bodies. `Ref.getUnsafe` is an atomic read of the backing cell — | 243 | return yield* new ProviderAdapterSessionClosedError({ |
| 244 | // no fiber suspension required, which keeps this callable everywhere. | | |
| 245 | if (Ref.getUnsafe(session.stopped)) { | | |
| 246 | throw new ProviderAdapterSessionClosedError({ | | |
| 247 | provider: PROVIDER, | 244 | provider: PROVIDER, |
| 248 | threadId, | 245 | threadId, |
| 249 | }); | 246 | }); |
| 250 | } | 247 | } |
| 251 | return session; | 248 | return session; |
| 252 | } | 249 | }); |
| 253 | | 250 | |
| 254 | function normalizeQuestionRequest(request: QuestionRequest): ReadonlyArray<UserInputQuestion> { | 251 | function normalizeQuestionRequest(request: QuestionRequest): ReadonlyArray<UserInputQuestion> { |
| 255 | return request.questions.map((question, index) => ({ | 252 | return request.questions.map((question, index) => ({ |
| ... | @@ -1167,7 +1164,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1167,7 +1164,7 @@ export function makeOpenCodeAdapter( |
| 1167 | ); | 1164 | ); |
| 1168 | | 1165 | |
| 1169 | const sendTurn: OpenCodeAdapterShape["sendTurn"] = Effect.fn("sendTurn")(function* (input) { | 1166 | const sendTurn: OpenCodeAdapterShape["sendTurn"] = Effect.fn("sendTurn")(function* (input) { |
| 1170 | const context = ensureSessionContext(sessions, input.threadId); | 1167 | const context = yield* ensureSessionContext(sessions, input.threadId); |
| 1171 | // A sendTurn while a turn is active is a steer: OpenCode queues the | 1168 | // A sendTurn while a turn is active is a steer: OpenCode queues the |
| 1172 | // prompt into the busy session and the work continues as one turn, so | 1169 | // prompt into the busy session and the work continues as one turn, so |
| 1173 | // the active turn id is reused instead of opening a new turn. | 1170 | // the active turn id is reused instead of opening a new turn. |
| ... | @@ -1291,7 +1288,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1291,7 +1288,7 @@ export function makeOpenCodeAdapter( |
| 1291 | | 1288 | |
| 1292 | const interruptTurn: OpenCodeAdapterShape["interruptTurn"] = Effect.fn("interruptTurn")( | 1289 | const interruptTurn: OpenCodeAdapterShape["interruptTurn"] = Effect.fn("interruptTurn")( |
| 1293 | function* (threadId, turnId) { | 1290 | function* (threadId, turnId) { |
| 1294 | const context = ensureSessionContext(sessions, threadId); | 1291 | const context = yield* ensureSessionContext(sessions, threadId); |
| 1295 | yield* runOpenCodeSdk("session.abort", () => | 1292 | yield* runOpenCodeSdk("session.abort", () => |
| 1296 | context.client.session.abort({ sessionID: context.openCodeSessionId }), | 1293 | context.client.session.abort({ sessionID: context.openCodeSessionId }), |
| 1297 | ).pipe(Effect.mapError(toRequestError)); | 1294 | ).pipe(Effect.mapError(toRequestError)); |
| ... | @@ -1313,7 +1310,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1313,7 +1310,7 @@ export function makeOpenCodeAdapter( |
| 1313 | const respondToRequest: OpenCodeAdapterShape["respondToRequest"] = Effect.fn( | 1310 | const respondToRequest: OpenCodeAdapterShape["respondToRequest"] = Effect.fn( |
| 1314 | "respondToRequest", | 1311 | "respondToRequest", |
| 1315 | )(function* (threadId, requestId, decision) { | 1312 | )(function* (threadId, requestId, decision) { |
| 1316 | const context = ensureSessionContext(sessions, threadId); | 1313 | const context = yield* ensureSessionContext(sessions, threadId); |
| 1317 | if (!context.pendingPermissions.has(requestId)) { | 1314 | if (!context.pendingPermissions.has(requestId)) { |
| 1318 | return yield* new ProviderAdapterRequestError({ | 1315 | return yield* new ProviderAdapterRequestError({ |
| 1319 | provider: PROVIDER, | 1316 | provider: PROVIDER, |
| ... | @@ -1333,7 +1330,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1333,7 +1330,7 @@ export function makeOpenCodeAdapter( |
| 1333 | const respondToUserInput: OpenCodeAdapterShape["respondToUserInput"] = Effect.fn( | 1330 | const respondToUserInput: OpenCodeAdapterShape["respondToUserInput"] = Effect.fn( |
| 1334 | "respondToUserInput", | 1331 | "respondToUserInput", |
| 1335 | )(function* (threadId, requestId, answers) { | 1332 | )(function* (threadId, requestId, answers) { |
| 1336 | const context = ensureSessionContext(sessions, threadId); | 1333 | const context = yield* ensureSessionContext(sessions, threadId); |
| 1337 | const request = context.pendingQuestions.get(requestId); | 1334 | const request = context.pendingQuestions.get(requestId); |
| 1338 | if (!request) { | 1335 | if (!request) { |
| 1339 | return yield* new ProviderAdapterRequestError({ | 1336 | return yield* new ProviderAdapterRequestError({ |
| ... | @@ -1355,7 +1352,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1355,7 +1352,7 @@ export function makeOpenCodeAdapter( |
| 1355 | function* (threadId) { | 1352 | function* (threadId) { |
| 1356 | const context = sessions.get(threadId); | 1353 | const context = sessions.get(threadId); |
| 1357 | if (!context) { | 1354 | if (!context) { |
| 1358 | throw new ProviderAdapterSessionNotFoundError({ | 1355 | return yield* new ProviderAdapterSessionNotFoundError({ |
| 1359 | provider: PROVIDER, | 1356 | provider: PROVIDER, |
| 1360 | threadId, | 1357 | threadId, |
| 1361 | }); | 1358 | }); |
| ... | @@ -1385,7 +1382,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1385,7 +1382,7 @@ export function makeOpenCodeAdapter( |
| 1385 | | 1382 | |
| 1386 | const readThread: OpenCodeAdapterShape["readThread"] = Effect.fn("readThread")( | 1383 | const readThread: OpenCodeAdapterShape["readThread"] = Effect.fn("readThread")( |
| 1387 | function* (threadId) { | 1384 | function* (threadId) { |
| 1388 | const context = ensureSessionContext(sessions, threadId); | 1385 | const context = yield* ensureSessionContext(sessions, threadId); |
| 1389 | const messages = yield* runOpenCodeSdk("session.messages", () => | 1386 | const messages = yield* runOpenCodeSdk("session.messages", () => |
| 1390 | context.client.session.messages({ | 1387 | context.client.session.messages({ |
| 1391 | sessionID: context.openCodeSessionId, | 1388 | sessionID: context.openCodeSessionId, |
| ... | @@ -1411,7 +1408,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1411,7 +1408,7 @@ export function makeOpenCodeAdapter( |
| 1411 | | 1408 | |
| 1412 | const rollbackThread: OpenCodeAdapterShape["rollbackThread"] = Effect.fn("rollbackThread")( | 1409 | const rollbackThread: OpenCodeAdapterShape["rollbackThread"] = Effect.fn("rollbackThread")( |
| 1413 | function* (threadId, numTurns) { | 1410 | function* (threadId, numTurns) { |
| 1414 | const context = ensureSessionContext(sessions, threadId); | 1411 | const context = yield* ensureSessionContext(sessions, threadId); |
| 1415 | const messages = yield* runOpenCodeSdk("session.messages", () => | 1412 | const messages = yield* runOpenCodeSdk("session.messages", () => |
| 1416 | context.client.session.messages({ | 1413 | context.client.session.messages({ |
| 1417 | sessionID: context.openCodeSessionId, | 1414 | sessionID: context.openCodeSessionId, |