In the previous chapter, Chapter 2: Compaction Orchestration, we acted as a "Triage Nurse," deciding whether to use a quick cleanup or a heavy-duty specialist.
We mentioned a "Specialist" called Reactive Mode. This mode is powerful but complex. It requires specific data formats, has its own internal status codes, and needs to update the UI constantly.
If we put all that complexity directly into our main command file, it would become unreadable. Instead, we create a dedicated Integration Adapter.
Imagine you are traveling to a country with different electrical outlets. You cannot plug your hairdryer directly into the wall; it won't fit, and the voltage might blow it up. You need a Travel Adapter.
In our project, the Reactive Compaction Integration (compactViaReactive) is that adapter. It takes our standard application data, converts it into what the Reactive Engine needs, and translates the engine's "foreign" result codes back into standard errors.
The user wants a "Reactive" summary (perhaps because the conversation is extremely long).
/compact.Before we start the heavy lifting, we need to tell the user that something is happening. We also need to run any "Pre-Compact Hooks" (setup scripts that other plugins might have registered).
// Inside compactViaReactive function
context.onCompactProgress?.({
type: 'hooks_start',
hookType: 'pre_compact'
});
context.setSDKStatus?.('compacting');
Explanation:
onCompactProgress: Sends a signal to the UI to maybe show a spinner.setSDKStatus: Sets the internal state to 'compacting' so other parts of the app know we are busy.The Reactive Engine needs two things:
We load these at the same time to save time.
const [hookResult, cacheSafeParams] = await Promise.all([
// 1. Run external hooks
executePreCompactHooks(
{ trigger: 'manual', customInstructions },
context.abortController.signal
),
// 2. Build the complex system prompt (See Chapter 4)
getCacheSharingParams(context, messages),
]);
Explanation:
Promise.all: Runs two tasks simultaneously.executePreCompactHooks: Checks if other plugins want to modify the instructions.getCacheSharingParams: Packs the necessary data (we will explore this deeply in Chapter 4: Context Assembly).Now we have our data, we actually call the engine. This is the "Specialist" doing the work.
// Update UI to show we are starting the main event
context.onCompactProgress?.({ type: 'compact_start' });
// Call the Reactive Engine
const outcome = await reactive.reactiveCompactOnPromptTooLong(
messages,
cacheSafeParams,
{ customInstructions: mergedInstructions, trigger: 'manual' },
);
Explanation:
reactiveCompactOnPromptTooLong: This is the heavy logic imported from the separate module. It takes the messages and the "luggage" (params) we prepared.
The Reactive Engine doesn't throw standard errors. It returns status codes like 'too_few_groups' or 'aborted'. Our main application doesn't know what those mean. We must translate them into standard JavaScript Errors.
if (!outcome.ok) {
switch (outcome.reason) {
case 'too_few_groups':
throw new Error('Not enough messages to compact');
case 'aborted':
throw new Error('User aborted the process');
default:
throw new Error('Incomplete response from AI');
}
}
Explanation:
outcome.reason.Error() with a human-readable message that the rest of the app understands.Let's visualize how this Adapter works. It sits between the generic "Context" of the CLI and the specific "Engine".
There is one final crucial piece: Cleanup.
Whether the compaction succeeds or fails, we must turn off the "On Air" sign (the UI spinner). We use a try/finally block for this.
try {
// ... run the engine ...
} finally {
// This runs NO MATTER WHAT (Success or Error)
context.setStreamMode?.('requesting');
context.setResponseLength?.(() => 0);
context.onCompactProgress?.({ type: 'compact_end' });
context.setSDKStatus?.(null);
}
Explanation:
finally: Even if an error is thrown in the try block, the code in finally executes.context.setSDKStatus?.(null): This unlocks the UI, allowing the user to type commands again.
Earlier, we fetched hookResult (external plugins) and customInstructions (what the user typed). We need to combine them before sending them to the engine.
const mergedInstructions = mergeHookInstructions(
customInstructions,
hookResult.newCustomInstructions,
);
Why?
You have built a robust Integration Layer.
However, in Step 2, we called a function getCacheSharingParams to pack our "luggage." We glossed over that part. How do we actually bundle up the system prompt, tool definitions, and conversation context into a neat package for the AI?
That is the topic of the next chapter.
Next Chapter: Context Assembly
Generated by Code IQ