- Replace provider-specific model options with typed modelSelection - Persist and replay selected provider, model, and options end to end - Update adapter, service, and chat composer tests for the new shape
Unified · Split
| @@ -313,7 +313,10 @@ describe("ProviderCommandReactor", () => { · 312 unmodified lines | |||
| 313 | expect(harness.startSession.mock.calls[0]?.[0]).toEqual(ThreadId.makeUnsafe("thread-1")); | 313 | expect(harness.startSession.mock.calls[0]?.[0]).toEqual(ThreadId.makeUnsafe("thread-1")); |
| 314 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 314 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 315 | cwd: "/tmp/provider-project", | 315 | cwd: "/tmp/provider-project", |
| 316 | model: "gpt-5-codex", | 316 | modelSelection: { |
| 317 | provider: "codex", | ||
| 318 | model: "gpt-5-codex", | ||
| 319 | }, | ||
| 317 | runtimeMode: "approval-required", | 320 | runtimeMode: "approval-required", |
| 318 | }); | 321 | }); |
| 319 | 322 | ||
| @@ -355,9 +358,10 @@ describe("ProviderCommandReactor", () => { · 35 unmodified lines | |||
| 355 | await waitFor(() => harness.startSession.mock.calls.length === 1); | 358 | await waitFor(() => harness.startSession.mock.calls.length === 1); |
| 356 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | 359 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); |
| 357 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 360 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 358 | model: "gpt-5.3-codex", | 361 | modelSelection: { |
| 359 | modelOptions: { | 362 | provider: "codex", |
| 360 | codex: { | 363 | model: "gpt-5.3-codex", |
| 364 | options: { | ||
| 361 | reasoningEffort: "high", | 365 | reasoningEffort: "high", |
| 362 | fastMode: true, | 366 | fastMode: true, |
| 363 | }, | 367 | }, |
| @@ -365,9 +369,10 @@ describe("ProviderCommandReactor", () => { · 1 unmodified line | |||
| 365 | }); | 369 | }); |
| 366 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ | 370 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ |
| 367 | threadId: ThreadId.makeUnsafe("thread-1"), | 371 | threadId: ThreadId.makeUnsafe("thread-1"), |
| 368 | model: "gpt-5.3-codex", | 372 | modelSelection: { |
| 369 | modelOptions: { | 373 | provider: "codex", |
| 370 | codex: { | 374 | model: "gpt-5.3-codex", |
| 375 | options: { | ||
| 371 | reasoningEffort: "high", | 376 | reasoningEffort: "high", |
| 372 | fastMode: true, | 377 | fastMode: true, |
| 373 | }, | 378 | }, |
| @@ -406,19 +411,20 @@ describe("ProviderCommandReactor", () => { · 32 unmodified lines | |||
| 406 | await waitFor(() => harness.startSession.mock.calls.length === 1); | 411 | await waitFor(() => harness.startSession.mock.calls.length === 1); |
| 407 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | 412 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); |
| 408 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 413 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 409 | provider: "claudeAgent", | 414 | modelSelection: { |
| 410 | model: "claude-sonnet-4-6", | 415 | provider: "claudeAgent", |
| 411 | modelOptions: { | 416 | model: "claude-sonnet-4-6", |
| 412 | claudeAgent: { | 417 | options: { |
| 413 | effort: "max", | 418 | effort: "max", |
| 414 | }, | 419 | }, |
| 415 | }, | 420 | }, |
| 416 | }); | 421 | }); |
| 417 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ | 422 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ |
| 418 | threadId: ThreadId.makeUnsafe("thread-1"), | 423 | threadId: ThreadId.makeUnsafe("thread-1"), |
| 419 | model: "claude-sonnet-4-6", | 424 | modelSelection: { |
| 420 | modelOptions: { | 425 | provider: "claudeAgent", |
| 421 | claudeAgent: { | 426 | model: "claude-sonnet-4-6", |
| 427 | options: { | ||
| 422 | effort: "max", | 428 | effort: "max", |
| 423 | }, | 429 | }, |
| 424 | }, | 430 | }, |
| @@ -456,19 +462,20 @@ describe("ProviderCommandReactor", () => { · 31 unmodified lines | |||
| 456 | await waitFor(() => harness.startSession.mock.calls.length === 1); | 462 | await waitFor(() => harness.startSession.mock.calls.length === 1); |
| 457 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | 463 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); |
| 458 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 464 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 459 | provider: "claudeAgent", | 465 | modelSelection: { |
| 460 | model: "claude-opus-4-6", | 466 | provider: "claudeAgent", |
| 461 | modelOptions: { | 467 | model: "claude-opus-4-6", |
| 462 | claudeAgent: { | 468 | options: { |
| 463 | fastMode: true, | 469 | fastMode: true, |
| 464 | }, | 470 | }, |
| 465 | }, | 471 | }, |
| 466 | }); | 472 | }); |
| 467 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ | 473 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ |
| 468 | threadId: ThreadId.makeUnsafe("thread-1"), | 474 | threadId: ThreadId.makeUnsafe("thread-1"), |
| 469 | model: "claude-opus-4-6", | 475 | modelSelection: { |
| 470 | modelOptions: { | 476 | provider: "claudeAgent", |
| 471 | claudeAgent: { | 477 | model: "claude-opus-4-6", |
| 478 | options: { | ||
| 472 | fastMode: true, | 479 | fastMode: true, |
| 473 | }, | 480 | }, |
| 474 | }, | 481 | }, |
| @@ -594,12 +601,17 @@ describe("ProviderCommandReactor", () => { · 119 unmodified lines | |||
| 594 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | 601 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); |
| 595 | 602 | ||
| 596 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 603 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 597 | provider: "codex", | 604 | modelSelection: { |
| 598 | model: "claude-sonnet-4-6", | 605 | provider: "codex", |
| 606 | model: "claude-sonnet-4-6", | ||
| 607 | }, | ||
| 599 | }); | 608 | }); |
| 600 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ | 609 | expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ |
| 601 | threadId: ThreadId.makeUnsafe("thread-1"), | 610 | threadId: ThreadId.makeUnsafe("thread-1"), |
| 602 | model: "claude-sonnet-4-6", | 611 | modelSelection: { |
| 612 | provider: "codex", | ||
| 613 | model: "claude-sonnet-4-6", | ||
| 614 | }, | ||
| 603 | }); | 615 | }); |
| 604 | }); | 616 | }); |
| 605 | 617 | ||
| @@ -707,10 +719,11 @@ describe("ProviderCommandReactor", () => { · 101 unmodified lines | |||
| 707 | await waitFor(() => harness.startSession.mock.calls.length === 2); | 719 | await waitFor(() => harness.startSession.mock.calls.length === 2); |
| 708 | await waitFor(() => harness.sendTurn.mock.calls.length === 2); | 720 | await waitFor(() => harness.sendTurn.mock.calls.length === 2); |
| 709 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ | 721 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ |
| 710 | provider: "claudeAgent", | ||
| 711 | resumeCursor: { opaque: "resume-1" }, | 722 | resumeCursor: { opaque: "resume-1" }, |
| 712 | modelOptions: { | 723 | modelSelection: { |
| 713 | claudeAgent: { | 724 | provider: "claudeAgent", |
| 725 | model: "claude-sonnet-4-6", | ||
| 726 | options: { | ||
| 714 | effort: "max", | 727 | effort: "max", |
| 715 | }, | 728 | }, |
| 716 | }, | 729 | }, |
| @@ -841,11 +854,12 @@ describe("ProviderCommandReactor", () => { · 124 unmodified lines | |||
| 841 | await waitFor(() => harness.startSession.mock.calls.length === 1); | 854 | await waitFor(() => harness.startSession.mock.calls.length === 1); |
| 842 | 855 | ||
| 843 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 856 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 844 | provider: "claudeAgent", | 857 | modelSelection: { |
| 845 | model: "claude-opus-4-6", | 858 | provider: "claudeAgent", |
| 859 | model: "claude-opus-4-6", | ||
| 860 | }, | ||
| 846 | runtimeMode: "approval-required", | 861 | runtimeMode: "approval-required", |
| 847 | }); | 862 | }); |
| 848 | expect(harness.startSession.mock.calls[0]?.[1]).not.toHaveProperty("modelOptions"); | ||
| 849 | }); | 863 | }); |
| 850 | 864 | ||
| 851 | it("rejects provider changes after a thread is already bound to a session provider", async () => { | 865 | it("rejects provider changes after a thread is already bound to a session provider", async () => { |
| @@ -5,7 +5,6 @@ import { · 4 unmodified lines | |||
| 5 | EventId, | 5 | EventId, |
| 6 | type ModelSelection, | 6 | type ModelSelection, |
| 7 | type OrchestrationEvent, | 7 | type OrchestrationEvent, |
| 8 | type ProviderModelOptions, | ||
| 9 | ProviderKind, | 8 | ProviderKind, |
| 10 | type ProviderStartOptions, | 9 | type ProviderStartOptions, |
| 11 | type OrchestrationSession, | 10 | type OrchestrationSession, |
| @@ -16,7 +15,6 @@ import { · 4 unmodified lines | |||
| 16 | } from "@t3tools/contracts"; | 15 | } from "@t3tools/contracts"; |
| 17 | import { Cache, Cause, Duration, Effect, Layer, Option, Schema, Stream } from "effect"; | 16 | import { Cache, Cause, Duration, Effect, Layer, Option, Schema, Stream } from "effect"; |
| 18 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; | 17 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 19 | import { toProviderModelOptions } from "@t3tools/shared/model"; | ||
| 20 | 18 | ||
| 21 | import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; | 19 | import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; |
| 22 | import { GitCore } from "../../git/Services/GitCore.ts"; | 20 | import { GitCore } from "../../git/Services/GitCore.ts"; |
| @@ -77,9 +75,9 @@ const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; · 54 unmodified lines | |||
| 77 | const WORKTREE_BRANCH_PREFIX = "t3code"; | 75 | const WORKTREE_BRANCH_PREFIX = "t3code"; |
| 78 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); | 76 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); |
| 79 | 77 | ||
| 80 | const sameModelOptions = ( | 78 | const sameModelSelectionOptions = ( |
| 81 | left: ProviderModelOptions | undefined, | 79 | left: ModelSelection | undefined, |
| 82 | right: ProviderModelOptions | undefined, | 80 | right: ModelSelection | undefined, |
| 83 | ): boolean => JSON.stringify(left ?? null) === JSON.stringify(right ?? null); | 81 | ): boolean => JSON.stringify(left ?? null) === JSON.stringify(right ?? null); |
| 84 | 82 | ||
| 85 | function isUnknownPendingApprovalRequestError(cause: Cause.Cause<ProviderServiceError>): boolean { | 83 | function isUnknownPendingApprovalRequestError(cause: Cause.Cause<ProviderServiceError>): boolean { |
| @@ -159,7 +157,7 @@ const make = Effect.gen(function* () { · 73 unmodified lines | |||
| 159 | ); | 157 | ); |
| 160 | 158 | ||
| 161 | const threadProviderOptions = new Map<string, ProviderStartOptions>(); | 159 | const threadProviderOptions = new Map<string, ProviderStartOptions>(); |
| 162 | const threadModelOptions = new Map<string, ProviderModelOptions>(); | 160 | const threadModelSelections = new Map<string, ModelSelection>(); |
| 163 | 161 | ||
| 164 | const appendProviderFailureActivity = (input: { | 162 | const appendProviderFailureActivity = (input: { |
| 165 | readonly threadId: ThreadId; | 163 | readonly threadId: ThreadId; |
| @@ -217,7 +215,6 @@ const make = Effect.gen(function* () { · 51 unmodified lines | |||
| 217 | createdAt: string, | 215 | createdAt: string, |
| 218 | options?: { | 216 | options?: { |
| 219 | readonly modelSelection?: ModelSelection; | 217 | readonly modelSelection?: ModelSelection; |
| 220 | readonly modelOptions?: ProviderModelOptions; | ||
| 221 | readonly providerOptions?: ProviderStartOptions; | 218 | readonly providerOptions?: ProviderStartOptions; |
| 222 | }, | 219 | }, |
| 223 | ) { | 220 | ) { |
| @@ -247,9 +244,6 @@ const make = Effect.gen(function* () { · 23 unmodified lines | |||
| 247 | } | 244 | } |
| 248 | const preferredProvider: ProviderKind = currentProvider ?? threadProvider; | 245 | const preferredProvider: ProviderKind = currentProvider ?? threadProvider; |
| 249 | const desiredModelSelection = requestedModelSelection ?? thread.modelSelection; | 246 | const desiredModelSelection = requestedModelSelection ?? thread.modelSelection; |
| 250 | const desiredModel = desiredModelSelection.model; | ||
| 251 | const desiredModelOptions = | ||
| 252 | options?.modelOptions ?? toProviderModelOptions(desiredModelSelection); | ||
| 253 | const effectiveCwd = resolveThreadWorkspaceCwd({ | 247 | const effectiveCwd = resolveThreadWorkspaceCwd({ |
| 254 | thread, | 248 | thread, |
| 255 | projects: readModel.projects, | 249 | projects: readModel.projects, |
| @@ -268,8 +262,7 @@ const make = Effect.gen(function* () { · 12 unmodified lines | |||
| 268 | threadId, | 262 | threadId, |
| 269 | ...(preferredProvider ? { provider: preferredProvider } : {}), | 263 | ...(preferredProvider ? { provider: preferredProvider } : {}), |
| 270 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), | 264 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), |
| 271 | ...(desiredModel ? { model: desiredModel } : {}), | 265 | modelSelection: desiredModelSelection, |
| 272 | ...(desiredModelOptions !== undefined ? { modelOptions: desiredModelOptions } : {}), | ||
| 273 | ...(options?.providerOptions !== undefined | 266 | ...(options?.providerOptions !== undefined |
| 274 | ? { providerOptions: options.providerOptions } | 267 | ? { providerOptions: options.providerOptions } |
| 275 | : {}), | 268 | : {}), |
| @@ -309,17 +302,17 @@ const make = Effect.gen(function* () { · 33 unmodified lines | |||
| 309 | requestedModelSelection !== undefined && | 302 | requestedModelSelection !== undefined && |
| 310 | requestedModelSelection.model !== activeSession?.model; | 303 | requestedModelSelection.model !== activeSession?.model; |
| 311 | const shouldRestartForModelChange = modelChanged && sessionModelSwitch === "restart-session"; | 304 | const shouldRestartForModelChange = modelChanged && sessionModelSwitch === "restart-session"; |
| 312 | const previousModelOptions = threadModelOptions.get(threadId); | 305 | const previousModelSelection = threadModelSelections.get(threadId); |
| 313 | const shouldRestartForModelOptionsChange = | 306 | const shouldRestartForModelSelectionChange = |
| 314 | currentProvider === "claudeAgent" && | 307 | currentProvider === "claudeAgent" && |
| 315 | options?.modelOptions !== undefined && | 308 | requestedModelSelection !== undefined && |
| 316 | !sameModelOptions(previousModelOptions, options.modelOptions); | 309 | !sameModelSelectionOptions(previousModelSelection, requestedModelSelection); |
| 317 | 310 | ||
| 318 | if ( | 311 | if ( |
| 319 | !runtimeModeChanged && | 312 | !runtimeModeChanged && |
| 320 | !providerChanged && | 313 | !providerChanged && |
| 321 | !shouldRestartForModelChange && | 314 | !shouldRestartForModelChange && |
| 322 | !shouldRestartForModelOptionsChange | 315 | !shouldRestartForModelSelectionChange |
| 323 | ) { | 316 | ) { |
| 324 | return existingSessionThreadId; | 317 | return existingSessionThreadId; |
| 325 | } | 318 | } |
| @@ -339,7 +332,7 @@ const make = Effect.gen(function* () { · 13 unmodified lines | |||
| 339 | providerChanged, | 332 | providerChanged, |
| 340 | modelChanged, | 333 | modelChanged, |
| 341 | shouldRestartForModelChange, | 334 | shouldRestartForModelChange, |
| 342 | shouldRestartForModelOptionsChange, | 335 | shouldRestartForModelSelectionChange, |
| 343 | hasResumeCursor: resumeCursor !== undefined, | 336 | hasResumeCursor: resumeCursor !== undefined, |
| 344 | }); | 337 | }); |
| 345 | const restartedSession = yield* startProviderSession( | 338 | const restartedSession = yield* startProviderSession( |
| @@ -366,7 +359,6 @@ const make = Effect.gen(function* () { · 20 unmodified lines | |||
| 366 | readonly messageText: string; | 359 | readonly messageText: string; |
| 367 | readonly attachments?: ReadonlyArray<ChatAttachment>; | 360 | readonly attachments?: ReadonlyArray<ChatAttachment>; |
| 368 | readonly modelSelection?: ModelSelection; | 361 | readonly modelSelection?: ModelSelection; |
| 369 | readonly modelOptions?: ProviderModelOptions; | ||
| 370 | readonly providerOptions?: ProviderStartOptions; | 362 | readonly providerOptions?: ProviderStartOptions; |
| 371 | readonly interactionMode?: "default" | "plan"; | 363 | readonly interactionMode?: "default" | "plan"; |
| 372 | readonly createdAt: string; | 364 | readonly createdAt: string; |
| @@ -377,14 +369,13 @@ const make = Effect.gen(function* () { · 4 unmodified lines | |||
| 377 | } | 369 | } |
| 378 | yield* ensureSessionForThread(input.threadId, input.createdAt, { | 370 | yield* ensureSessionForThread(input.threadId, input.createdAt, { |
| 379 | ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), | 371 | ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), |
| 380 | ...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}), | ||
| 381 | ...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}), | 372 | ...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}), |
| 382 | }); | 373 | }); |
| 383 | if (input.providerOptions !== undefined) { | 374 | if (input.providerOptions !== undefined) { |
| 384 | threadProviderOptions.set(input.threadId, input.providerOptions); | 375 | threadProviderOptions.set(input.threadId, input.providerOptions); |
| 385 | } | 376 | } |
| 386 | if (input.modelOptions !== undefined) { | 377 | if (input.modelSelection !== undefined) { |
| 387 | threadModelOptions.set(input.threadId, input.modelOptions); | 378 | threadModelSelections.set(input.threadId, input.modelSelection); |
| 388 | } | 379 | } |
| 389 | const normalizedInput = toNonEmptyProviderInput(input.messageText); | 380 | const normalizedInput = toNonEmptyProviderInput(input.messageText); |
| 390 | const normalizedAttachments = input.attachments ?? []; | 381 | const normalizedAttachments = input.attachments ?? []; |
| @@ -398,14 +389,20 @@ const make = Effect.gen(function* () { · 7 unmodified lines | |||
| 398 | ? "in-session" | 389 | ? "in-session" |
| 399 | : (yield* providerService.getCapabilities(activeSession.provider)).sessionModelSwitch; | 390 | : (yield* providerService.getCapabilities(activeSession.provider)).sessionModelSwitch; |
| 400 | const modelForTurn = | 391 | const modelForTurn = |
| 401 | sessionModelSwitch === "unsupported" ? activeSession?.model : input.modelSelection?.model; | 392 | sessionModelSwitch === "unsupported" |
| 393 | ? input.modelSelection | ||
| 394 | ? { | ||
| 395 | ...input.modelSelection, | ||
| 396 | model: activeSession?.model ?? input.modelSelection.model, | ||
| 397 | } | ||
| 398 | : undefined | ||
| 399 | : input.modelSelection; | ||
| 402 | 400 | ||
| 403 | yield* providerService.sendTurn({ | 401 | yield* providerService.sendTurn({ |
| 404 | threadId: input.threadId, | 402 | threadId: input.threadId, |
| 405 | ...(normalizedInput ? { input: normalizedInput } : {}), | 403 | ...(normalizedInput ? { input: normalizedInput } : {}), |
| 406 | ...(normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {}), | 404 | ...(normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {}), |
| 407 | ...(modelForTurn !== undefined ? { model: modelForTurn } : {}), | 405 | ...(modelForTurn !== undefined ? { modelSelection: modelForTurn } : {}), |
| 408 | ...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}), | ||
| 409 | ...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}), | 406 | ...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}), |
| 410 | }); | 407 | }); |
| 411 | }); | 408 | }); |
| @@ -514,10 +511,6 @@ const make = Effect.gen(function* () { · 102 unmodified lines | |||
| 514 | ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), | 511 | ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), |
| 515 | }).pipe(Effect.forkScoped); | 512 | }).pipe(Effect.forkScoped); |
| 516 | 513 | ||
| 517 | const requestedModelOptions = event.payload.modelSelection | ||
| 518 | ? toProviderModelOptions(event.payload.modelSelection) | ||
| 519 | : undefined; | ||
| 520 | |||
| 521 | yield* sendTurnForThread({ | 514 | yield* sendTurnForThread({ |
| 522 | threadId: event.payload.threadId, | 515 | threadId: event.payload.threadId, |
| 523 | messageText: message.text, | 516 | messageText: message.text, |
| @@ -525,7 +518,6 @@ const make = Effect.gen(function* () { · 1 unmodified line | |||
| 525 | ...(event.payload.modelSelection !== undefined | 518 | ...(event.payload.modelSelection !== undefined |
| 526 | ? { modelSelection: event.payload.modelSelection } | 519 | ? { modelSelection: event.payload.modelSelection } |
| 527 | : {}), | 520 | : {}), |
| 528 | ...(requestedModelOptions !== undefined ? { modelOptions: requestedModelOptions } : {}), | ||
| 529 | ...(event.payload.providerOptions !== undefined | 521 | ...(event.payload.providerOptions !== undefined |
| 530 | ? { providerOptions: event.payload.providerOptions } | 522 | ? { providerOptions: event.payload.providerOptions } |
| 531 | : {}), | 523 | : {}), |
| @@ -695,12 +687,12 @@ const make = Effect.gen(function* () { · 163 unmodified lines | |||
| 695 | return; | 687 | return; |
| 696 | } | 688 | } |
| 697 | const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId); | 689 | const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId); |
| 698 | const cachedModelOptions = threadModelOptions.get(event.payload.threadId); | 690 | const cachedModelSelection = threadModelSelections.get(event.payload.threadId); |
| 699 | yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, { | 691 | yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, { |
| 700 | ...(cachedProviderOptions !== undefined | 692 | ...(cachedProviderOptions !== undefined |
| 701 | ? { providerOptions: cachedProviderOptions } | 693 | ? { providerOptions: cachedProviderOptions } |
| 702 | : {}), | 694 | : {}), |
| 703 | ...(cachedModelOptions !== undefined ? { modelOptions: cachedModelOptions } : {}), | 695 | ...(cachedModelSelection !== undefined ? { modelSelection: cachedModelSelection } : {}), |
| 704 | }); | 696 | }); |
| 705 | return; | 697 | return; |
| 706 | } | 698 | } |
| @@ -332,13 +332,14 @@ describe("ClaudeAdapterLive", () => { · 331 unmodified lines | |||
| 332 | yield* adapter.startSession({ | 332 | yield* adapter.startSession({ |
| 333 | threadId: THREAD_ID, | 333 | threadId: THREAD_ID, |
| 334 | provider: "claudeAgent", | 334 | provider: "claudeAgent", |
| 335 | model: "claude-opus-4-6", | 335 | modelSelection: { |
| 336 | runtimeMode: "full-access", | 336 | provider: "claudeAgent", |
| 337 | modelOptions: { | 337 | model: "claude-opus-4-6", |
| 338 | claudeAgent: { | 338 | options: { |
| 339 | effort: "max", | 339 | effort: "max", |
| 340 | }, | 340 | }, |
| 341 | }, | 341 | }, |
| 342 | runtimeMode: "full-access", | ||
| 342 | }); | 343 | }); |
| 343 | 344 | ||
| 344 | const createInput = harness.getLastCreateQueryInput(); | 345 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -356,13 +357,14 @@ describe("ClaudeAdapterLive", () => { · 11 unmodified lines | |||
| 356 | yield* adapter.startSession({ | 357 | yield* adapter.startSession({ |
| 357 | threadId: THREAD_ID, | 358 | threadId: THREAD_ID, |
| 358 | provider: "claudeAgent", | 359 | provider: "claudeAgent", |
| 359 | model: "claude-sonnet-4-6", | 360 | modelSelection: { |
| 360 | runtimeMode: "full-access", | 361 | provider: "claudeAgent", |
| 361 | modelOptions: { | 362 | model: "claude-sonnet-4-6", |
| 362 | claudeAgent: { | 363 | options: { |
| 363 | effort: "max", | 364 | effort: "max", |
| 364 | }, | 365 | }, |
| 365 | }, | 366 | }, |
| 367 | runtimeMode: "full-access", | ||
| 366 | }); | 368 | }); |
| 367 | 369 | ||
| 368 | const createInput = harness.getLastCreateQueryInput(); | 370 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -380,13 +382,14 @@ describe("ClaudeAdapterLive", () => { · 11 unmodified lines | |||
| 380 | yield* adapter.startSession({ | 382 | yield* adapter.startSession({ |
| 381 | threadId: THREAD_ID, | 383 | threadId: THREAD_ID, |
| 382 | provider: "claudeAgent", | 384 | provider: "claudeAgent", |
| 383 | model: "claude-haiku-4-5", | 385 | modelSelection: { |
| 384 | runtimeMode: "full-access", | 386 | provider: "claudeAgent", |
| 385 | modelOptions: { | 387 | model: "claude-haiku-4-5", |
| 386 | claudeAgent: { | 388 | options: { |
| 387 | effort: "high", | 389 | effort: "high", |
| 388 | }, | 390 | }, |
| 389 | }, | 391 | }, |
| 392 | runtimeMode: "full-access", | ||
| 390 | }); | 393 | }); |
| 391 | 394 | ||
| 392 | const createInput = harness.getLastCreateQueryInput(); | 395 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -404,13 +407,14 @@ describe("ClaudeAdapterLive", () => { · 11 unmodified lines | |||
| 404 | yield* adapter.startSession({ | 407 | yield* adapter.startSession({ |
| 405 | threadId: THREAD_ID, | 408 | threadId: THREAD_ID, |
| 406 | provider: "claudeAgent", | 409 | provider: "claudeAgent", |
| 407 | model: "claude-haiku-4-5", | 410 | modelSelection: { |
| 408 | runtimeMode: "full-access", | 411 | provider: "claudeAgent", |
| 409 | modelOptions: { | 412 | model: "claude-haiku-4-5", |
| 410 | claudeAgent: { | 413 | options: { |
| 411 | thinking: false, | 414 | thinking: false, |
| 412 | }, | 415 | }, |
| 413 | }, | 416 | }, |
| 417 | runtimeMode: "full-access", | ||
| 414 | }); | 418 | }); |
| 415 | 419 | ||
| 416 | const createInput = harness.getLastCreateQueryInput(); | 420 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -430,13 +434,14 @@ describe("ClaudeAdapterLive", () => { · 13 unmodified lines | |||
| 430 | yield* adapter.startSession({ | 434 | yield* adapter.startSession({ |
| 431 | threadId: THREAD_ID, | 435 | threadId: THREAD_ID, |
| 432 | provider: "claudeAgent", | 436 | provider: "claudeAgent", |
| 433 | model: "claude-sonnet-4-6", | 437 | modelSelection: { |
| 434 | runtimeMode: "full-access", | 438 | provider: "claudeAgent", |
| 435 | modelOptions: { | 439 | model: "claude-sonnet-4-6", |
| 436 | claudeAgent: { | 440 | options: { |
| 437 | thinking: false, | 441 | thinking: false, |
| 438 | }, | 442 | }, |
| 439 | }, | 443 | }, |
| 444 | runtimeMode: "full-access", | ||
| 440 | }); | 445 | }); |
| 441 | 446 | ||
| 442 | const createInput = harness.getLastCreateQueryInput(); | 447 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -454,13 +459,14 @@ describe("ClaudeAdapterLive", () => { · 11 unmodified lines | |||
| 454 | yield* adapter.startSession({ | 459 | yield* adapter.startSession({ |
| 455 | threadId: THREAD_ID, | 460 | threadId: THREAD_ID, |
| 456 | provider: "claudeAgent", | 461 | provider: "claudeAgent", |
| 457 | model: "claude-opus-4-6", | 462 | modelSelection: { |
| 458 | runtimeMode: "full-access", | 463 | provider: "claudeAgent", |
| 459 | modelOptions: { | 464 | model: "claude-opus-4-6", |
| 460 | claudeAgent: { | 465 | options: { |
| 461 | fastMode: true, | 466 | fastMode: true, |
| 462 | }, | 467 | }, |
| 463 | }, | 468 | }, |
| 469 | runtimeMode: "full-access", | ||
| 464 | }); | 470 | }); |
| 465 | 471 | ||
| 466 | const createInput = harness.getLastCreateQueryInput(); | 472 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -480,13 +486,14 @@ describe("ClaudeAdapterLive", () => { · 13 unmodified lines | |||
| 480 | yield* adapter.startSession({ | 486 | yield* adapter.startSession({ |
| 481 | threadId: THREAD_ID, | 487 | threadId: THREAD_ID, |
| 482 | provider: "claudeAgent", | 488 | provider: "claudeAgent", |
| 483 | model: "claude-sonnet-4-6", | 489 | modelSelection: { |
| 484 | runtimeMode: "full-access", | 490 | provider: "claudeAgent", |
| 485 | modelOptions: { | 491 | model: "claude-sonnet-4-6", |
| 486 | claudeAgent: { | 492 | options: { |
| 487 | fastMode: true, | 493 | fastMode: true, |
| 488 | }, | 494 | }, |
| 489 | }, | 495 | }, |
| 496 | runtimeMode: "full-access", | ||
| 490 | }); | 497 | }); |
| 491 | 498 | ||
| 492 | const createInput = harness.getLastCreateQueryInput(); | 499 | const createInput = harness.getLastCreateQueryInput(); |
| @@ -504,22 +511,24 @@ describe("ClaudeAdapterLive", () => { · 11 unmodified lines | |||
| 504 | const session = yield* adapter.startSession({ | 511 | const session = yield* adapter.startSession({ |
| 505 | threadId: THREAD_ID, | 512 | threadId: THREAD_ID, |
| 506 | provider: "claudeAgent", | 513 | provider: "claudeAgent", |
| 507 | model: "claude-sonnet-4-6", | 514 | modelSelection: { |
| 508 | runtimeMode: "full-access", | 515 | provider: "claudeAgent", |
| 509 | modelOptions: { | 516 | model: "claude-sonnet-4-6", |
| 510 | claudeAgent: { | 517 | options: { |
| 511 | effort: "ultrathink", | 518 | effort: "ultrathink", |
| 512 | }, | 519 | }, |
| 513 | }, | 520 | }, |
| 521 | runtimeMode: "full-access", | ||
| 514 | }); | 522 | }); |
| 515 | 523 | ||
| 516 | yield* adapter.sendTurn({ | 524 | yield* adapter.sendTurn({ |
| 517 | threadId: session.threadId, | 525 | threadId: session.threadId, |
| 518 | input: "Investigate the edge cases", | 526 | input: "Investigate the edge cases", |
| 519 | attachments: [], | 527 | attachments: [], |
| 520 | model: "claude-sonnet-4-6", | 528 | modelSelection: { |
| 521 | modelOptions: { | 529 | provider: "claudeAgent", |
| 522 | claudeAgent: { | 530 | model: "claude-sonnet-4-6", |
| 531 | options: { | ||
| 523 | effort: "ultrathink", | 532 | effort: "ultrathink", |
| 524 | }, | 533 | }, |
| 525 | }, | 534 | }, |
| @@ -613,7 +622,10 @@ describe("ClaudeAdapterLive", () => { · 87 unmodified lines | |||
| 613 | const session = yield* adapter.startSession({ | 622 | const session = yield* adapter.startSession({ |
| 614 | threadId: THREAD_ID, | 623 | threadId: THREAD_ID, |
| 615 | provider: "claudeAgent", | 624 | provider: "claudeAgent", |
| 616 | model: "claude-sonnet-4-5", | 625 | modelSelection: { |
| 626 | provider: "claudeAgent", | ||
| 627 | model: "claude-sonnet-4-5", | ||
| 628 | }, | ||
| 617 | runtimeMode: "full-access", | 629 | runtimeMode: "full-access", |
| 618 | }); | 630 | }); |
| 619 | 631 | ||
| @@ -2239,7 +2251,10 @@ describe("ClaudeAdapterLive", () => { · 1619 unmodified lines | |||
| 2239 | yield* adapter.sendTurn({ | 2251 | yield* adapter.sendTurn({ |
| 2240 | threadId: session.threadId, | 2252 | threadId: session.threadId, |
| 2241 | input: "hello", | 2253 | input: "hello", |
| 2242 | model: "claude-opus-4-6", | 2254 | modelSelection: { |
| 2255 | provider: "claudeAgent", | ||
| 2256 | model: "claude-opus-4-6", | ||
| 2257 | }, | ||
| 2243 | attachments: [], | 2258 | attachments: [], |
| 2244 | }); | 2259 | }); |
| 2245 | 2260 | ||
| @@ -436,11 +436,17 @@ const CLAUDE_SETTING_SOURCES = [ · 435 unmodified lines | |||
| 436 | function buildPromptText(input: ProviderSendTurnInput): string { | 436 | function buildPromptText(input: ProviderSendTurnInput): string { |
| 437 | const requestedEffort = resolveReasoningEffortForProvider( | 437 | const requestedEffort = resolveReasoningEffortForProvider( |
| 438 | "claudeAgent", | 438 | "claudeAgent", |
| 439 | input.modelOptions?.claudeAgent?.effort ?? null, | 439 | input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.options?.effort : null, |
| 440 | ); | ||
| 441 | const supportedEffortOptions = getReasoningEffortOptions( | ||
| 442 | "claudeAgent", | ||
| 443 | input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.model : undefined, | ||
| 440 | ); | 444 | ); |
| 441 | const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model); | ||
| 442 | const promptEffort = | 445 | const promptEffort = |
| 443 | requestedEffort === "ultrathink" && supportsClaudeUltrathinkKeyword(input.model) | 446 | requestedEffort === "ultrathink" && |
| 447 | supportsClaudeUltrathinkKeyword( | ||
| 448 | input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.model : undefined, | ||
| 449 | ) | ||
| 444 | ? "ultrathink" | 450 | ? "ultrathink" |
| 445 | : requestedEffort && supportedEffortOptions.includes(requestedEffort) | 451 | : requestedEffort && supportedEffortOptions.includes(requestedEffort) |
| 446 | ? requestedEffort | 452 | ? requestedEffort |
| @@ -2542,21 +2548,27 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 2095 unmodified lines | |||
| 2542 | ); | 2548 | ); |
| 2543 | 2549 | ||
| 2544 | const providerOptions = input.providerOptions?.claudeAgent; | 2550 | const providerOptions = input.providerOptions?.claudeAgent; |
| 2551 | const modelSelection = | ||
| 2552 | input.modelSelection?.provider === "claudeAgent" ? input.modelSelection : undefined; | ||
| 2545 | const requestedEffort = resolveReasoningEffortForProvider( | 2553 | const requestedEffort = resolveReasoningEffortForProvider( |
| 2546 | "claudeAgent", | 2554 | "claudeAgent", |
| 2547 | input.modelOptions?.claudeAgent?.effort ?? null, | 2555 | modelSelection?.options?.effort ?? null, |
| 2556 | ); | ||
| 2557 | const supportedEffortOptions = getReasoningEffortOptions( | ||
| 2558 | "claudeAgent", | ||
| 2559 | modelSelection?.model, | ||
| 2548 | ); | 2560 | ); |
| 2549 | const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model); | ||
| 2550 | const effort = | 2561 | const effort = |
| 2551 | requestedEffort && supportedEffortOptions.includes(requestedEffort) | 2562 | requestedEffort && supportedEffortOptions.includes(requestedEffort) |
| 2552 | ? requestedEffort | 2563 | ? requestedEffort |
| 2553 | : null; | 2564 | : null; |
| 2554 | const fastMode = | 2565 | const fastMode = |
| 2555 | input.modelOptions?.claudeAgent?.fastMode === true && supportsClaudeFastMode(input.model); | 2566 | modelSelection?.options?.fastMode === true && |
| 2567 | supportsClaudeFastMode(modelSelection?.model); | ||
| 2556 | const thinking = | 2568 | const thinking = |
| 2557 | typeof input.modelOptions?.claudeAgent?.thinking === "boolean" && | 2569 | typeof modelSelection?.options?.thinking === "boolean" && |
| 2558 | supportsClaudeThinkingToggle(input.model) | 2570 | supportsClaudeThinkingToggle(modelSelection?.model) |
| 2559 | ? input.modelOptions.claudeAgent.thinking | 2571 | ? modelSelection.options.thinking |
| 2560 | : undefined; | 2572 | : undefined; |
| 2561 | const effectiveEffort = getEffectiveClaudeCodeEffort(effort); | 2573 | const effectiveEffort = getEffectiveClaudeCodeEffort(effort); |
| 2562 | const permissionMode = | 2574 | const permissionMode = |
| @@ -2569,7 +2581,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 6 unmodified lines | |||
| 2569 | 2581 | ||
| 2570 | const queryOptions: ClaudeQueryOptions = { | 2582 | const queryOptions: ClaudeQueryOptions = { |
| 2571 | ...(input.cwd ? { cwd: input.cwd } : {}), | 2583 | ...(input.cwd ? { cwd: input.cwd } : {}), |
| 2572 | ...(input.model ? { model: input.model } : {}), | 2584 | ...(modelSelection?.model ? { model: modelSelection.model } : {}), |
| 2573 | pathToClaudeCodeExecutable: providerOptions?.binaryPath ?? "claude", | 2585 | pathToClaudeCodeExecutable: providerOptions?.binaryPath ?? "claude", |
| 2574 | settingSources: [...CLAUDE_SETTING_SOURCES], | 2586 | settingSources: [...CLAUDE_SETTING_SOURCES], |
| 2575 | ...(effectiveEffort ? { effort: effectiveEffort } : {}), | 2587 | ...(effectiveEffort ? { effort: effectiveEffort } : {}), |
| @@ -2610,7 +2622,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 34 unmodified lines | |||
| 2610 | status: "ready", | 2622 | status: "ready", |
| 2611 | runtimeMode: input.runtimeMode, | 2623 | runtimeMode: input.runtimeMode, |
| 2612 | ...(input.cwd ? { cwd: input.cwd } : {}), | 2624 | ...(input.cwd ? { cwd: input.cwd } : {}), |
| 2613 | ...(input.model ? { model: input.model } : {}), | 2625 | ...(modelSelection?.model ? { model: modelSelection.model } : {}), |
| 2614 | ...(threadId ? { threadId } : {}), | 2626 | ...(threadId ? { threadId } : {}), |
| 2615 | resumeCursor: { | 2627 | resumeCursor: { |
| 2616 | ...(threadId ? { threadId } : {}), | 2628 | ...(threadId ? { threadId } : {}), |
| @@ -2664,7 +2676,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 47 unmodified lines | |||
| 2664 | threadId, | 2676 | threadId, |
| 2665 | payload: { | 2677 | payload: { |
| 2666 | config: { | 2678 | config: { |
| 2667 | ...(input.model ? { model: input.model } : {}), | 2679 | ...(modelSelection?.model ? { model: modelSelection.model } : {}), |
| 2668 | ...(input.cwd ? { cwd: input.cwd } : {}), | 2680 | ...(input.cwd ? { cwd: input.cwd } : {}), |
| 2669 | ...(effectiveEffort ? { effort: effectiveEffort } : {}), | 2681 | ...(effectiveEffort ? { effort: effectiveEffort } : {}), |
| 2670 | ...(permissionMode ? { permissionMode } : {}), | 2682 | ...(permissionMode ? { permissionMode } : {}), |
| @@ -2710,6 +2722,8 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 39 unmodified lines | |||
| 2710 | const sendTurn: ClaudeAdapterShape["sendTurn"] = (input) => | 2722 | const sendTurn: ClaudeAdapterShape["sendTurn"] = (input) => |
| 2711 | Effect.gen(function* () { | 2723 | Effect.gen(function* () { |
| 2712 | const context = yield* requireSession(input.threadId); | 2724 | const context = yield* requireSession(input.threadId); |
| 2725 | const modelSelection = | ||
| 2726 | input.modelSelection?.provider === "claudeAgent" ? input.modelSelection : undefined; | ||
| 2713 | 2727 | ||
| 2714 | if (context.turnState) { | 2728 | if (context.turnState) { |
| 2715 | // Auto-close a stale synthetic turn (from background agent responses | 2729 | // Auto-close a stale synthetic turn (from background agent responses |
| @@ -2717,9 +2731,9 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 1 unmodified line | |||
| 2717 | yield* completeTurn(context, "completed"); | 2731 | yield* completeTurn(context, "completed"); |
| 2718 | } | 2732 | } |
| 2719 | 2733 | ||
| 2720 | if (input.model) { | 2734 | if (modelSelection?.model) { |
| 2721 | yield* Effect.tryPromise({ | 2735 | yield* Effect.tryPromise({ |
| 2722 | try: () => context.query.setModel(input.model), | 2736 | try: () => context.query.setModel(modelSelection.model), |
| 2723 | catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause), | 2737 | catch: (cause) => toRequestError(input.threadId, "turn/setModel", cause), |
| 2724 | }); | 2738 | }); |
| 2725 | } | 2739 | } |
| @@ -2769,7 +2783,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { · 43 unmodified lines | |||
| 2769 | createdAt: turnStartedStamp.createdAt, | 2783 | createdAt: turnStartedStamp.createdAt, |
| 2770 | threadId: context.session.threadId, | 2784 | threadId: context.session.threadId, |
| 2771 | turnId, | 2785 | turnId, |
| 2772 | payload: input.model ? { model: input.model } : {}, | 2786 | payload: modelSelection?.model ? { model: modelSelection.model } : {}, |
| 2773 | providerRefs: {}, | 2787 | providerRefs: {}, |
| 2774 | }); | 2788 | }); |
| 2775 | 2789 | ||
| @@ -188,9 +188,10 @@ validationLayer("CodexAdapterLive validation", (it) => { · 187 unmodified lines | |||
| 188 | yield* adapter.startSession({ | 188 | yield* adapter.startSession({ |
| 189 | provider: "codex", | 189 | provider: "codex", |
| 190 | threadId: asThreadId("thread-1"), | 190 | threadId: asThreadId("thread-1"), |
| 191 | model: "gpt-5.3-codex", | 191 | modelSelection: { |
| 192 | modelOptions: { | 192 | provider: "codex", |
| 193 | codex: { | 193 | model: "gpt-5.3-codex", |
| 194 | options: { | ||
| 194 | fastMode: true, | 195 | fastMode: true, |
| 195 | }, | 196 | }, |
| 196 | }, | 197 | }, |
| @@ -256,9 +257,10 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => { · 59 unmodified lines | |||
| 256 | adapter.sendTurn({ | 257 | adapter.sendTurn({ |
| 257 | threadId: asThreadId("sess-missing"), | 258 | threadId: asThreadId("sess-missing"), |
| 258 | input: "hello", | 259 | input: "hello", |
| 259 | model: "gpt-5.3-codex", | 260 | modelSelection: { |
| 260 | modelOptions: { | 261 | provider: "codex", |
| 261 | codex: { | 262 | model: "gpt-5.3-codex", |
| 263 | options: { | ||
| 262 | reasoningEffort: "high", | 264 | reasoningEffort: "high", |
| 263 | fastMode: true, | 265 | fastMode: true, |
| 264 | }, | 266 | }, |
| @@ -1313,8 +1313,12 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => · 1312 unmodified lines | |||
| 1313 | ...(input.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}), | 1313 | ...(input.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}), |
| 1314 | ...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}), | 1314 | ...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}), |
| 1315 | runtimeMode: input.runtimeMode, | 1315 | runtimeMode: input.runtimeMode, |
| 1316 | ...(input.model !== undefined ? { model: input.model } : {}), | 1316 | ...(input.modelSelection?.provider === "codex" |
| 1317 | ...(input.modelOptions?.codex?.fastMode ? { serviceTier: "fast" } : {}), | 1317 | ? { model: input.modelSelection.model } |
| 1318 | : {}), | ||
| 1319 | ...(input.modelSelection?.provider === "codex" && input.modelSelection.options?.fastMode | ||
| 1320 | ? { serviceTier: "fast" } | ||
| 1321 | : {}), | ||
| 1318 | }; | 1322 | }; |
| 1319 | 1323 | ||
| 1320 | return Effect.tryPromise({ | 1324 | return Effect.tryPromise({ |
| @@ -1370,11 +1374,17 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => · 49 unmodified lines | |||
| 1370 | const managerInput = { | 1374 | const managerInput = { |
| 1371 | threadId: input.threadId, | 1375 | threadId: input.threadId, |
| 1372 | ...(input.input !== undefined ? { input: input.input } : {}), | 1376 | ...(input.input !== undefined ? { input: input.input } : {}), |
| 1373 | ...(input.model !== undefined ? { model: input.model } : {}), | 1377 | ...(input.modelSelection?.provider === "codex" |
| 1374 | ...(input.modelOptions?.codex?.reasoningEffort !== undefined | 1378 | ? { model: input.modelSelection.model } |
| 1375 | ? { effort: input.modelOptions.codex.reasoningEffort } | 1379 | : {}), |
| 1380 | ...(input.modelSelection?.provider === "codex" && | ||
| 1381 | input.modelSelection.options?.reasoningEffort !== undefined | ||
| 1382 | ? { effort: input.modelSelection.options.reasoningEffort } | ||
| 1383 | : {}), | ||
| 1384 | ...(input.modelSelection?.provider === "codex" && | ||
| 1385 | input.modelSelection.options?.fastMode | ||
| 1386 | ? { serviceTier: "fast" } | ||
| 1376 | : {}), | 1387 | : {}), |
| 1377 | ...(input.modelOptions?.codex?.fastMode ? { serviceTier: "fast" } : {}), | ||
| 1378 | ...(input.interactionMode !== undefined | 1388 | ...(input.interactionMode !== undefined |
| 1379 | ? { interactionMode: input.interactionMode } | 1389 | ? { interactionMode: input.interactionMode } |
| 1380 | : {}), | 1390 | : {}), |
| @@ -633,8 +633,10 @@ routing.layer("ProviderServiceLive routing", (it) => { · 632 unmodified lines | |||
| 633 | provider: "claudeAgent", | 633 | provider: "claudeAgent", |
| 634 | threadId: asThreadId("thread-claude-send-turn"), | 634 | threadId: asThreadId("thread-claude-send-turn"), |
| 635 | cwd: "/tmp/project-claude-send-turn", | 635 | cwd: "/tmp/project-claude-send-turn", |
| 636 | modelOptions: { | 636 | modelSelection: { |
| 637 | claudeAgent: { | 637 | provider: "claudeAgent", |
| 638 | model: "claude-opus-4-6", | ||
| 639 | options: { | ||
| 638 | effort: "max", | 640 | effort: "max", |
| 639 | }, | 641 | }, |
| 640 | }, | 642 | }, |
| @@ -658,14 +660,16 @@ routing.layer("ProviderServiceLive routing", (it) => { · 17 unmodified lines | |||
| 658 | const startPayload = resumedStartInput as { | 660 | const startPayload = resumedStartInput as { |
| 659 | provider?: string; | 661 | provider?: string; |
| 660 | cwd?: string; | 662 | cwd?: string; |
| 661 | modelOptions?: unknown; | 663 | modelSelection?: unknown; |
| 662 | resumeCursor?: unknown; | 664 | resumeCursor?: unknown; |
| 663 | threadId?: string; | 665 | threadId?: string; |
| 664 | }; | 666 | }; |
| 665 | assert.equal(startPayload.provider, "claudeAgent"); | 667 | assert.equal(startPayload.provider, "claudeAgent"); |
| 666 | assert.equal(startPayload.cwd, "/tmp/project-claude-send-turn"); | 668 | assert.equal(startPayload.cwd, "/tmp/project-claude-send-turn"); |
| 667 | assert.deepEqual(startPayload.modelOptions, { | 669 | assert.deepEqual(startPayload.modelSelection, { |
| 668 | claudeAgent: { | 670 | provider: "claudeAgent", |
| 671 | model: "claude-opus-4-6", | ||
| 672 | options: { | ||
| 669 | effort: "max", | 673 | effort: "max", |
| 670 | }, | 674 | }, |
| 671 | }); | 675 | }); |
| @@ -10,6 +10,7 @@ · 9 unmodified lines | |||
| 10 | * @module ProviderServiceLive | 10 | * @module ProviderServiceLive |
| 11 | */ | 11 | */ |
| 12 | import { | 12 | import { |
| 13 | ModelSelection, | ||
| 13 | NonNegativeInt, | 14 | NonNegativeInt, |
| 14 | ThreadId, | 15 | ThreadId, |
| 15 | ProviderInterruptTurnInput, | 16 | ProviderInterruptTurnInput, |
| @@ -89,7 +90,7 @@ function toRuntimeStatus(session: ProviderSession): "starting" | "running" | "st · 73 unmodified lines | |||
| 89 | function toRuntimePayloadFromSession( | 90 | function toRuntimePayloadFromSession( |
| 90 | session: ProviderSession, | 91 | session: ProviderSession, |
| 91 | extra?: { | 92 | extra?: { |
| 92 | readonly modelOptions?: unknown; | 93 | readonly modelSelection?: unknown; |
| 93 | readonly providerOptions?: unknown; | 94 | readonly providerOptions?: unknown; |
| 94 | readonly lastRuntimeEvent?: string; | 95 | readonly lastRuntimeEvent?: string; |
| 95 | readonly lastRuntimeEventAt?: string; | 96 | readonly lastRuntimeEventAt?: string; |
| @@ -100,7 +101,7 @@ function toRuntimePayloadFromSession( · 4 unmodified lines | |||
| 100 | model: session.model ?? null, | 101 | model: session.model ?? null, |
| 101 | activeTurnId: session.activeTurnId ?? null, | 102 | activeTurnId: session.activeTurnId ?? null, |
| 102 | lastError: session.lastError ?? null, | 103 | lastError: session.lastError ?? null, |
| 103 | ...(extra?.modelOptions !== undefined ? { modelOptions: extra.modelOptions } : {}), | 104 | ...(extra?.modelSelection !== undefined ? { modelSelection: extra.modelSelection } : {}), |
| 104 | ...(extra?.providerOptions !== undefined ? { providerOptions: extra.providerOptions } : {}), | 105 | ...(extra?.providerOptions !== undefined ? { providerOptions: extra.providerOptions } : {}), |
| 105 | ...(extra?.lastRuntimeEvent !== undefined ? { lastRuntimeEvent: extra.lastRuntimeEvent } : {}), | 106 | ...(extra?.lastRuntimeEvent !== undefined ? { lastRuntimeEvent: extra.lastRuntimeEvent } : {}), |
| 106 | ...(extra?.lastRuntimeEventAt !== undefined | 107 | ...(extra?.lastRuntimeEventAt !== undefined |
| @@ -109,15 +110,14 @@ function toRuntimePayloadFromSession( · 2 unmodified lines | |||
| 109 | }; | 110 | }; |
| 110 | } | 111 | } |
| 111 | 112 | ||
| 112 | function readPersistedModelOptions( | 113 | function readPersistedModelSelection( |
| 113 | runtimePayload: ProviderRuntimeBinding["runtimePayload"], | 114 | runtimePayload: ProviderRuntimeBinding["runtimePayload"], |
| 114 | ): Record<string, unknown> | undefined { | 115 | ): ModelSelection | undefined { |
| 115 | if (!runtimePayload || typeof runtimePayload !== "object" || Array.isArray(runtimePayload)) { | 116 | if (!runtimePayload || typeof runtimePayload !== "object" || Array.isArray(runtimePayload)) { |
| 116 | return undefined; | 117 | return undefined; |
| 117 | } | 118 | } |
| 118 | const raw = "modelOptions" in runtimePayload ? runtimePayload.modelOptions : undefined; | 119 | const raw = "modelSelection" in runtimePayload ? runtimePayload.modelSelection : undefined; |
| 119 | if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined; | 120 | return Schema.is(ModelSelection)(raw) ? raw : undefined; |
| 120 | return raw as Record<string, unknown>; | ||
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | function readPersistedProviderOptions( | 123 | function readPersistedProviderOptions( |
| @@ -172,7 +172,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 48 unmodified lines | |||
| 172 | session: ProviderSession, | 172 | session: ProviderSession, |
| 173 | threadId: ThreadId, | 173 | threadId: ThreadId, |
| 174 | extra?: { | 174 | extra?: { |
| 175 | readonly modelOptions?: unknown; | 175 | readonly modelSelection?: unknown; |
| 176 | readonly providerOptions?: unknown; | 176 | readonly providerOptions?: unknown; |
| 177 | readonly lastRuntimeEvent?: string; | 177 | readonly lastRuntimeEvent?: string; |
| 178 | readonly lastRuntimeEventAt?: string; | 178 | readonly lastRuntimeEventAt?: string; |
| @@ -238,14 +238,14 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 59 unmodified lines | |||
| 238 | } | 238 | } |
| 239 | 239 | ||
| 240 | const persistedCwd = readPersistedCwd(input.binding.runtimePayload); | 240 | const persistedCwd = readPersistedCwd(input.binding.runtimePayload); |
| 241 | const persistedModelOptions = readPersistedModelOptions(input.binding.runtimePayload); | 241 | const persistedModelSelection = readPersistedModelSelection(input.binding.runtimePayload); |
| 242 | const persistedProviderOptions = readPersistedProviderOptions(input.binding.runtimePayload); | 242 | const persistedProviderOptions = readPersistedProviderOptions(input.binding.runtimePayload); |
| 243 | 243 | ||
| 244 | const resumed = yield* adapter.startSession({ | 244 | const resumed = yield* adapter.startSession({ |
| 245 | threadId: input.binding.threadId, | 245 | threadId: input.binding.threadId, |
| 246 | provider: input.binding.provider, | 246 | provider: input.binding.provider, |
| 247 | ...(persistedCwd ? { cwd: persistedCwd } : {}), | 247 | ...(persistedCwd ? { cwd: persistedCwd } : {}), |
| 248 | ...(persistedModelOptions ? { modelOptions: persistedModelOptions } : {}), | 248 | ...(persistedModelSelection ? { modelSelection: persistedModelSelection } : {}), |
| 249 | ...(persistedProviderOptions ? { providerOptions: persistedProviderOptions } : {}), | 249 | ...(persistedProviderOptions ? { providerOptions: persistedProviderOptions } : {}), |
| 250 | ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}), | 250 | ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}), |
| 251 | runtimeMode: input.binding.runtimeMode ?? "full-access", | 251 | runtimeMode: input.binding.runtimeMode ?? "full-access", |
| @@ -328,7 +328,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 76 unmodified lines | |||
| 328 | } | 328 | } |
| 329 | 329 | ||
| 330 | yield* upsertSessionBinding(session, threadId, { | 330 | yield* upsertSessionBinding(session, threadId, { |
| 331 | modelOptions: input.modelOptions, | 331 | modelSelection: input.modelSelection, |
| 332 | providerOptions: input.providerOptions, | 332 | providerOptions: input.providerOptions, |
| 333 | }); | 333 | }); |
| 334 | yield* analytics.record("provider.session.started", { | 334 | yield* analytics.record("provider.session.started", { |
| @@ -336,7 +336,9 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 1 unmodified line | |||
| 336 | runtimeMode: input.runtimeMode, | 336 | runtimeMode: input.runtimeMode, |
| 337 | hasResumeCursor: session.resumeCursor !== undefined, | 337 | hasResumeCursor: session.resumeCursor !== undefined, |
| 338 | hasCwd: typeof input.cwd === "string" && input.cwd.trim().length > 0, | 338 | hasCwd: typeof input.cwd === "string" && input.cwd.trim().length > 0, |
| 339 | hasModel: typeof input.model === "string" && input.model.trim().length > 0, | 339 | hasModel: |
| 340 | typeof input.modelSelection?.model === "string" && | ||
| 341 | input.modelSelection.model.trim().length > 0, | ||
| 340 | }); | 342 | }); |
| 341 | 343 | ||
| 342 | return session; | 344 | return session; |
| @@ -372,6 +374,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 29 unmodified lines | |||
| 372 | status: "running", | 374 | status: "running", |
| 373 | ...(turn.resumeCursor !== undefined ? { resumeCursor: turn.resumeCursor } : {}), | 375 | ...(turn.resumeCursor !== undefined ? { resumeCursor: turn.resumeCursor } : {}), |
| 374 | runtimePayload: { | 376 | runtimePayload: { |
| 377 | ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), | ||
| 375 | activeTurnId: turn.turnId, | 378 | activeTurnId: turn.turnId, |
| 376 | lastRuntimeEvent: "provider.sendTurn", | 379 | lastRuntimeEvent: "provider.sendTurn", |
| 377 | lastRuntimeEventAt: new Date().toISOString(), | 380 | lastRuntimeEventAt: new Date().toISOString(), |
| @@ -379,7 +382,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 1 unmodified line | |||
| 379 | }); | 382 | }); |
| 380 | yield* analytics.record("provider.turn.sent", { | 383 | yield* analytics.record("provider.turn.sent", { |
| 381 | provider: routed.adapter.provider, | 384 | provider: routed.adapter.provider, |
| 382 | model: input.model, | 385 | model: input.modelSelection?.model, |
| 383 | interactionMode: input.interactionMode, | 386 | interactionMode: input.interactionMode, |
| 384 | attachmentCount: input.attachments.length, | 387 | attachmentCount: input.attachments.length, |
| 385 | hasInput: typeof input.input === "string" && input.input.trim().length > 0, | 388 | hasInput: typeof input.input === "string" && input.input.trim().length > 0, |
| @@ -7,7 +7,6 @@ import { · 6 unmodified lines | |||
| 7 | type ProjectScript, | 7 | type ProjectScript, |
| 8 | type ModelSlug, | 8 | type ModelSlug, |
| 9 | type ProviderKind, | 9 | type ProviderKind, |
| 10 | type ProviderModelOptions, | ||
| 11 | type ProjectEntry, | 10 | type ProjectEntry, |
| 12 | type ProjectId, | 11 | type ProjectId, |
| 13 | type ProviderApprovalDecision, | 12 | type ProviderApprovalDecision, |
| @@ -28,7 +27,6 @@ import { · 14 unmodified lines | |||
| 28 | getDefaultModel, | 27 | getDefaultModel, |
| 29 | normalizeModelSlug, | 28 | normalizeModelSlug, |
| 30 | resolveModelSlugForProvider, | 29 | resolveModelSlugForProvider, |
| 31 | toProviderModelOptions, | ||
| 32 | } from "@t3tools/shared/model"; | 30 | } from "@t3tools/shared/model"; |
| 33 | import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; | 31 | import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; |
| 34 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | 32 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; |
| @@ -205,12 +203,6 @@ function formatOutgoingPrompt(params: { · 170 unmodified lines | |||
| 205 | } | 203 | } |
| 206 | return params.text; | 204 | return params.text; |
| 207 | } | 205 | } |
| 208 | function extractModelSelectionOptions( | ||
| 209 | provider: ProviderKind, | ||
| 210 | modelOptions: ProviderModelOptions | undefined, | ||
| 211 | ): ModelSelection["options"] | undefined { | ||
| 212 | return provider === "codex" ? modelOptions?.codex : modelOptions?.claudeAgent; | ||
| 213 | } | ||
| 214 | const COMPOSER_PATH_QUERY_DEBOUNCE_MS = 120; | 206 | const COMPOSER_PATH_QUERY_DEBOUNCE_MS = 120; |
| 215 | const SCRIPT_TERMINAL_COLS = 120; | 207 | const SCRIPT_TERMINAL_COLS = 120; |
| 216 | const SCRIPT_TERMINAL_ROWS = 30; | 208 | const SCRIPT_TERMINAL_ROWS = 30; |
| @@ -630,16 +622,15 @@ export default function ChatView({ threadId }: ChatViewProps) { · 413 unmodified lines | |||
| 630 | customModelsByProvider, | 622 | customModelsByProvider, |
| 631 | selectedProvider, | 623 | selectedProvider, |
| 632 | ]); | 624 | ]); |
| 633 | const draftModelOptions = toProviderModelOptions(composerDraft.modelSelection); | ||
| 634 | const composerProviderState = useMemo( | 625 | const composerProviderState = useMemo( |
| 635 | () => | 626 | () => |
| 636 | getComposerProviderState({ | 627 | getComposerProviderState({ |
| 637 | provider: selectedProvider, | 628 | provider: selectedProvider, |
| 638 | model: selectedModel, | 629 | model: selectedModel, |
| 639 | prompt, | 630 | prompt, |
| 640 | modelOptions: draftModelOptions, | 631 | modelOptions: composerDraft.modelSelection?.options, |
| 641 | }), | 632 | }), |
| 642 | [draftModelOptions, prompt, selectedModel, selectedProvider], | 633 | [composerDraft.modelSelection?.options, prompt, selectedModel, selectedProvider], |
| 643 | ); | 634 | ); |
| 644 | const selectedPromptEffort = composerProviderState.promptEffort; | 635 | const selectedPromptEffort = composerProviderState.promptEffort; |
| 645 | const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; | 636 | const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; |
| @@ -647,14 +638,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1 unmodified line | |||
| 647 | () => ({ | 638 | () => ({ |
| 648 | provider: selectedProvider, | 639 | provider: selectedProvider, |
| 649 | model: selectedModel, | 640 | model: selectedModel, |
| 650 | ...(extractModelSelectionOptions(selectedProvider, selectedModelOptionsForDispatch) | 641 | ...(selectedModelOptionsForDispatch ? { options: selectedModelOptionsForDispatch } : {}), |
| 651 | ? { | ||
| 652 | options: extractModelSelectionOptions( | ||
| 653 | selectedProvider, | ||
| 654 | selectedModelOptionsForDispatch, | ||
| 655 | ), | ||
| 656 | } | ||
| 657 | : {}), | ||
| 658 | }), | 642 | }), |
| 659 | [selectedModel, selectedModelOptionsForDispatch, selectedProvider], | 643 | [selectedModel, selectedModelOptionsForDispatch, selectedProvider], |
| 660 | ); | 644 | ); |
| @@ -23,10 +23,8 @@ describe("getComposerProviderState", () => { · 22 unmodified lines | |||
| 23 | model: "gpt-5.4", | 23 | model: "gpt-5.4", |
| 24 | prompt: "", | 24 | prompt: "", |
| 25 | modelOptions: { | 25 | modelOptions: { |
| 26 | codex: { | 26 | reasoningEffort: "low", |
| 27 | reasoningEffort: "low", | 27 | fastMode: true, |
| 28 | fastMode: true, | ||
| 29 | }, | ||
| 30 | }, | 28 | }, |
| 31 | }); | 29 | }); |
| 32 | 30 | ||
| @@ -34,10 +32,8 @@ describe("getComposerProviderState", () => { · 1 unmodified line | |||
| 34 | provider: "codex", | 32 | provider: "codex", |
| 35 | promptEffort: "low", | 33 | promptEffort: "low", |
| 36 | modelOptionsForDispatch: { | 34 | modelOptionsForDispatch: { |
| 37 | codex: { | 35 | reasoningEffort: "low", |
| 38 | reasoningEffort: "low", | 36 | fastMode: true, |
| 39 | fastMode: true, | ||
| 40 | }, | ||
| 41 | }, | 37 | }, |
| 42 | }); | 38 | }); |
| 43 | }); | 39 | }); |
| @@ -63,9 +59,7 @@ describe("getComposerProviderState", () => { · 19 unmodified lines | |||
| 63 | model: "claude-sonnet-4-6", | 59 | model: "claude-sonnet-4-6", |
| 64 | prompt: "Ultrathink:\nInvestigate this failure", | 60 | prompt: "Ultrathink:\nInvestigate this failure", |
| 65 | modelOptions: { | 61 | modelOptions: { |
| 66 | claudeAgent: { | 62 | effort: "medium", |
| 67 | effort: "medium", | ||
| 68 | }, | ||
| 69 | }, | 63 | }, |
| 70 | }); | 64 | }); |
| 71 | 65 | ||
| @@ -73,9 +67,7 @@ describe("getComposerProviderState", () => { · 1 unmodified line | |||
| 73 | provider: "claudeAgent", | 67 | provider: "claudeAgent", |
| 74 | promptEffort: "medium", | 68 | promptEffort: "medium", |
| 75 | modelOptionsForDispatch: { | 69 | modelOptionsForDispatch: { |
| 76 | claudeAgent: { | 70 | effort: "medium", |
| 77 | effort: "medium", | ||
| 78 | }, | ||
| 79 | }, | 71 | }, |
| 80 | composerFrameClassName: "ultrathink-frame", | 72 | composerFrameClassName: "ultrathink-frame", |
| 81 | composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]", | 73 | composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]", |
| @@ -89,10 +81,8 @@ describe("getComposerProviderState", () => { · 7 unmodified lines | |||
| 89 | model: "claude-haiku-4-5", | 81 | model: "claude-haiku-4-5", |
| 90 | prompt: "", | 82 | prompt: "", |
| 91 | modelOptions: { | 83 | modelOptions: { |
| 92 | claudeAgent: { | 84 | effort: "max", |
| 93 | effort: "max", | 85 | thinking: false, |
| 94 | thinking: false, | ||
| 95 | }, | ||
| 96 | }, | 86 | }, |
| 97 | }); | 87 | }); |
| 98 | 88 | ||
| @@ -100,50 +90,8 @@ describe("getComposerProviderState", () => { · 1 unmodified line | |||
| 100 | provider: "claudeAgent", | 90 | provider: "claudeAgent", |
| 101 | promptEffort: null, | 91 | promptEffort: null, |
| 102 | modelOptionsForDispatch: { | 92 | modelOptionsForDispatch: { |
| 103 | claudeAgent: { | 93 | thinking: false, |
| 104 | thinking: false, | ||
| 105 | }, | ||
| 106 | }, | 94 | }, |
| 107 | }); | 95 | }); |
| 108 | }); | 96 | }); |
| 109 | |||
| 110 | it("ignores codex options while resolving Claude state", () => { | ||
| 111 | const state = getComposerProviderState({ | ||
| 112 | provider: "claudeAgent", | ||
| 113 | model: "claude-opus-4-6", | ||
| 114 | prompt: "", | ||
| 115 | modelOptions: { | ||
| 116 | codex: { | ||
| 117 | reasoningEffort: "low", | ||
| 118 | fastMode: true, | ||
| 119 | }, | ||
| 120 | }, | ||
| 121 | }); | ||
| 122 | |||
| 123 | expect(state).toEqual({ | ||
| 124 | provider: "claudeAgent", | ||
| 125 | promptEffort: "high", | ||
| 126 | modelOptionsForDispatch: undefined, | ||
| 127 | }); | ||
| 128 | }); | ||
| 129 | |||
| 130 | it("ignores Claude options while resolving codex state", () => { | ||
| 131 | const state = getComposerProviderState({ | ||
| 132 | provider: "codex", | ||
| 133 | model: "gpt-5.4", | ||
| 134 | prompt: "Ultrathink:\nThis should not matter", | ||
| 135 | modelOptions: { | ||
| 136 | claudeAgent: { | ||
| 137 | effort: "max", | ||
| 138 | fastMode: true, | ||
| 139 | }, | ||
| 140 | }, | ||
| 141 | }); | ||
| 142 | |||
| 143 | expect(state).toEqual({ | ||
| 144 | provider: "codex", | ||
| 145 | promptEffort: "high", | ||
| 146 | modelOptionsForDispatch: undefined, | ||
| 147 | }); | ||
| 148 | }); | ||
| 149 | }); | 97 | }); |
| @@ -1,7 +1,9 @@ | |||
| 1 | import { | 1 | import { |
| 2 | type ClaudeModelOptions, | ||
| 3 | type CodexModelOptions, | ||
| 2 | type ModelSlug, | 4 | type ModelSlug, |
| 5 | type ModelSelection, | ||
| 3 | type ProviderKind, | 6 | type ProviderKind, |
| 4 | type ProviderModelOptions, | ||
| 5 | type ThreadId, | 7 | type ThreadId, |
| 6 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 7 | import { | 9 | import { |
| @@ -21,13 +23,13 @@ export type ComposerProviderStateInput = { · 13 unmodified lines | |||
| 21 | provider: ProviderKind; | 23 | provider: ProviderKind; |
| 22 | model: ModelSlug; | 24 | model: ModelSlug; |
| 23 | prompt: string; | 25 | prompt: string; |
| 24 | modelOptions: ProviderModelOptions | null | undefined; | 26 | modelOptions: ModelSelection["options"] | null | undefined; |
| 25 | }; | 27 | }; |
| 26 | 28 | ||
| 27 | export type ComposerProviderState = { | 29 | export type ComposerProviderState = { |
| 28 | provider: ProviderKind; | 30 | provider: ProviderKind; |
| 29 | promptEffort: string | null; | 31 | promptEffort: string | null; |
| 30 | modelOptionsForDispatch: ProviderModelOptions | undefined; | 32 | modelOptionsForDispatch: ModelSelection["options"] | undefined; |
| 31 | composerFrameClassName?: string; | 33 | composerFrameClassName?: string; |
| 32 | composerSurfaceClassName?: string; | 34 | composerSurfaceClassName?: string; |
| 33 | modelPickerIconClassName?: string; | 35 | modelPickerIconClassName?: string; |
| @@ -47,20 +49,33 @@ type ProviderRegistryEntry = { · 13 unmodified lines | |||
| 47 | }) => ReactNode; | 49 | }) => ReactNode; |
| 48 | }; | 50 | }; |
| 49 | 51 | ||
| 52 | function asCodexModelOptions( | ||
| 53 | modelOptions: ModelSelection["options"] | null | undefined, | ||
| 54 | ): CodexModelOptions | undefined { | ||
| 55 | return modelOptions && "reasoningEffort" in modelOptions ? modelOptions : undefined; | ||
| 56 | } | ||
| 57 | |||
| 58 | function asClaudeModelOptions( | ||
| 59 | modelOptions: ModelSelection["options"] | null | undefined, | ||
| 60 | ): ClaudeModelOptions | undefined { | ||
| 61 | return modelOptions && ("effort" in modelOptions || "thinking" in modelOptions) | ||
| 62 | ? modelOptions | ||
| 63 | : undefined; | ||
| 64 | } | ||
| 65 | |||
| 50 | const composerProviderRegistry: Record<ProviderKind, ProviderRegistryEntry> = { | 66 | const composerProviderRegistry: Record<ProviderKind, ProviderRegistryEntry> = { |
| 51 | codex: { | 67 | codex: { |
| 52 | getState: ({ modelOptions }) => { | 68 | getState: ({ modelOptions }) => { |
| 69 | const codexModelOptions = asCodexModelOptions(modelOptions); | ||
| 53 | const promptEffort = | 70 | const promptEffort = |
| 54 | resolveReasoningEffortForProvider("codex", modelOptions?.codex?.reasoningEffort) ?? | 71 | resolveReasoningEffortForProvider("codex", codexModelOptions?.reasoningEffort) ?? |
| 55 | getDefaultReasoningEffort("codex"); | 72 | getDefaultReasoningEffort("codex"); |
| 56 | const normalizedCodexOptions = normalizeCodexModelOptions(modelOptions?.codex); | 73 | const normalizedCodexOptions = normalizeCodexModelOptions(codexModelOptions); |
| 57 | 74 | ||
| 58 | return { | 75 | return { |
| 59 | provider: "codex", | 76 | provider: "codex", |
| 60 | promptEffort, | 77 | promptEffort, |
| 61 | modelOptionsForDispatch: normalizedCodexOptions | 78 | modelOptionsForDispatch: normalizedCodexOptions, |
| 62 | ? { codex: normalizedCodexOptions } | ||
| 63 | : undefined, | ||
| 64 | }; | 79 | }; |
| 65 | }, | 80 | }, |
| 66 | renderTraitsMenuContent: ({ threadId }) => <CodexTraitsMenuContent threadId={threadId} />, | 81 | renderTraitsMenuContent: ({ threadId }) => <CodexTraitsMenuContent threadId={threadId} />, |
| @@ -68,10 +83,11 @@ const composerProviderRegistry: Record<ProviderKind, ProviderRegistryEntry> = { · 1 unmodified line | |||
| 68 | }, | 83 | }, |
| 69 | claudeAgent: { | 84 | claudeAgent: { |
| 70 | getState: ({ model, prompt, modelOptions }) => { | 85 | getState: ({ model, prompt, modelOptions }) => { |
| 86 | const claudeModelOptions = asClaudeModelOptions(modelOptions); | ||
| 71 | const reasoningOptions = getReasoningEffortOptions("claudeAgent", model); | 87 | const reasoningOptions = getReasoningEffortOptions("claudeAgent", model); |
| 72 | const draftEffort = resolveReasoningEffortForProvider( | 88 | const draftEffort = resolveReasoningEffortForProvider( |
| 73 | "claudeAgent", | 89 | "claudeAgent", |
| 74 | modelOptions?.claudeAgent?.effort, | 90 | claudeModelOptions?.effort, |
| 75 | ); | 91 | ); |
| 76 | const defaultEffort = getDefaultReasoningEffort("claudeAgent"); | 92 | const defaultEffort = getDefaultReasoningEffort("claudeAgent"); |
| 77 | const promptEffort = | 93 | const promptEffort = |
| @@ -80,16 +96,14 @@ const composerProviderRegistry: Record<ProviderKind, ProviderRegistryEntry> = { · 2 unmodified lines | |||
| 80 | : reasoningOptions.includes(defaultEffort) | 96 | : reasoningOptions.includes(defaultEffort) |
| 81 | ? defaultEffort | 97 | ? defaultEffort |
| 82 | : null; | 98 | : null; |
| 83 | const normalizedClaudeOptions = normalizeClaudeModelOptions(model, modelOptions?.claudeAgent); | 99 | const normalizedClaudeOptions = normalizeClaudeModelOptions(model, claudeModelOptions); |
| 84 | const ultrathinkActive = | 100 | const ultrathinkActive = |
| 85 | supportsClaudeUltrathinkKeyword(model) && isClaudeUltrathinkPrompt(prompt); | 101 | supportsClaudeUltrathinkKeyword(model) && isClaudeUltrathinkPrompt(prompt); |
| 86 | 102 | ||
| 87 | return { | 103 | return { |
| 88 | provider: "claudeAgent", | 104 | provider: "claudeAgent", |
| 89 | promptEffort, | 105 | promptEffort, |
| 90 | modelOptionsForDispatch: normalizedClaudeOptions | 106 | modelOptionsForDispatch: normalizedClaudeOptions, |
| 91 | ? { claudeAgent: normalizedClaudeOptions } | ||
| 92 | : undefined, | ||
| 93 | ...(ultrathinkActive ? { composerFrameClassName: "ultrathink-frame" } : {}), | 107 | ...(ultrathinkActive ? { composerFrameClassName: "ultrathink-frame" } : {}), |
| 94 | ...(ultrathinkActive | 108 | ...(ultrathinkActive |
| 95 | ? { composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]" } | 109 | ? { composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]" } |
| @@ -133,7 +133,7 @@ function normalizeLegacyThreadModelSelection( · 132 unmodified lines | |||
| 133 | return Effect.succeed(input.modelSelection); | 133 | return Effect.succeed(input.modelSelection); |
| 134 | } | 134 | } |
| 135 | if (input.model === undefined) { | 135 | if (input.model === undefined) { |
| 136 | return Effect.succeed(undefined); | 136 | return Effect.succeed<ModelSelection | undefined>(undefined); |
| 137 | } | 137 | } |
| 138 | return Effect.try({ | 138 | return Effect.try({ |
| 139 | try: () => | 139 | try: () => |
| @@ -12,9 +12,10 @@ describe("ProviderSessionStartInput", () => { · 11 unmodified lines | |||
| 12 | threadId: "thread-1", | 12 | threadId: "thread-1", |
| 13 | provider: "codex", | 13 | provider: "codex", |
| 14 | cwd: "/tmp/workspace", | 14 | cwd: "/tmp/workspace", |
| 15 | model: "gpt-5.3-codex", | 15 | modelSelection: { |
| 16 | modelOptions: { | 16 | provider: "codex", |
| 17 | codex: { | 17 | model: "gpt-5.3-codex", |
| 18 | options: { | ||
| 18 | reasoningEffort: "high", | 19 | reasoningEffort: "high", |
| 19 | fastMode: true, | 20 | fastMode: true, |
| 20 | }, | 21 | }, |
| @@ -28,8 +29,13 @@ describe("ProviderSessionStartInput", () => { · 7 unmodified lines | |||
| 28 | }, | 29 | }, |
| 29 | }); | 30 | }); |
| 30 | expect(parsed.runtimeMode).toBe("full-access"); | 31 | expect(parsed.runtimeMode).toBe("full-access"); |
| 31 | expect(parsed.modelOptions?.codex?.reasoningEffort).toBe("high"); | 32 | expect(parsed.modelSelection?.provider).toBe("codex"); |
| 32 | expect(parsed.modelOptions?.codex?.fastMode).toBe(true); | 33 | expect(parsed.modelSelection?.model).toBe("gpt-5.3-codex"); |
| 34 | if (parsed.modelSelection?.provider !== "codex") { | ||
| 35 | throw new Error("Expected codex modelSelection"); | ||
| 36 | } | ||
| 37 | expect(parsed.modelSelection.options?.reasoningEffort).toBe("high"); | ||
| 38 | expect(parsed.modelSelection.options?.fastMode).toBe(true); | ||
| 33 | expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex"); | 39 | expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex"); |
| 34 | expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex"); | 40 | expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex"); |
| 35 | }); | 41 | }); |
| @@ -48,9 +54,10 @@ describe("ProviderSessionStartInput", () => { · 12 unmodified lines | |||
| 48 | threadId: "thread-1", | 54 | threadId: "thread-1", |
| 49 | provider: "claudeAgent", | 55 | provider: "claudeAgent", |
| 50 | cwd: "/tmp/workspace", | 56 | cwd: "/tmp/workspace", |
| 51 | model: "claude-sonnet-4-6", | 57 | modelSelection: { |
| 52 | modelOptions: { | 58 | provider: "claudeAgent", |
| 53 | claudeAgent: { | 59 | model: "claude-sonnet-4-6", |
| 60 | options: { | ||
| 54 | thinking: true, | 61 | thinking: true, |
| 55 | effort: "max", | 62 | effort: "max", |
| 56 | fastMode: true, | 63 | fastMode: true, |
| @@ -66,9 +73,14 @@ describe("ProviderSessionStartInput", () => { · 9 unmodified lines | |||
| 66 | runtimeMode: "full-access", | 73 | runtimeMode: "full-access", |
| 67 | }); | 74 | }); |
| 68 | expect(parsed.provider).toBe("claudeAgent"); | 75 | expect(parsed.provider).toBe("claudeAgent"); |
| 69 | expect(parsed.modelOptions?.claudeAgent?.thinking).toBe(true); | 76 | expect(parsed.modelSelection?.provider).toBe("claudeAgent"); |
| 70 | expect(parsed.modelOptions?.claudeAgent?.effort).toBe("max"); | 77 | expect(parsed.modelSelection?.model).toBe("claude-sonnet-4-6"); |
| 71 | expect(parsed.modelOptions?.claudeAgent?.fastMode).toBe(true); | 78 | if (parsed.modelSelection?.provider !== "claudeAgent") { |
| 79 | throw new Error("Expected claude modelSelection"); | ||
| 80 | } | ||
| 81 | expect(parsed.modelSelection.options?.thinking).toBe(true); | ||
| 82 | expect(parsed.modelSelection.options?.effort).toBe("max"); | ||
| 83 | expect(parsed.modelSelection.options?.fastMode).toBe(true); | ||
| 72 | expect(parsed.providerOptions?.claudeAgent?.binaryPath).toBe("/usr/local/bin/claude"); | 84 | expect(parsed.providerOptions?.claudeAgent?.binaryPath).toBe("/usr/local/bin/claude"); |
| 73 | expect(parsed.providerOptions?.claudeAgent?.permissionMode).toBe("plan"); | 85 | expect(parsed.providerOptions?.claudeAgent?.permissionMode).toBe("plan"); |
| 74 | expect(parsed.providerOptions?.claudeAgent?.maxThinkingTokens).toBe(12_000); | 86 | expect(parsed.providerOptions?.claudeAgent?.maxThinkingTokens).toBe(12_000); |
| @@ -77,36 +89,46 @@ describe("ProviderSessionStartInput", () => { · 2 unmodified lines | |||
| 77 | }); | 89 | }); |
| 78 | 90 | ||
| 79 | describe("ProviderSendTurnInput", () => { | 91 | describe("ProviderSendTurnInput", () => { |
| 80 | it("accepts provider-scoped model options", () => { | 92 | it("accepts codex modelSelection", () => { |
| 81 | const parsed = decodeProviderSendTurnInput({ | 93 | const parsed = decodeProviderSendTurnInput({ |
| 82 | threadId: "thread-1", | 94 | threadId: "thread-1", |
| 83 | model: "gpt-5.3-codex", | 95 | modelSelection: { |
| 84 | modelOptions: { | 96 | provider: "codex", |
| 85 | codex: { | 97 | model: "gpt-5.3-codex", |
| 98 | options: { | ||
| 86 | reasoningEffort: "xhigh", | 99 | reasoningEffort: "xhigh", |
| 87 | fastMode: true, | 100 | fastMode: true, |
| 88 | }, | 101 | }, |
| 89 | }, | 102 | }, |
| 90 | }); | 103 | }); |
| 91 | 104 | ||
| 92 | expect(parsed.model).toBe("gpt-5.3-codex"); | 105 | expect(parsed.modelSelection?.provider).toBe("codex"); |
| 93 | expect(parsed.modelOptions?.codex?.reasoningEffort).toBe("xhigh"); | 106 | expect(parsed.modelSelection?.model).toBe("gpt-5.3-codex"); |
| 94 | expect(parsed.modelOptions?.codex?.fastMode).toBe(true); | 107 | if (parsed.modelSelection?.provider !== "codex") { |
| 108 | throw new Error("Expected codex modelSelection"); | ||
| 109 | } | ||
| 110 | expect(parsed.modelSelection.options?.reasoningEffort).toBe("xhigh"); | ||
| 111 | expect(parsed.modelSelection.options?.fastMode).toBe(true); | ||
| 95 | }); | 112 | }); |
| 96 | 113 | ||
| 97 | it("accepts claude provider effort options including ultrathink", () => { | 114 | it("accepts claude modelSelection including ultrathink", () => { |
| 98 | const parsed = decodeProviderSendTurnInput({ | 115 | const parsed = decodeProviderSendTurnInput({ |
| 99 | threadId: "thread-1", | 116 | threadId: "thread-1", |
| 100 | model: "claude-sonnet-4-6", | 117 | modelSelection: { |
| 101 | modelOptions: { | 118 | provider: "claudeAgent", |
| 102 | claudeAgent: { | 119 | model: "claude-sonnet-4-6", |
| 120 | options: { | ||
| 103 | effort: "ultrathink", | 121 | effort: "ultrathink", |
| 104 | fastMode: true, | 122 | fastMode: true, |
| 105 | }, | 123 | }, |
| 106 | }, | 124 | }, |
| 107 | }); | 125 | }); |
| 108 | 126 | ||
| 109 | expect(parsed.modelOptions?.claudeAgent?.effort).toBe("ultrathink"); | 127 | expect(parsed.modelSelection?.provider).toBe("claudeAgent"); |
| 110 | expect(parsed.modelOptions?.claudeAgent?.fastMode).toBe(true); | 128 | if (parsed.modelSelection?.provider !== "claudeAgent") { |
| 129 | throw new Error("Expected claude modelSelection"); | ||
| 130 | } | ||
| 131 | expect(parsed.modelSelection.options?.effort).toBe("ultrathink"); | ||
| 132 | expect(parsed.modelSelection.options?.fastMode).toBe(true); | ||
| 111 | }); | 133 | }); |
| 112 | }); | 134 | }); |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { Schema } from "effect"; | 1 | import { Schema } from "effect"; |
| 2 | import { TrimmedNonEmptyString } from "./baseSchemas"; | 2 | import { TrimmedNonEmptyString } from "./baseSchemas"; |
| 3 | import { ProviderModelOptions } from "./model"; | ||
| 4 | import { | 3 | import { |
| 5 | ApprovalRequestId, | 4 | ApprovalRequestId, |
| 6 | EventId, | 5 | EventId, |
| @@ -11,6 +10,7 @@ import { · 4 unmodified lines | |||
| 11 | } from "./baseSchemas"; | 10 | } from "./baseSchemas"; |
| 12 | import { | 11 | import { |
| 13 | ChatAttachment, | 12 | ChatAttachment, |
| 13 | ModelSelection, | ||
| 14 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, | 14 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 15 | PROVIDER_SEND_TURN_MAX_INPUT_CHARS, | 15 | PROVIDER_SEND_TURN_MAX_INPUT_CHARS, |
| 16 | ProviderApprovalDecision, | 16 | ProviderApprovalDecision, |
| @@ -51,8 +51,7 @@ export const ProviderSessionStartInput = Schema.Struct({ · 34 unmodified lines | |||
| 51 | threadId: ThreadId, | 51 | threadId: ThreadId, |
| 52 | provider: Schema.optional(ProviderKind), | 52 | provider: Schema.optional(ProviderKind), |
| 53 | cwd: Schema.optional(TrimmedNonEmptyString), | 53 | cwd: Schema.optional(TrimmedNonEmptyString), |
| 54 | model: Schema.optional(TrimmedNonEmptyString), | 54 | modelSelection: Schema.optional(ModelSelection), |
| 55 | modelOptions: Schema.optional(ProviderModelOptions), | ||
| 56 | resumeCursor: Schema.optional(Schema.Unknown), | 55 | resumeCursor: Schema.optional(Schema.Unknown), |
| 57 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | 56 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), |
| 58 | sandboxMode: Schema.optional(ProviderSandboxMode), | 57 | sandboxMode: Schema.optional(ProviderSandboxMode), |
| @@ -69,8 +68,7 @@ export const ProviderSendTurnInput = Schema.Struct({ · 10 unmodified lines | |||
| 69 | attachments: Schema.optional( | 68 | attachments: Schema.optional( |
| 70 | Schema.Array(ChatAttachment).check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS)), | 69 | Schema.Array(ChatAttachment).check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS)), |
| 71 | ), | 70 | ), |
| 72 | model: Schema.optional(TrimmedNonEmptyString), | 71 | modelSelection: Schema.optional(ModelSelection), |
| 73 | modelOptions: Schema.optional(ProviderModelOptions), | ||
| 74 | interactionMode: Schema.optional(ProviderInteractionMode), | 72 | interactionMode: Schema.optional(ProviderInteractionMode), |
| 75 | }); | 73 | }); |
| 76 | export type ProviderSendTurnInput = typeof ProviderSendTurnInput.Type; | 74 | export type ProviderSendTurnInput = typeof ProviderSendTurnInput.Type; |
| @@ -27,7 +27,6 @@ import { · 26 unmodified lines | |||
| 27 | supportsClaudeFastMode, | 27 | supportsClaudeFastMode, |
| 28 | supportsClaudeMaxEffort, | 28 | supportsClaudeMaxEffort, |
| 29 | supportsClaudeThinkingToggle, | 29 | supportsClaudeThinkingToggle, |
| 30 | toProviderModelOptions, | ||
| 31 | supportsClaudeUltrathinkKeyword, | 30 | supportsClaudeUltrathinkKeyword, |
| 32 | } from "./model"; | 31 | } from "./model"; |
| 33 | 32 | ||
| @@ -273,45 +272,6 @@ describe("normalizeClaudeModelOptions", () => { · 239 unmodified lines | |||
| 273 | }); | 272 | }); |
| 274 | }); | 273 | }); |
| 275 | 274 | ||
| 276 | describe("toProviderModelOptions", () => { | ||
| 277 | it("returns undefined when no model options exist", () => { | ||
| 278 | expect(toProviderModelOptions({ provider: "codex", model: "gpt-5.4" })).toBeUndefined(); | ||
| 279 | expect(toProviderModelOptions(null)).toBeUndefined(); | ||
| 280 | }); | ||
| 281 | |||
| 282 | it("scopes codex options under the codex provider key", () => { | ||
| 283 | expect( | ||
| 284 | toProviderModelOptions({ | ||
| 285 | provider: "codex", | ||
| 286 | model: "gpt-5.4", | ||
| 287 | options: { | ||
| 288 | fastMode: true, | ||
| 289 | }, | ||
| 290 | }), | ||
| 291 | ).toEqual({ | ||
| 292 | codex: { | ||
| 293 | fastMode: true, | ||
| 294 | }, | ||
| 295 | }); | ||
| 296 | }); | ||
| 297 | |||
| 298 | it("scopes claude options under the claudeAgent provider key", () => { | ||
| 299 | expect( | ||
| 300 | toProviderModelOptions({ | ||
| 301 | provider: "claudeAgent", | ||
| 302 | model: "claude-opus-4-6", | ||
| 303 | options: { | ||
| 304 | effort: "max", | ||
| 305 | }, | ||
| 306 | }), | ||
| 307 | ).toEqual({ | ||
| 308 | claudeAgent: { | ||
| 309 | effort: "max", | ||
| 310 | }, | ||
| 311 | }); | ||
| 312 | }); | ||
| 313 | }); | ||
| 314 | |||
| 315 | describe("supportsClaudeAdaptiveReasoning", () => { | 275 | describe("supportsClaudeAdaptiveReasoning", () => { |
| 316 | it("only enables adaptive reasoning for Opus 4.6 and Sonnet 4.6", () => { | 276 | it("only enables adaptive reasoning for Opus 4.6 and Sonnet 4.6", () => { |
| 317 | expect(supportsClaudeAdaptiveReasoning("claude-opus-4-6")).toBe(true); | 277 | expect(supportsClaudeAdaptiveReasoning("claude-opus-4-6")).toBe(true); |
| @@ -10,9 +10,7 @@ import { · 9 unmodified lines | |||
| 10 | type ClaudeCodeEffort, | 10 | type ClaudeCodeEffort, |
| 11 | type CodexModelOptions, | 11 | type CodexModelOptions, |
| 12 | type CodexReasoningEffort, | 12 | type CodexReasoningEffort, |
| 13 | type ModelSelection, | ||
| 14 | type ModelSlug, | 13 | type ModelSlug, |
| 15 | type ProviderModelOptions, | ||
| 16 | type ProviderReasoningEffort, | 14 | type ProviderReasoningEffort, |
| 17 | type ProviderKind, | 15 | type ProviderKind, |
| 18 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| @@ -268,15 +266,4 @@ export function applyClaudePromptEffortPrefix( · 249 unmodified lines | |||
| 268 | return `Ultrathink:\n${trimmed}`; | 266 | return `Ultrathink:\n${trimmed}`; |
| 269 | } | 267 | } |
| 270 | 268 | ||
| 271 | export function toProviderModelOptions( | ||
| 272 | modelSelection: ModelSelection | null | undefined, | ||
| 273 | ): ProviderModelOptions | undefined { | ||
| 274 | if (!modelSelection?.options) { | ||
| 275 | return undefined; | ||
| 276 | } | ||
| 277 | return modelSelection.provider === "codex" | ||
| 278 | ? { codex: modelSelection.options } | ||
| 279 | : { claudeAgent: modelSelection.options }; | ||
| 280 | } | ||
| 281 | |||
| 282 | export { CLAUDE_CODE_EFFORT_OPTIONS, CODEX_REASONING_EFFORT_OPTIONS }; | 269 | export { CLAUDE_CODE_EFFORT_OPTIONS, CODEX_REASONING_EFFORT_OPTIONS }; |