History

split: move Claude provider surface to sibling stack

Julius Marminge committed 8 months ago

Commit 6496aed1 parent e972fad23 files · +48 −506Browse files

Unified · Split

23 files
  1. apps/server/integration/OrchestrationEngineHarness.integration.ts +1 −1
  2. apps/server/integration/TestProviderAdapter.integration.ts +5 −5
  3. apps/server/integration/orchestrationEngine.integration.test.ts +1 −414
  4. apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +1 −1
  5. apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +0 −1
  6. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +1 −1
  7. apps/server/src/provider/Layers/CodexAdapter.test.ts +2 −2
  8. apps/server/src/provider/Layers/ProviderAdapterRegistry.ts +1 −1
  9. apps/server/src/provider/Layers/ProviderService.test.ts +1 −1
  10. apps/server/src/provider/Layers/ProviderSessionDirectory.ts +1 −1
  11. apps/web/src/appSettings.test.ts +3 −9
  12. apps/web/src/appSettings.ts +0 −5
  13. apps/web/src/components/ChatView.tsx +16 −11
  14. apps/web/src/composerDraftStore.ts +1 −1
  15. apps/web/src/routes/_chat.settings.tsx +0 −7
  16. apps/web/src/session-logic.ts +3 −1
  17. apps/web/src/store.test.ts +2 −24
  18. apps/web/src/store.ts +3 −11
  19. packages/contracts/src/orchestration.test.ts +2 −2
  20. packages/contracts/src/orchestration.ts +1 −1
  21. packages/contracts/src/providerRuntime.test.ts +3 −3
  22. packages/contracts/src/providerRuntime.ts +0 −2
  23. packages/shared/src/model.test.ts +0 −1
apps/server/integration/OrchestrationEngineHarness.integration.ts+1−1
@@ -188,7 +188,7 @@ export interface OrchestrationIntegrationHarness { · 187 unmodified lines
188188 }
189189
190190 interface MakeOrchestrationIntegrationHarnessOptions {
191− readonly provider?: "codex" | "claudeCode";
191+ readonly provider?: "codex" | "cursor";
192192 readonly realCodex?: boolean;
193193 }
194194
apps/server/integration/TestProviderAdapter.integration.ts+5−5
@@ -35,7 +35,7 @@ export interface TestTurnResponse { · 34 unmodified lines
3535 export type FixtureProviderRuntimeEvent = {
3636 readonly type: string;
3737 readonly eventId: EventId;
38− readonly provider: "codex" | "claudeCode" | "cursor";
38+ readonly provider: "codex" | "cursor";
3939 readonly createdAt: string;
4040 readonly threadId: string;
4141 readonly turnId?: string | undefined;
@@ -178,7 +178,7 @@ function normalizeFixtureEvent(rawEvent: Record<string, unknown>): ProviderRunti · 136 unmodified lines
178178
179179 export interface TestProviderAdapterHarness {
180180 readonly adapter: ProviderAdapterShape<ProviderAdapterError>;
181− readonly provider: "codex" | "claudeCode";
181+ readonly provider: "codex" | "cursor";
182182 readonly queueTurnResponse: (
183183 threadId: ThreadId,
184184 response: TestTurnResponse,
@@ -198,7 +198,7 @@ export interface TestProviderAdapterHarness { · 13 unmodified lines
198198 }
199199
200200 interface MakeTestProviderAdapterHarnessOptions {
201− readonly provider?: "codex" | "claudeCode";
201+ readonly provider?: "codex" | "cursor";
202202 }
203203
204204 function nowIso(): string {
@@ -206,7 +206,7 @@ function nowIso(): string { · 1 unmodified line
206206 }
207207
208208 function sessionNotFound(
209− provider: "codex" | "claudeCode",
209+ provider: "codex" | "cursor",
210210 threadId: ThreadId,
211211 ): ProviderAdapterSessionNotFoundError {
212212 return new ProviderAdapterSessionNotFoundError({
@@ -216,7 +216,7 @@ function sessionNotFound( · 3 unmodified lines
216216 }
217217
218218 function missingSessionEffect(
219− provider: "codex" | "claudeCode",
219+ provider: "codex" | "cursor",
220220 threadId: ThreadId,
221221 ): Effect.Effect<never, ProviderAdapterError> {
222222 return Effect.fail(sessionNotFound(provider, threadId));
apps/server/integration/orchestrationEngine.integration.test.ts+1−414
@@ -32,7 +32,7 @@ const PROJECT_ID = asProjectId("project-1"); · 31 unmodified lines
3232 const THREAD_ID = ThreadId.makeUnsafe("thread-1");
3333 const FIXTURE_TURN_ID = "fixture-turn";
3434 const APPROVAL_REQUEST_ID = asApprovalRequestId("req-approval-1");
35−type IntegrationProvider = "codex" | "claudeCode";
35+type IntegrationProvider = "codex" | "cursor";
3636
3737 function nowIso() {
3838 return new Date().toISOString();
@@ -842,416 +842,3 @@ it.live( · 803 unmodified lines
842842 }),
843843 ),
844844 );
845−
846−it.live("starts a claudeCode session on first turn when provider is requested", () =>
847− withHarness(
848− (harness) =>
849− Effect.gen(function* () {
850− yield* seedProjectAndThread(harness);
851−
852− yield* harness.adapterHarness.queueTurnResponseForNextSession({
853− events: [
854− {
855− type: "turn.started",
856− ...runtimeBase("evt-claude-start-1", "2026-02-24T10:10:00.000Z", "claudeCode"),
857− threadId: THREAD_ID,
858− turnId: FIXTURE_TURN_ID,
859− },
860− {
861− type: "message.delta",
862− ...runtimeBase("evt-claude-start-2", "2026-02-24T10:10:00.050Z", "claudeCode"),
863− threadId: THREAD_ID,
864− turnId: FIXTURE_TURN_ID,
865− delta: "Claude first turn.\n",
866− },
867− {
868− type: "turn.completed",
869− ...runtimeBase("evt-claude-start-3", "2026-02-24T10:10:00.100Z", "claudeCode"),
870− threadId: THREAD_ID,
871− turnId: FIXTURE_TURN_ID,
872− status: "completed",
873− },
874− ],
875− });
876−
877− yield* startTurn({
878− harness,
879− commandId: "cmd-turn-start-claude-initial",
880− messageId: "msg-user-claude-initial",
881− text: "Use Claude",
882− provider: "claudeCode",
883− });
884−
885− const thread = yield* harness.waitForThread(
886− THREAD_ID,
887− (entry) =>
888− entry.session?.providerName === "claudeCode" &&
889− entry.session.status === "ready" &&
890− entry.messages.some(
891− (message) => message.role === "assistant" && message.text === "Claude first turn.\n",
892− ),
893− );
894− assert.equal(thread.session?.providerName, "claudeCode");
895− }),
896− "claudeCode",
897− ),
898−);
899−
900−it.live("recovers claudeCode sessions after provider stopAll using persisted resume state", () =>
901− withHarness(
902− (harness) =>
903− Effect.gen(function* () {
904− yield* seedProjectAndThread(harness);
905−
906− yield* harness.adapterHarness.queueTurnResponseForNextSession({
907− events: [
908− {
909− type: "turn.started",
910− ...runtimeBase("evt-claude-recover-1", "2026-02-24T10:11:00.000Z", "claudeCode"),
911− threadId: THREAD_ID,
912− turnId: FIXTURE_TURN_ID,
913− },
914− {
915− type: "message.delta",
916− ...runtimeBase("evt-claude-recover-2", "2026-02-24T10:11:00.050Z", "claudeCode"),
917− threadId: THREAD_ID,
918− turnId: FIXTURE_TURN_ID,
919− delta: "Turn before restart.\n",
920− },
921− {
922− type: "turn.completed",
923− ...runtimeBase("evt-claude-recover-3", "2026-02-24T10:11:00.100Z", "claudeCode"),
924− threadId: THREAD_ID,
925− turnId: FIXTURE_TURN_ID,
926− status: "completed",
927− },
928− ],
929− });
930−
931− yield* startTurn({
932− harness,
933− commandId: "cmd-turn-start-claude-recover-1",
934− messageId: "msg-user-claude-recover-1",
935− text: "Before restart",
936− provider: "claudeCode",
937− });
938−
939− yield* harness.waitForThread(
940− THREAD_ID,
941− (entry) => entry.latestTurn?.turnId === "turn-1" && entry.session?.threadId === "thread-1",
942− );
943−
944− yield* harness.providerService.stopAll();
945− yield* waitForSync(
946− () => harness.adapterHarness.listActiveSessionIds(),
947− (sessionIds) => sessionIds.length === 0,
948− "provider stopAll",
949− );
950−
951− yield* harness.adapterHarness.queueTurnResponseForNextSession({
952− events: [
953− {
954− type: "turn.started",
955− ...runtimeBase("evt-claude-recover-4", "2026-02-24T10:11:01.000Z", "claudeCode"),
956− threadId: THREAD_ID,
957− turnId: FIXTURE_TURN_ID,
958− },
959− {
960− type: "message.delta",
961− ...runtimeBase("evt-claude-recover-5", "2026-02-24T10:11:01.050Z", "claudeCode"),
962− threadId: THREAD_ID,
963− turnId: FIXTURE_TURN_ID,
964− delta: "Turn after restart.\n",
965− },
966− {
967− type: "turn.completed",
968− ...runtimeBase("evt-claude-recover-6", "2026-02-24T10:11:01.100Z", "claudeCode"),
969− threadId: THREAD_ID,
970− turnId: FIXTURE_TURN_ID,
971− status: "completed",
972− },
973− ],
974− });
975−
976− yield* startTurn({
977− harness,
978− commandId: "cmd-turn-start-claude-recover-2",
979− messageId: "msg-user-claude-recover-2",
980− text: "After restart",
981− });
982− yield* waitForSync(
983− () => harness.adapterHarness.getStartCount(),
984− (count) => count === 2,
985− "claude provider recovery start",
986− );
987−
988− const recoveredThread = yield* harness.waitForThread(
989− THREAD_ID,
990− (entry) =>
991− entry.session?.providerName === "claudeCode" &&
992− entry.messages.some(
993− (message) => message.role === "user" && message.text === "After restart",
994− ) &&
995− !entry.activities.some((activity) => activity.kind === "provider.turn.start.failed"),
996− );
997− assert.equal(recoveredThread.session?.providerName, "claudeCode");
998− assert.equal(recoveredThread.session?.threadId, "thread-1");
999− }),
1000− "claudeCode",
1001− ),
1002−);
1003−
1004−it.live("forwards claudeCode approval responses to the provider session", () =>
1005− withHarness(
1006− (harness) =>
1007− Effect.gen(function* () {
1008− yield* seedProjectAndThread(harness);
1009−
1010− yield* harness.adapterHarness.queueTurnResponseForNextSession({
1011− events: [
1012− {
1013− type: "turn.started",
1014− ...runtimeBase("evt-claude-approval-1", "2026-02-24T10:12:00.000Z", "claudeCode"),
1015− threadId: THREAD_ID,
1016− turnId: FIXTURE_TURN_ID,
1017− },
1018− {
1019− type: "approval.requested",
1020− ...runtimeBase("evt-claude-approval-2", "2026-02-24T10:12:00.050Z", "claudeCode"),
1021− threadId: THREAD_ID,
1022− turnId: FIXTURE_TURN_ID,
1023− requestId: APPROVAL_REQUEST_ID,
1024− requestKind: "command",
1025− detail: "Approve Claude tool call",
1026− },
1027− {
1028− type: "turn.completed",
1029− ...runtimeBase("evt-claude-approval-3", "2026-02-24T10:12:00.100Z", "claudeCode"),
1030− threadId: THREAD_ID,
1031− turnId: FIXTURE_TURN_ID,
1032− status: "completed",
1033− },
1034− ],
1035− });
1036−
1037− yield* startTurn({
1038− harness,
1039− commandId: "cmd-turn-start-claude-approval",
1040− messageId: "msg-user-claude-approval",
1041− text: "Need approval",
1042− provider: "claudeCode",
1043− });
1044−
1045− const thread = yield* harness.waitForThread(THREAD_ID, (entry) =>
1046− entry.activities.some((activity) => activity.kind === "approval.requested"),
1047− );
1048− assert.equal(thread.session?.threadId, "thread-1");
1049−
1050− yield* harness.engine.dispatch({
1051− type: "thread.approval.respond",
1052− commandId: CommandId.makeUnsafe("cmd-claude-approval-respond"),
1053− threadId: THREAD_ID,
1054− requestId: APPROVAL_REQUEST_ID,
1055− decision: "accept",
1056− createdAt: nowIso(),
1057− });
1058−
1059− yield* harness.waitForPendingApproval(
1060− "req-approval-1",
1061− (row) => row.status === "resolved" && row.decision === "accept",
1062− );
1063−
1064− const approvalResponses = yield* waitForSync(
1065− () => harness.adapterHarness.getApprovalResponses(THREAD_ID),
1066− (responses) => responses.length === 1,
1067− "claude provider approval response",
1068− );
1069− assert.equal(approvalResponses[0]?.decision, "accept");
1070− }),
1071− "claudeCode",
1072− ),
1073−);
1074−
1075−it.live("forwards thread.turn.interrupt to claudeCode provider sessions", () =>
1076− withHarness(
1077− (harness) =>
1078− Effect.gen(function* () {
1079− yield* seedProjectAndThread(harness);
1080−
1081− yield* harness.adapterHarness.queueTurnResponseForNextSession({
1082− events: [
1083− {
1084− type: "turn.started",
1085− ...runtimeBase("evt-claude-interrupt-1", "2026-02-24T10:13:00.000Z", "claudeCode"),
1086− threadId: THREAD_ID,
1087− turnId: FIXTURE_TURN_ID,
1088− },
1089− {
1090− type: "message.delta",
1091− ...runtimeBase("evt-claude-interrupt-2", "2026-02-24T10:13:00.050Z", "claudeCode"),
1092− threadId: THREAD_ID,
1093− turnId: FIXTURE_TURN_ID,
1094− delta: "Long running output.\n",
1095− },
1096− {
1097− type: "turn.completed",
1098− ...runtimeBase("evt-claude-interrupt-3", "2026-02-24T10:13:00.100Z", "claudeCode"),
1099− threadId: THREAD_ID,
1100− turnId: FIXTURE_TURN_ID,
1101− status: "completed",
1102− },
1103− ],
1104− });
1105−
1106− yield* startTurn({
1107− harness,
1108− commandId: "cmd-turn-start-claude-interrupt",
1109− messageId: "msg-user-claude-interrupt",
1110− text: "Start long turn",
1111− provider: "claudeCode",
1112− });
1113−
1114− const thread = yield* harness.waitForThread(
1115− THREAD_ID,
1116− (entry) => entry.session?.threadId === "thread-1",
1117− );
1118− assert.equal(thread.session?.threadId, "thread-1");
1119−
1120− yield* harness.engine.dispatch({
1121− type: "thread.turn.interrupt",
1122− commandId: CommandId.makeUnsafe("cmd-turn-interrupt-claude"),
1123− threadId: THREAD_ID,
1124− createdAt: nowIso(),
1125− });
1126− yield* harness.waitForDomainEvent((event) => event.type === "thread.turn-interrupt-requested");
1127−
1128− const interruptCalls = yield* waitForSync(
1129− () => harness.adapterHarness.getInterruptCalls(THREAD_ID),
1130− (calls) => calls.length === 1,
1131− "claude provider interrupt call",
1132− );
1133− assert.equal(interruptCalls.length, 1);
1134− }),
1135− "claudeCode",
1136− ),
1137−);
1138−
1139−it.live("reverts claudeCode turns and rolls back provider conversation state", () =>
1140− withHarness(
1141− (harness) =>
1142− Effect.gen(function* () {
1143− yield* seedProjectAndThread(harness);
1144−
1145− yield* harness.adapterHarness.queueTurnResponseForNextSession({
1146− events: [
1147− {
1148− type: "turn.started",
1149− ...runtimeBase("evt-claude-revert-1", "2026-02-24T10:14:00.000Z", "claudeCode"),
1150− threadId: THREAD_ID,
1151− turnId: FIXTURE_TURN_ID,
1152− },
1153− {
1154− type: "message.delta",
1155− ...runtimeBase("evt-claude-revert-2", "2026-02-24T10:14:00.050Z", "claudeCode"),
1156− threadId: THREAD_ID,
1157− turnId: FIXTURE_TURN_ID,
1158− delta: "README -> v2\n",
1159− },
1160− {
1161− type: "turn.completed",
1162− ...runtimeBase("evt-claude-revert-3", "2026-02-24T10:14:00.100Z", "claudeCode"),
1163− threadId: THREAD_ID,
1164− turnId: FIXTURE_TURN_ID,
1165− status: "completed",
1166− },
1167− ],
1168− mutateWorkspace: ({ cwd }) =>
1169− Effect.sync(() => {
1170− fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8");
1171− }),
1172− });
1173−
1174− yield* startTurn({
1175− harness,
1176− commandId: "cmd-turn-start-claude-revert-1",
1177− messageId: "msg-user-claude-revert-1",
1178− text: "First Claude edit",
1179− provider: "claudeCode",
1180− });
1181−
1182− yield* harness.waitForThread(
1183− THREAD_ID,
1184− (entry) => entry.latestTurn?.turnId === "turn-1" && entry.session?.threadId === "thread-1",
1185− );
1186−
1187− yield* harness.adapterHarness.queueTurnResponse(THREAD_ID, {
1188− events: [
1189− {
1190− type: "turn.started",
1191− ...runtimeBase("evt-claude-revert-4", "2026-02-24T10:14:01.000Z", "claudeCode"),
1192− threadId: THREAD_ID,
1193− turnId: FIXTURE_TURN_ID,
1194− },
1195− {
1196− type: "message.delta",
1197− ...runtimeBase("evt-claude-revert-5", "2026-02-24T10:14:01.050Z", "claudeCode"),
1198− threadId: THREAD_ID,
1199− turnId: FIXTURE_TURN_ID,
1200− delta: "README -> v3\n",
1201− },
1202− {
1203− type: "turn.completed",
1204− ...runtimeBase("evt-claude-revert-6", "2026-02-24T10:14:01.100Z", "claudeCode"),
1205− threadId: THREAD_ID,
1206− turnId: FIXTURE_TURN_ID,
1207− status: "completed",
1208− },
1209− ],
1210− mutateWorkspace: ({ cwd }) =>
1211− Effect.sync(() => {
1212− fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8");
1213− }),
1214− });
1215−
1216− yield* startTurn({
1217− harness,
1218− commandId: "cmd-turn-start-claude-revert-2",
1219− messageId: "msg-user-claude-revert-2",
1220− text: "Second Claude edit",
1221− });
1222−
1223− yield* harness.waitForThread(
1224− THREAD_ID,
1225− (entry) =>
1226− entry.latestTurn?.turnId === "turn-2" &&
1227− entry.checkpoints.length === 2 &&
1228− entry.session?.providerName === "claudeCode",
1229− );
1230−
1231− yield* harness.engine.dispatch({
1232− type: "thread.checkpoint.revert",
1233− commandId: CommandId.makeUnsafe("cmd-checkpoint-revert-claude"),
1234− threadId: THREAD_ID,
1235− turnCount: 1,
1236− createdAt: nowIso(),
1237− });
1238−
1239− const revertedThread = yield* harness.waitForThread(
1240− THREAD_ID,
1241− (entry) =>
1242− entry.checkpoints.length === 1 && entry.checkpoints[0]?.checkpointTurnCount === 1,
1243− );
1244− assert.equal(revertedThread.checkpoints[0]?.checkpointTurnCount, 1);
1245− assert.equal(
1246− gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 1)),
1247− true,
1248− );
1249− assert.equal(
1250− gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 2)),
1251− false,
1252− );
1253− assert.deepEqual(harness.adapterHarness.getRollbackCalls(THREAD_ID), [1]);
1254− }),
1255− "claudeCode",
1256− ),
1257−);
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+1−1
@@ -43,7 +43,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); · 42 unmodified lines
4343 type LegacyProviderRuntimeEvent = {
4444 readonly type: string;
4545 readonly eventId: EventId;
46− readonly provider: "codex" | "claudeCode" | "cursor";
46+ readonly provider: "codex" | "cursor";
4747 readonly createdAt: string;
4848 readonly threadId: ThreadId;
4949 readonly turnId?: string | undefined;
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+0−1
@@ -212,7 +212,6 @@ const make = Effect.gen(function* () { · 211 unmodified lines
212212 const desiredRuntimeMode = thread.runtimeMode;
213213 const currentProvider: ProviderKind | undefined =
214214 thread.session?.providerName === "codex" ||
215− thread.session?.providerName === "claudeCode" ||
216215 thread.session?.providerName === "cursor"
217216 ? thread.session.providerName
218217 : undefined;
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+1−1
@@ -41,7 +41,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); · 40 unmodified lines
4141 type LegacyProviderRuntimeEvent = {
4242 readonly type: string;
4343 readonly eventId: EventId;
44− readonly provider: "codex" | "claudeCode" | "cursor";
44+ readonly provider: "codex" | "cursor";
4545 readonly createdAt: string;
4646 readonly threadId: ThreadId;
4747 readonly turnId?: string | undefined;
apps/server/src/provider/Layers/CodexAdapter.test.ts+2−2
@@ -162,7 +162,7 @@ validationLayer("CodexAdapterLive validation", (it) => { · 161 unmodified lines
162162 const adapter = yield* CodexAdapter;
163163 const result = yield* adapter
164164 .startSession({
165− provider: "claudeCode",
165+ provider: "cursor",
166166 threadId: asThreadId("thread-1"),
167167 runtimeMode: "full-access",
168168 })
@@ -174,7 +174,7 @@ validationLayer("CodexAdapterLive validation", (it) => { · 5 unmodified lines
174174 new ProviderAdapterValidationError({
175175 provider: "codex",
176176 operation: "startSession",
177− issue: "Expected provider 'codex' but received 'claudeCode'.",
177+ issue: "Expected provider 'codex' but received 'cursor'.",
178178 }),
179179 );
180180 assert.equal(validationManager.startSessionImpl.mock.calls.length, 0);
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts+1−1
@@ -1,7 +1,7 @@
11 /**
22 * ProviderAdapterRegistryLive - In-memory provider adapter lookup layer.
33 *
4− * Binds provider kinds (codex/claudeCode/...) to concrete adapter services.
4+ * Binds provider kinds (codex/cursor/...) to concrete adapter services.
55 * This layer only performs adapter lookup; it does not route session-scoped
66 * calls or own provider lifecycle workflows.
77 *
apps/server/src/provider/Layers/ProviderService.test.ts+1−1
@@ -51,7 +51,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); · 50 unmodified lines
5151 type LegacyProviderRuntimeEvent = {
5252 readonly type: string;
5353 readonly eventId: EventId;
54− readonly provider: "codex" | "claudeCode" | "cursor";
54+ readonly provider: "codex" | "cursor";
5555 readonly createdAt: string;
5656 readonly threadId: ThreadId;
5757 readonly turnId?: string | undefined;
apps/server/src/provider/Layers/ProviderSessionDirectory.ts+1−1
@@ -25,7 +25,7 @@ function decodeProviderKind( · 24 unmodified lines
2525 providerName: string,
2626 operation: string,
2727 ): Effect.Effect<ProviderKind, ProviderSessionDirectoryPersistenceError> {
28− if (providerName === "codex" || providerName === "claudeCode" || providerName === "cursor") {
28+ if (providerName === "codex" || providerName === "cursor") {
2929 return Effect.succeed(providerName);
3030 }
3131 return Effect.fail(
apps/web/src/appSettings.test.ts+3−9
@@ -16,11 +16,7 @@ describe("normalizeCustomModelSlugs", () => { · 15 unmodified lines
1616 ).toEqual(["custom/internal-model"]);
1717 });
1818
19− it("normalizes provider-specific aliases for claude and cursor", () => {
20− expect(normalizeCustomModelSlugs(["sonnet"], "claudeCode")).toEqual([]);
21− expect(normalizeCustomModelSlugs(["claude/custom-sonnet"], "claudeCode")).toEqual([
22− "claude/custom-sonnet",
23− ]);
19+ it("normalizes provider-specific aliases for cursor", () => {
2420 expect(normalizeCustomModelSlugs(["composer"], "cursor")).toEqual([]);
2521 expect(normalizeCustomModelSlugs(["cursor/custom-model"], "cursor")).toEqual([
2622 "cursor/custom-model",
@@ -53,9 +49,9 @@ describe("getAppModelOptions", () => { · 26 unmodified lines
5349 });
5450
5551 it("keeps a saved custom provider model available as an exact slug option", () => {
56− const options = getAppModelOptions("claudeCode", ["claude/custom-opus"], "claude/custom-opus");
52+ const options = getAppModelOptions("cursor", ["cursor/custom-model"], "cursor/custom-model");
5753
58− expect(options.some((option) => option.slug === "claude/custom-opus" && option.isCustom)).toBe(
54+ expect(options.some((option) => option.slug === "cursor/custom-model" && option.isCustom)).toBe(
5955 true,
6056 );
6157 });
@@ -85,10 +81,8 @@ describe("getSlashModelOptions", () => { · 23 unmodified lines
8581 });
8682
8783 it("includes provider-specific custom slugs in non-codex model lists", () => {
88− const claudeOptions = getAppModelOptions("claudeCode", ["claude/custom-opus"]);
8984 const cursorOptions = getAppModelOptions("cursor", ["cursor/custom-model"]);
9085
91− expect(claudeOptions.some((option) => option.slug === "claude/custom-opus")).toBe(true);
9286 expect(cursorOptions.some((option) => option.slug === "cursor/custom-model")).toBe(true);
9387 });
9488 });
apps/web/src/appSettings.ts+0−5
@@ -8,7 +8,6 @@ const MAX_CUSTOM_MODEL_COUNT = 32; · 7 unmodified lines
88 export const MAX_CUSTOM_MODEL_LENGTH = 256;
99 const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record<ProviderKind, ReadonlySet<string>> = {
1010 codex: new Set(getModelOptions("codex").map((option) => option.slug)),
11− claudeCode: new Set(getModelOptions("claudeCode").map((option) => option.slug)),
1211 cursor: new Set(getModelOptions("cursor").map((option) => option.slug)),
1312 };
1413
@@ -26,9 +25,6 @@ const AppSettingsSchema = Schema.Struct({ · 11 unmodified lines
2625 customCodexModels: Schema.Array(Schema.String).pipe(
2726 Schema.withConstructorDefault(() => Option.some([])),
2827 ),
29− customClaudeModels: Schema.Array(Schema.String).pipe(
30− Schema.withConstructorDefault(() => Option.some([])),
31− ),
3228 customCursorModels: Schema.Array(Schema.String).pipe(
3329 Schema.withConstructorDefault(() => Option.some([])),
3430 ),
@@ -79,7 +75,6 @@ function normalizeAppSettings(settings: AppSettings): AppSettings { · 44 unmodified lines
7975 return {
8076 ...settings,
8177 customCodexModels: normalizeCustomModelSlugs(settings.customCodexModels, "codex"),
82− customClaudeModels: normalizeCustomModelSlugs(settings.customClaudeModels, "claudeCode"),
8378 customCursorModels: normalizeCustomModelSlugs(settings.customCursorModels, "cursor"),
8479 };
8580 }
apps/web/src/components/ChatView.tsx+16−11
@@ -77,6 +77,7 @@ import { · 76 unmodified lines
7777 findLatestProposedPlan,
7878 type PendingApproval,
7979 type PendingUserInput,
80+ type ProviderPickerKind,
8081 PROVIDER_OPTIONS,
8182 deriveWorkLogEntries,
8283 hasToolActivityForTurn,
@@ -847,9 +848,8 @@ export default function ChatView({ threadId }: ChatViewProps) { · 764 unmodified lines
847848 }, [modelOptionsByProvider, selectedModelForPicker, selectedProvider]);
848849 const searchableModelOptions = useMemo(
849850 () =>
850− PROVIDER_OPTIONS.filter(
851− (option) =>
852− option.available && (lockedProvider === null || option.value === lockedProvider),
851+ AVAILABLE_PROVIDER_OPTIONS.filter(
852+ (option) => lockedProvider === null || option.value === lockedProvider,
853853 ).flatMap((option) =>
854854 modelOptionsByProvider[option.value].map(({ slug, name }) => ({
855855 provider: option.value,
@@ -5278,7 +5278,17 @@ const MessagesTimeline = memo(function MessagesTimeline({ · 4422 unmodified lines
52785278 );
52795279 });
52805280
5281−const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => option.available);
5281+function isAvailableProviderOption(
5282+ option: (typeof PROVIDER_OPTIONS)[number],
5283+): option is {
5284+ value: ProviderKind;
5285+ label: string;
5286+ available: true;
5287+} {
5288+ return option.available && option.value !== "claudeCode";
5289+}
5290+
5291+const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter(isAvailableProviderOption);
52825292 const UNAVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => !option.available);
52835293 const COMING_SOON_PROVIDER_OPTIONS = [
52845294 { id: "opencode", label: "OpenCode", icon: OpenCodeIcon },
@@ -5287,13 +5297,11 @@ const COMING_SOON_PROVIDER_OPTIONS = [ · 2 unmodified lines
52875297
52885298 function getCustomModelOptionsByProvider(settings: {
52895299 customCodexModels: readonly string[];
5290− customClaudeModels: readonly string[];
52915300 customCursorModels: readonly string[];
52925301 }): Record<ProviderKind, ReadonlyArray<{ slug: string; name: string }>> {
52935302 const cursorFamilyOptions = getCursorModelFamilyOptions();
52945303 return {
52955304 codex: getAppModelOptions("codex", settings.customCodexModels),
5296− claudeCode: getAppModelOptions("claudeCode", settings.customClaudeModels),
52975305 cursor: [
52985306 ...cursorFamilyOptions,
52995307 ...getAppModelOptions("cursor", settings.customCursorModels).filter(
@@ -5304,7 +5312,7 @@ function getCustomModelOptionsByProvider(settings: { · 4 unmodified lines
53045312 };
53055313 }
53065314
5307−const PROVIDER_ICON_BY_PROVIDER: Record<ProviderKind, Icon> = {
5315+const PROVIDER_ICON_BY_PROVIDER: Record<ProviderPickerKind, Icon> = {
53085316 codex: OpenAI,
53095317 claudeCode: ClaudeAI,
53105318 cursor: CursorIcon,
@@ -5385,10 +5393,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { · 74 unmodified lines
53855393 <span className="flex min-w-0 items-center gap-2">
53865394 <ProviderIcon
53875395 aria-hidden="true"
5388− className={cn(
5389− "size-4 shrink-0",
5390− props.provider === "claudeCode" ? "" : "text-muted-foreground/70",
5391− )}
5396+ className="size-4 shrink-0 text-muted-foreground/70"
53925397 />
53935398 <span className="truncate">{selectedModelLabel}</span>
53945399 <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" />
apps/web/src/composerDraftStore.ts+1−1
@@ -208,7 +208,7 @@ function shouldRemoveDraft(draft: ComposerThreadDraftState): boolean { · 207 unmodified lines
208208 }
209209
210210 function normalizeProviderKind(value: unknown): ProviderKind | null {
211− return value === "codex" || value === "claudeCode" || value === "cursor" ? value : null;
211+ return value === "codex" || value === "cursor" ? value : null;
212212 }
213213
214214 function revokeObjectPreviewUrl(previewUrl: string): void {
apps/web/src/routes/_chat.settings.tsx+0−7
@@ -54,8 +54,6 @@ function getCustomModelsForProvider( · 53 unmodified lines
5454 provider: ProviderKind,
5555 ) {
5656 switch (provider) {
57− case "claudeCode":
58− return settings.customClaudeModels;
5957 case "cursor":
6058 return settings.customCursorModels;
6159 case "codex":
@@ -69,8 +67,6 @@ function getDefaultCustomModelsForProvider( · 7 unmodified lines
6967 provider: ProviderKind,
7068 ) {
7169 switch (provider) {
72− case "claudeCode":
73− return defaults.customClaudeModels;
7470 case "cursor":
7571 return defaults.customCursorModels;
7672 case "codex":
@@ -81,8 +77,6 @@ function getDefaultCustomModelsForProvider( · 4 unmodified lines
8177
8278 function patchCustomModels(provider: ProviderKind, models: string[]) {
8379 switch (provider) {
84− case "claudeCode":
85− return { customClaudeModels: models };
8680 case "cursor":
8781 return { customCursorModels: models };
8882 case "codex":
@@ -101,7 +95,6 @@ function SettingsRouteView() { · 12 unmodified lines
10195 Record<ProviderKind, string>
10296 >({
10397 codex: "",
104− claudeCode: "",
10598 cursor: "",
10699 });
107100 const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState<
apps/web/src/session-logic.ts+3−1
@@ -10,8 +10,10 @@ import { · 9 unmodified lines
1010
1111 import type { ChatMessage, ProposedPlan, SessionPhase, ThreadSession, TurnDiffSummary } from "./types";
1212
13+export type ProviderPickerKind = ProviderKind | "claudeCode";
14+
1315 export const PROVIDER_OPTIONS: Array<{
14− value: ProviderKind;
16+ value: ProviderPickerKind;
1517 label: string;
1618 available: boolean;
1719 }> = [
apps/web/src/store.test.ts+2−24
@@ -129,7 +129,7 @@ describe("store pure functions", () => { · 128 unmodified lines
129129 });
130130
131131 describe("store read model sync", () => {
132− it("preserves claude model slugs without an active session", () => {
132+ it("falls back to the codex default for unsupported provider models without an active session", () => {
133133 const initialState = makeState(makeThread());
134134 const readModel = makeReadModel(
135135 makeReadModelThread({
@@ -139,29 +139,7 @@ describe("store read model sync", () => { · 3 unmodified lines
139139
140140 const next = syncServerReadModel(initialState, readModel);
141141
142− expect(next.threads[0]?.model).toBe("claude-opus-4-6");
143− });
144−
145− it("resolves claude aliases when session provider is claudeCode", () => {
146− const initialState = makeState(makeThread());
147− const readModel = makeReadModel(
148− makeReadModelThread({
149− model: "sonnet",
150− session: {
151− threadId: ThreadId.makeUnsafe("thread-1"),
152− status: "ready",
153− providerName: "claudeCode",
154− runtimeMode: "approval-required",
155− activeTurnId: null,
156− lastError: null,
157− updatedAt: "2026-02-27T00:00:00.000Z",
158− },
159− }),
160− );
161−
162− const next = syncServerReadModel(initialState, readModel);
163−
164− expect(next.threads[0]?.model).toBe("claude-sonnet-4-6");
142+ expect(next.threads[0]?.model).toBe("gpt-5.3-codex");
165143 });
166144
167145 it("resolves cursor aliases when session provider is cursor", () => {
apps/web/src/store.ts+3−11
@@ -145,19 +145,16 @@ function toLegacySessionStatus( · 144 unmodified lines
145145 }
146146
147147 function toLegacyProvider(providerName: string | null): ProviderKind {
148− if (providerName === "codex" || providerName === "claudeCode" || providerName === "cursor") {
148+ if (providerName === "codex" || providerName === "cursor") {
149149 return providerName;
150150 }
151151 return "codex";
152152 }
153153
154154 const CODEX_MODEL_SLUGS = new Set<string>(getModelOptions("codex").map((option) => option.slug));
155−const CLAUDE_MODEL_SLUGS = new Set<string>(getModelOptions("claudeCode").map((option) => option.slug));
156155 const CURSOR_MODEL_SLUGS = new Set<string>(getModelOptions("cursor").map((option) => option.slug));
157156 const CURSOR_DISTINCT_MODEL_SLUGS = new Set(
158− [...CURSOR_MODEL_SLUGS].filter(
159− (slug) => !CODEX_MODEL_SLUGS.has(slug) && !CLAUDE_MODEL_SLUGS.has(slug),
160− ),
157+ [...CURSOR_MODEL_SLUGS].filter((slug) => !CODEX_MODEL_SLUGS.has(slug)),
161158 );
162159
163160 function inferProviderForThreadModel(input: {
@@ -166,7 +163,6 @@ function inferProviderForThreadModel(input: { · 2 unmodified lines
166163 }): ProviderKind {
167164 if (
168165 input.sessionProviderName === "codex" ||
169− input.sessionProviderName === "claudeCode" ||
170166 input.sessionProviderName === "cursor"
171167 ) {
172168 return input.sessionProviderName;
@@ -175,10 +171,6 @@ function inferProviderForThreadModel(input: { · 2 unmodified lines
175171 if (normalizedCursor && CURSOR_DISTINCT_MODEL_SLUGS.has(normalizedCursor)) {
176172 return "cursor";
177173 }
178− const normalizedClaude = normalizeModelSlug(input.model, "claudeCode");
179− if (normalizedClaude && CLAUDE_MODEL_SLUGS.has(normalizedClaude)) {
180− return "claudeCode";
181− }
182174 const normalizedCodex = normalizeModelSlug(input.model, "codex");
183175 if (normalizedCodex && CODEX_MODEL_SLUGS.has(normalizedCodex)) {
184176 return "codex";
@@ -190,7 +182,7 @@ function inferProviderForThreadModel(input: { · 5 unmodified lines
190182 ) {
191183 return "cursor";
192184 }
193− return input.model.trim().startsWith("claude-") ? "claudeCode" : "codex";
185+ return "codex";
194186 }
195187
196188 function resolveWsHttpOrigin(): string {
packages/contracts/src/orchestration.test.ts+2−2
@@ -128,11 +128,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( · 127 unmodified lines
128128 text: "hello",
129129 attachments: [],
130130 },
131− provider: "claudeCode",
131+ provider: "cursor",
132132 runtimeMode: "full-access",
133133 createdAt: "2026-01-01T00:00:00.000Z",
134134 });
135− assert.strictEqual(parsed.provider, "claudeCode");
135+ assert.strictEqual(parsed.provider, "cursor");
136136 assert.strictEqual(parsed.runtimeMode, "full-access");
137137 assert.strictEqual(parsed.interactionMode, DEFAULT_PROVIDER_INTERACTION_MODE);
138138 }),
packages/contracts/src/orchestration.ts+1−1
@@ -27,7 +27,7 @@ export const ORCHESTRATION_WS_CHANNELS = { · 26 unmodified lines
2727 domainEvent: "orchestration.domainEvent",
2828 } as const;
2929
30−export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]);
30+export const ProviderKind = Schema.Literals(["codex", "cursor"]);
3131 export type ProviderKind = typeof ProviderKind.Type;
3232 export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";
3333 export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]);
packages/contracts/src/providerRuntime.test.ts+3−3
@@ -10,7 +10,7 @@ describe("ProviderRuntimeEvent", () => { · 9 unmodified lines
1010 const parsed = decodeRuntimeEvent({
1111 type: "turn.plan.updated",
1212 eventId: "event-1",
13− provider: "claudeCode",
13+ provider: "codex",
1414 sessionId: "runtime-session-1",
1515 createdAt: "2026-02-28T00:00:00.000Z",
1616 threadId: "thread-1",
@@ -56,7 +56,7 @@ describe("ProviderRuntimeEvent", () => { · 39 unmodified lines
5656 const parsed = decodeRuntimeEvent({
5757 type: "user-input.requested",
5858 eventId: "event-2",
59− provider: "claudeCode",
59+ provider: "codex",
6060 sessionId: "runtime-session-2",
6161 createdAt: "2026-02-28T00:00:01.000Z",
6262 threadId: "thread-2",
@@ -94,7 +94,7 @@ describe("ProviderRuntimeEvent", () => { · 31 unmodified lines
9494 const parsed = decodeRuntimeEvent({
9595 type: "user-input.resolved",
9696 eventId: "event-3",
97− provider: "claudeCode",
97+ provider: "codex",
9898 sessionId: "runtime-session-2",
9999 createdAt: "2026-02-28T00:00:02.000Z",
100100 threadId: "thread-2",
packages/contracts/src/providerRuntime.ts+0−2
@@ -19,8 +19,6 @@ const RuntimeEventRawSource = Schema.Literals([ · 18 unmodified lines
1919 "codex.app-server.notification",
2020 "codex.app-server.request",
2121 "codex.eventmsg",
22− "claude.sdk.message",
23− "claude.sdk.permission",
2422 "codex.sdk.thread-event",
2523 "cursor.acp.notification",
2624 "cursor.acp.request",
packages/shared/src/model.test.ts+0−1
@@ -84,7 +84,6 @@ describe("resolveModelSlug", () => { · 83 unmodified lines
8484 it("keeps codex defaults for backward compatibility", () => {
8585 expect(getDefaultModel()).toBe(DEFAULT_MODEL);
8686 expect(getModelOptions()).toEqual(MODEL_OPTIONS);
87− expect(getModelOptions("claudeCode")).toEqual([]);
8887 expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor);
8988 expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS);
9089 });

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t