( messages: Message[], summaryRequest: UserMessage, appState: Awaited<ReturnType<ToolUseContext['getAppState']>>, context: ToolUseContext, preCompactTokenCount: number, cacheSafeParams: CacheSafeParams, )
| 1210 | } |
| 1211 | |
| 1212 | async function streamCompactSummaryInner( |
| 1213 | messages: Message[], |
| 1214 | summaryRequest: UserMessage, |
| 1215 | appState: Awaited<ReturnType<ToolUseContext['getAppState']>>, |
| 1216 | context: ToolUseContext, |
| 1217 | preCompactTokenCount: number, |
| 1218 | cacheSafeParams: CacheSafeParams, |
| 1219 | ): Promise<AssistantMessage> { |
| 1220 | // When prompt cache sharing is enabled, use forked agent to reuse the |
| 1221 | // main conversation's cached prefix (system prompt, tools, model, |
| 1222 | // messages prefix, thinking config). Falls back to regular streaming path |
| 1223 | // on failure. |
| 1224 | // 3P default: true — see comment at the other ncode_compact_cache_prefix |
| 1225 | // read above. |
| 1226 | const promptCacheSharingEnabled = getFeatureValue_CACHED_MAY_BE_STALE( |
| 1227 | 'ncode_compact_cache_prefix', |
| 1228 | true, |
| 1229 | ) |
| 1230 | |
| 1231 | if (promptCacheSharingEnabled) { |
| 1232 | try { |
| 1233 | // DO NOT set maxOutputTokens here. The fork piggybacks on the main thread's |
| 1234 | // prompt cache by sending identical cache-key params (system, tools, model, |
| 1235 | // messages prefix, thinking config). Setting maxOutputTokens would clamp |
| 1236 | // budget_tokens via Math.min(budget, maxOutputTokens-1) in claude.ts, |
| 1237 | // creating a thinking config mismatch that invalidates the cache. |
| 1238 | // The streaming fallback path (below) can safely set maxOutputTokensOverride |
| 1239 | // since it doesn't share cache with the main thread. |
| 1240 | const result = await runForkedAgent({ |
| 1241 | promptMessages: [summaryRequest], |
| 1242 | cacheSafeParams, |
| 1243 | canUseTool: createCompactCanUseTool(), |
| 1244 | querySource: 'compact', |
| 1245 | forkLabel: 'compact', |
| 1246 | maxTurns: 1, |
| 1247 | skipCacheWrite: true, |
| 1248 | // Pass the compact context's abortController so user Esc aborts the |
| 1249 | // fork — same signal the streaming fallback uses at |
| 1250 | // `signal: context.abortController.signal` below. |
| 1251 | overrides: { abortController: context.abortController }, |
| 1252 | }) |
| 1253 | const assistantMsg = getLastAssistantMessage(result.messages) |
| 1254 | const assistantText = assistantMsg |
| 1255 | ? getAssistantMessageText(assistantMsg) |
| 1256 | : null |
| 1257 | // Guard isApiErrorMessage: query() catches API errors (including |
| 1258 | // APIUserAbortError on ESC) and yields them as synthetic assistant |
| 1259 | // messages. Without this check, an aborted compact "succeeds" with |
| 1260 | // "Request was aborted." as the summary — the text doesn't start with |
| 1261 | // "API Error" so the caller's startsWithApiErrorPrefix guard misses it. |
| 1262 | if (assistantMsg && assistantText && !assistantMsg.isApiErrorMessage) { |
| 1263 | // Skip success logging for PTL error text — it's returned so the |
| 1264 | // caller's retry loop catches it, but it's not a successful summary. |
| 1265 | if (!assistantText.startsWith(PROMPT_TOO_LONG_ERROR_MESSAGE)) { |
| 1266 | logEvent('ncode_compact_cache_sharing_success', { |
| 1267 | preCompactTokenCount, |
| 1268 | outputTokens: result.totalUsage.output_tokens, |
| 1269 | cacheReadInputTokens: result.totalUsage.cache_read_input_tokens, |
no test coverage detected