Skip to content

Commit 2aae280

Browse files
committed
Address Copilot PR review feedback
1 parent fbaaf56 commit 2aae280

File tree

5 files changed

+71
-65
lines changed

5 files changed

+71
-65
lines changed

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

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -732,12 +732,71 @@ const makeCopilotAdapter = (options?: CopilotAdapterLiveOptions) =>
732732
toProcessError(threadId, "Failed to start GitHub Copilot CLI client.", cause),
733733
});
734734

735+
const pendingApprovals = new Map<ApprovalRequestId, PendingApproval>();
736+
let context: CopilotSessionContext | undefined;
737+
const onPermissionRequest = (request: CopilotPermissionRequest) => {
738+
if (input.runtimeMode === "full-access") {
739+
return { kind: "approved" } satisfies CopilotPermissionRequestResult;
740+
}
741+
const requestId = ApprovalRequestId.makeUnsafe(
742+
`copilot-permission:${crypto.randomUUID()}`,
743+
);
744+
let resolve!: (decision: ProviderApprovalDecision) => void;
745+
const promise = new Promise<ProviderApprovalDecision>((resolvePromise) => {
746+
resolve = resolvePromise;
747+
});
748+
const detail = permissionDetail(request);
749+
pendingApprovals.set(requestId, {
750+
requestType: inferRequestType(request),
751+
...(detail ? { detail } : {}),
752+
promise,
753+
resolve,
754+
});
755+
void Effect.runPromise(
756+
emitEvent({
757+
...makeBase(
758+
threadId,
759+
context?.turnState
760+
? { turnId: context.turnState.turnId, requestId }
761+
: { requestId },
762+
),
763+
type: "request.opened",
764+
payload: {
765+
requestType: inferRequestType(request),
766+
...(detail ? { detail } : {}),
767+
args: request,
768+
},
769+
}),
770+
);
771+
return promise.then((decision) => {
772+
const result = mapApprovalDecisionToPermissionResult(decision);
773+
void Effect.runPromise(
774+
emitEvent({
775+
...makeBase(
776+
threadId,
777+
context?.turnState
778+
? { turnId: context.turnState.turnId, requestId }
779+
: { requestId },
780+
),
781+
type: "request.resolved",
782+
payload: {
783+
requestType: inferRequestType(request),
784+
decision: result.kind,
785+
resolution: result,
786+
},
787+
}).pipe(Effect.ensuring(Effect.sync(() => pendingApprovals.delete(requestId)))),
788+
);
789+
return result;
790+
});
791+
};
792+
735793
const reasonEffort = input.modelOptions?.copilot?.reasoningEffort;
736794
const sessionConfig = {
737795
...(input.model ? { model: input.model } : {}),
738796
...(reasonEffort ? { reasoningEffort: reasonEffort } : {}),
739797
workingDirectory: resolvedCwd,
740798
streaming: true,
799+
onPermissionRequest,
741800
...(providerOptions.configDir ? { configDir: providerOptions.configDir } : {}),
742801
};
743802

@@ -747,11 +806,9 @@ const makeCopilotAdapter = (options?: CopilotAdapterLiveOptions) =>
747806
resumeCursor
748807
? client.resumeSession(resumeCursor.sessionId, {
749808
...sessionConfig,
750-
onPermissionRequest: () => ({ kind: "approved" }),
751809
})
752810
: client.createSession({
753811
...sessionConfig,
754-
onPermissionRequest: () => ({ kind: "approved" }),
755812
}),
756813
catch: (cause) =>
757814
toProcessError(threadId, "Failed to create GitHub Copilot session.", cause),
@@ -778,11 +835,11 @@ const makeCopilotAdapter = (options?: CopilotAdapterLiveOptions) =>
778835
resumeCursor: makeResumeCursor(copilotSession.sessionId),
779836
};
780837

781-
const context: CopilotSessionContext = {
838+
context = {
782839
session,
783840
client,
784841
copilotSession,
785-
pendingApprovals: new Map(),
842+
pendingApprovals,
786843
pendingUserInputs: new Map(),
787844
turns: [],
788845
turnState: undefined,
@@ -794,62 +851,6 @@ const makeCopilotAdapter = (options?: CopilotAdapterLiveOptions) =>
794851
void Effect.runPromise(handleSessionEvent(context, event));
795852
});
796853

797-
copilotSession.registerPermissionHandler((request) => {
798-
if (context.session.runtimeMode === "full-access") {
799-
return { kind: "approved" };
800-
}
801-
const requestId = ApprovalRequestId.makeUnsafe(
802-
`copilot-permission:${crypto.randomUUID()}`,
803-
);
804-
let resolve!: (decision: ProviderApprovalDecision) => void;
805-
const promise = new Promise<ProviderApprovalDecision>((resolvePromise) => {
806-
resolve = resolvePromise;
807-
});
808-
const detail = permissionDetail(request);
809-
context.pendingApprovals.set(requestId, {
810-
requestType: inferRequestType(request),
811-
...(detail ? { detail } : {}),
812-
promise,
813-
resolve,
814-
});
815-
void Effect.runPromise(
816-
emitEvent({
817-
...makeBase(
818-
threadId,
819-
context.turnState ? { turnId: context.turnState.turnId, requestId } : { requestId },
820-
),
821-
type: "request.opened",
822-
payload: {
823-
requestType: inferRequestType(request),
824-
...(permissionDetail(request) ? { detail: permissionDetail(request) } : {}),
825-
args: request,
826-
},
827-
}),
828-
);
829-
return promise.then((decision) => {
830-
const result = mapApprovalDecisionToPermissionResult(decision);
831-
void Effect.runPromise(
832-
emitEvent({
833-
...makeBase(
834-
threadId,
835-
context.turnState
836-
? { turnId: context.turnState.turnId, requestId }
837-
: { requestId },
838-
),
839-
type: "request.resolved",
840-
payload: {
841-
requestType: inferRequestType(request),
842-
decision: result.kind,
843-
resolution: result,
844-
},
845-
}).pipe(
846-
Effect.ensuring(Effect.sync(() => context.pendingApprovals.delete(requestId))),
847-
),
848-
);
849-
return result;
850-
});
851-
});
852-
853854
copilotSession.registerUserInputHandler((request) => {
854855
const requestId = ApprovalRequestId.makeUnsafe(
855856
`copilot-user-input:${crypto.randomUUID()}`,

apps/web/src/components/chat/ProviderModelPicker.browser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const MODEL_OPTIONS_BY_PROVIDER = {
1515
{ slug: "gpt-5-codex", name: "GPT-5 Codex" },
1616
{ slug: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
1717
],
18-
copilot: [{ slug: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" }],
18+
copilot: [{ slug: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" }],
1919
openclaw: [],
2020
} as const satisfies Record<ProviderKind, ReadonlyArray<{ slug: ModelSlug; name: string }>>;
2121

apps/web/src/routes/_chat.settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1944,7 +1944,7 @@ function SettingsRouteView() {
19441944

19451945
<SettingsRow
19461946
title="Custom models"
1947-
description="Add custom model slugs for Codex or Anthropic. The chat picker groups models by provider."
1947+
description="Add custom model slugs for Codex, Anthropic, GitHub Copilot, or OpenClaw. The chat picker groups models by provider."
19481948
resetAction={
19491949
totalCustomModels > 0 ? (
19501950
<SettingResetButton

packages/contracts/src/model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER: Record<ProviderKind, Record<string,
139139
"gpt-5.4": "gpt-5.4",
140140
"5.4-mini": "gpt-5.4-mini",
141141
"gpt-5.4-mini": "gpt-5.4-mini",
142-
"claude-sonnet-4.5": "claude-sonnet-4.5",
143-
"claude sonnet 4.5": "claude-sonnet-4.5",
142+
"claude-sonnet-4.5": "claude-sonnet-4-5",
143+
"claude sonnet 4.5": "claude-sonnet-4-5",
144144
"claude-sonnet-4-5": "claude-sonnet-4-5",
145145
"claude sonnet 4.6": "claude-sonnet-4-6",
146146
"claude-sonnet-4.6": "claude-sonnet-4-6",

packages/shared/src/model.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const MODEL_SLUG_SET_BY_PROVIDER: Record<ProviderKind, ReadonlySet<ModelSlug>> =
2424
claudeAgent: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeAgent.map((option) => option.slug)),
2525
codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)),
2626
openclaw: new Set<ModelSlug>(),
27-
copilot: new Set<ModelSlug>(),
27+
copilot: new Set(MODEL_OPTIONS_BY_PROVIDER.copilot.map((option) => option.slug)),
2828
};
2929

3030
const CLAUDE_OPUS_4_6_MODEL = "claude-opus-4-6";
@@ -166,6 +166,11 @@ export function inferProviderForModel(
166166
return "codex";
167167
}
168168

169+
const normalizedCopilot = normalizeModelSlug(model, "copilot");
170+
if (normalizedCopilot && MODEL_SLUG_SET_BY_PROVIDER.copilot.has(normalizedCopilot)) {
171+
return "copilot";
172+
}
173+
169174
if (typeof model === "string") {
170175
const trimmed = model.trim();
171176
if (trimmed.startsWith("claude-")) return "claudeAgent";

0 commit comments

Comments
 (0)