Skip to content

Commit 97b11b2

Browse files
committed
feat: ensure that compaction message is tracked as agent initiated, consistent with github copilot extension and opencode
1 parent 090c617 commit 97b11b2

7 files changed

Lines changed: 35 additions & 14 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ The following command line options are available for the `start` command:
198198
"gpt-5-mini": "low"
199199
},
200200
"useFunctionApplyPatch": true,
201-
"compactUseSmallModel": true,
202201
"useMessagesApi": true
203202
}
204203
```
@@ -207,7 +206,6 @@ The following command line options are available for the `start` command:
207206
- **smallModel:** Fallback model used for tool-less warmup messages (e.g., Claude Code probe requests) to avoid spending premium requests; defaults to `gpt-5-mini`.
208207
- **modelReasoningEfforts:** Per-model `reasoning.effort` sent to the Copilot Responses API. Allowed values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. If a model isn’t listed, `high` is used by default.
209208
- **useFunctionApplyPatch:** When `true`, the server will convert any custom tool named `apply_patch` in Responses payloads into an OpenAI-style function tool (`type: "function"`) with a parameter schema so assistants can call it using function-calling semantics to edit files. Set to `false` to leave tools unchanged. Defaults to `true`.
210-
- **compactUseSmallModel:** When `true`, detected "compact" requests (e.g., from Claude Code or Opencode compact mode) will automatically use the configured `smallModel` to avoid consuming premium model usage for short/background tasks. Defaults to `true`.
211209
- **useMessagesApi:** When `true`, Claude-family models that support Copilot's native `/v1/messages` endpoint will use the Messages API; otherwise they fall back to `/chat/completions`. Set to `false` to disable Messages API routing and always use `/chat/completions`. Defaults to `true`.
212210

213211
Edit this file to customize prompts or swap in your own fast model. Restart the server (or rerun the command) after changes so the cached config is refreshed.

src/lib/api-config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ export const getOauthAppConfig = (): OauthAppConfig => {
7575
}
7676
}
7777

78+
export const prepareForCompact = (
79+
headers: Record<string, string>,
80+
isCompact?: boolean,
81+
) => {
82+
if (isCompact) {
83+
headers["x-initiator"] = "agent"
84+
}
85+
}
86+
7887
export const prepareInteractionHeaders = (
7988
sessionId: string | undefined,
8089
isSubagent: boolean,

src/lib/config.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ const defaultConfig: AppConfig = {
6262
"gpt-5.3-codex": "xhigh",
6363
},
6464
useFunctionApplyPatch: true,
65-
compactUseSmallModel: true,
6665
useMessagesApi: true,
6766
}
6867

@@ -203,11 +202,6 @@ export function getReasoningEffortForModel(
203202
return config.modelReasoningEfforts?.[model] ?? "high"
204203
}
205204

206-
export function shouldCompactUseSmallModel(): boolean {
207-
const config = getConfig()
208-
return config.compactUseSmallModel ?? true
209-
}
210-
211205
export function isMessagesApiEnabled(): boolean {
212206
const config = getConfig()
213207
return config.useMessagesApi ?? true

src/routes/messages/handler.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type { Model } from "~/services/copilot/get-models"
77
import { awaitApproval } from "~/lib/approval"
88
import {
99
getSmallModel,
10-
shouldCompactUseSmallModel,
1110
getReasoningEffortForModel,
1211
isMessagesApiEnabled,
1312
} from "~/lib/config"
@@ -90,9 +89,6 @@ export async function handleCompletion(c: Context) {
9089

9190
if (isCompact) {
9291
logger.debug("Is compact request:", isCompact)
93-
if (shouldCompactUseSmallModel()) {
94-
anthropicPayload.model = getSmallModel()
95-
}
9692
} else {
9793
// Merge tool_result and text blocks into tool_result to avoid consuming premium requests
9894
// (caused by skill invocations, edit hooks, plan or to do reminders)
@@ -119,6 +115,7 @@ export async function handleCompletion(c: Context) {
119115
selectedModel,
120116
requestId,
121117
sessionId,
118+
isCompact,
122119
})
123120
}
124121

@@ -128,13 +125,15 @@ export async function handleCompletion(c: Context) {
128125
selectedModel,
129126
requestId,
130127
sessionId,
128+
isCompact,
131129
})
132130
}
133131

134132
return await handleWithChatCompletions(c, anthropicPayload, {
135133
subagentMarker,
136134
requestId,
137135
sessionId,
136+
isCompact,
138137
})
139138
}
140139

@@ -148,9 +147,10 @@ const handleWithChatCompletions = async (
148147
subagentMarker?: SubagentMarker | null
149148
requestId: string
150149
sessionId?: string
150+
isCompact?: boolean
151151
},
152152
) => {
153-
const { subagentMarker, requestId, sessionId } = options
153+
const { subagentMarker, requestId, sessionId, isCompact } = options
154154
const openAIPayload = translateToOpenAI(anthropicPayload)
155155
logger.debug(
156156
"Translated OpenAI request payload:",
@@ -161,6 +161,7 @@ const handleWithChatCompletions = async (
161161
subagentMarker,
162162
requestId,
163163
sessionId,
164+
isCompact,
164165
})
165166

166167
if (isNonStreaming(response)) {
@@ -217,9 +218,11 @@ const handleWithResponsesApi = async (
217218
selectedModel?: Model
218219
requestId: string
219220
sessionId?: string
221+
isCompact?: boolean
220222
},
221223
) => {
222-
const { subagentMarker, selectedModel, requestId, sessionId } = options
224+
const { subagentMarker, selectedModel, requestId, sessionId, isCompact } =
225+
options
223226

224227
const responsesPayload =
225228
translateAnthropicMessagesToResponsesPayload(anthropicPayload)
@@ -243,6 +246,7 @@ const handleWithResponsesApi = async (
243246
subagentMarker,
244247
requestId,
245248
sessionId,
249+
isCompact,
246250
})
247251

248252
if (responsesPayload.stream && isAsyncIterable(response)) {
@@ -321,6 +325,7 @@ const handleWithMessagesApi = async (
321325
selectedModel?: Model
322326
requestId: string
323327
sessionId?: string
328+
isCompact?: boolean
324329
},
325330
) => {
326331
const {
@@ -329,6 +334,7 @@ const handleWithMessagesApi = async (
329334
selectedModel,
330335
requestId,
331336
sessionId,
337+
isCompact,
332338
} = options
333339
// Pre-request processing: filter thinking blocks for Claude models so only
334340
// valid thinking blocks are sent to the Copilot Messages API.
@@ -366,6 +372,7 @@ const handleWithMessagesApi = async (
366372
subagentMarker,
367373
requestId,
368374
sessionId,
375+
isCompact,
369376
})
370377

371378
if (isAsyncIterable(response)) {

src/services/copilot/create-chat-completions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { SubagentMarker } from "~/routes/messages/subagent-marker"
66
import {
77
copilotBaseUrl,
88
copilotHeaders,
9+
prepareForCompact,
910
prepareInteractionHeaders,
1011
} from "~/lib/api-config"
1112
import { HTTPError } from "~/lib/error"
@@ -17,6 +18,7 @@ export const createChatCompletions = async (
1718
subagentMarker?: SubagentMarker | null
1819
requestId: string
1920
sessionId?: string
21+
isCompact?: boolean
2022
},
2123
) => {
2224
if (!state.copilotToken) throw new Error("Copilot token not found")
@@ -50,6 +52,8 @@ export const createChatCompletions = async (
5052
headers,
5153
)
5254

55+
prepareForCompact(headers, options.isCompact)
56+
5357
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
5458
method: "POST",
5559
headers,

src/services/copilot/create-messages.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { SubagentMarker } from "~/routes/messages/subagent-marker"
1010
import {
1111
copilotBaseUrl,
1212
copilotHeaders,
13+
prepareForCompact,
1314
prepareInteractionHeaders,
1415
} from "~/lib/api-config"
1516
import { HTTPError } from "~/lib/error"
@@ -64,6 +65,7 @@ export const createMessages = async (
6465
subagentMarker?: SubagentMarker | null
6566
requestId: string
6667
sessionId?: string
68+
isCompact?: boolean
6769
},
6870
): Promise<CreateMessagesReturn> => {
6971
if (!state.copilotToken) throw new Error("Copilot token not found")
@@ -94,6 +96,8 @@ export const createMessages = async (
9496
headers,
9597
)
9698

99+
prepareForCompact(headers, options.isCompact)
100+
97101
// align with vscode copilot extension anthropic-beta
98102
const anthropicBeta = buildAnthropicBetaHeader(
99103
anthropicBetaHeader,

src/services/copilot/create-responses.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { SubagentMarker } from "~/routes/messages/subagent-marker"
66
import {
77
copilotBaseUrl,
88
copilotHeaders,
9+
prepareForCompact,
910
prepareInteractionHeaders,
1011
} from "~/lib/api-config"
1112
import { HTTPError } from "~/lib/error"
@@ -357,6 +358,7 @@ interface ResponsesRequestOptions {
357358
subagentMarker?: SubagentMarker | null
358359
requestId: string
359360
sessionId?: string
361+
isCompact?: boolean
360362
}
361363

362364
export const createResponses = async (
@@ -367,6 +369,7 @@ export const createResponses = async (
367369
subagentMarker,
368370
requestId,
369371
sessionId,
372+
isCompact,
370373
}: ResponsesRequestOptions,
371374
): Promise<CreateResponsesReturn> => {
372375
if (!state.copilotToken) throw new Error("Copilot token not found")
@@ -378,6 +381,8 @@ export const createResponses = async (
378381

379382
prepareInteractionHeaders(sessionId, Boolean(subagentMarker), headers)
380383

384+
prepareForCompact(headers, isCompact)
385+
381386
// service_tier is not supported by github copilot
382387
payload.service_tier = null
383388

0 commit comments

Comments
 (0)