Skip to content

Commit 38b1aee

Browse files
committed
refactor: consolidate api-formats into fetch-wrapper
- Move ToolTracker to fetch-wrapper/tool-tracker.ts - Move prunable list logic to fetch-wrapper/prunable-list.ts - Move session helpers into handler.ts - Delete lib/api-formats/ directory - Update all imports
1 parent 6311141 commit 38b1aee

File tree

9 files changed

+98
-310
lines changed

9 files changed

+98
-310
lines changed

index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createPluginState } from "./lib/state"
77
import { installFetchWrapper } from "./lib/fetch-wrapper"
88
import { createPruningTool } from "./lib/pruning-tool"
99
import { createEventHandler, createChatParamsHandler } from "./lib/hooks"
10-
import { createToolTracker } from "./lib/api-formats/synth-instruction"
10+
import { createToolTracker } from "./lib/fetch-wrapper/tool-tracker"
1111
import { loadPrompt } from "./lib/core/prompt"
1212

1313
const plugin: Plugin = (async (ctx) => {

lib/api-formats/synth-instruction.ts

Lines changed: 0 additions & 184 deletions
This file was deleted.

lib/fetch-wrapper/handler.ts

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,74 @@
1-
import type { FetchHandlerContext, FetchHandlerResult, FormatDescriptor } from "./types"
2-
import {
3-
PRUNED_CONTENT_MESSAGE,
4-
getAllPrunedIds,
5-
fetchSessionMessages
6-
} from "./types"
7-
import { buildPrunableToolsList, buildEndInjection } from "../api-formats/prunable-list"
1+
import type { FetchHandlerContext, FetchHandlerResult, FormatDescriptor, PrunedIdData } from "./types"
2+
import { type PluginState, ensureSessionRestored } from "../state"
3+
import type { Logger } from "../logger"
4+
import { buildPrunableToolsList, buildEndInjection } from "./prunable-list"
5+
6+
// ============================================================================
7+
// Constants
8+
// ============================================================================
9+
10+
/** The message used to replace pruned tool output content */
11+
const PRUNED_CONTENT_MESSAGE = '[Output removed to save context - information superseded or no longer needed]'
12+
13+
// ============================================================================
14+
// Session Helpers
15+
// ============================================================================
16+
17+
/**
18+
* Get the most recent active (non-subagent) session.
19+
*/
20+
function getMostRecentActiveSession(allSessions: any): any | undefined {
21+
const activeSessions = allSessions.data?.filter((s: any) => !s.parentID) || []
22+
return activeSessions.length > 0 ? activeSessions[0] : undefined
23+
}
24+
25+
/**
26+
* Fetch session messages for logging purposes.
27+
*/
28+
async function fetchSessionMessages(
29+
client: any,
30+
sessionId: string
31+
): Promise<any[] | undefined> {
32+
try {
33+
const messagesResponse = await client.session.messages({
34+
path: { id: sessionId },
35+
query: { limit: 100 }
36+
})
37+
return Array.isArray(messagesResponse.data)
38+
? messagesResponse.data
39+
: Array.isArray(messagesResponse) ? messagesResponse : undefined
40+
} catch (e) {
41+
return undefined
42+
}
43+
}
44+
45+
/**
46+
* Get all pruned IDs from the current session.
47+
*/
48+
async function getAllPrunedIds(
49+
client: any,
50+
state: PluginState,
51+
logger?: Logger
52+
): Promise<PrunedIdData> {
53+
const allSessions = await client.session.list()
54+
const allPrunedIds = new Set<string>()
55+
56+
const currentSession = getMostRecentActiveSession(allSessions)
57+
if (currentSession) {
58+
await ensureSessionRestored(state, currentSession.id, logger)
59+
const prunedIds = state.prunedIds.get(currentSession.id) ?? []
60+
prunedIds.forEach((id: string) => allPrunedIds.add(id.toLowerCase()))
61+
62+
if (logger && prunedIds.length > 0) {
63+
logger.debug("fetch", "Loaded pruned IDs for replacement", {
64+
sessionId: currentSession.id,
65+
prunedCount: prunedIds.length
66+
})
67+
}
68+
}
69+
70+
return { allSessions, allPrunedIds }
71+
}
872

973
/**
1074
* Generic format handler that processes any API format using a FormatDescriptor.

lib/fetch-wrapper/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { PluginState } from "../state"
22
import type { Logger } from "../logger"
33
import type { FetchHandlerContext, SynthPrompts } from "./types"
4-
import type { ToolTracker } from "../api-formats/synth-instruction"
4+
import type { ToolTracker } from "./types"
55
import type { PluginConfig } from "../config"
66
import { openaiChatFormat, openaiResponsesFormat, geminiFormat, bedrockFormat } from "./formats"
77
import { handleFormat } from "./handler"
Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
* Builds and injects a single message at the end of the conversation containing:
55
* - Nudge instruction (when toolResultCount > nudge_freq)
66
* - Prunable tools list
7-
*
8-
* Note: The base synthetic instructions (signal_management, context_window_management,
9-
* context_pruning) are still appended to the last user message separately via
10-
* synth-instruction.ts - that behavior is unchanged.
117
*/
128

139
import { extractParameterKey } from '../ui/display-utils'
@@ -106,54 +102,3 @@ export function buildEndInjection(
106102

107103
return parts.join('\n\n')
108104
}
109-
110-
// ============================================================================
111-
// OpenAI Chat / Anthropic Format
112-
// ============================================================================
113-
114-
/**
115-
* Injects the prunable list (and optionally nudge) at the end of OpenAI/Anthropic messages.
116-
* Appends a new user message at the end.
117-
*/
118-
export function injectPrunableList(
119-
messages: any[],
120-
injection: string
121-
): boolean {
122-
if (!injection) return false
123-
messages.push({ role: 'user', content: injection })
124-
return true
125-
}
126-
127-
// ============================================================================
128-
// Google/Gemini Format
129-
// ============================================================================
130-
131-
/**
132-
* Injects the prunable list (and optionally nudge) at the end of Gemini contents.
133-
* Appends a new user content at the end.
134-
*/
135-
export function injectPrunableListGemini(
136-
contents: any[],
137-
injection: string
138-
): boolean {
139-
if (!injection) return false
140-
contents.push({ role: 'user', parts: [{ text: injection }] })
141-
return true
142-
}
143-
144-
// ============================================================================
145-
// OpenAI Responses API Format
146-
// ============================================================================
147-
148-
/**
149-
* Injects the prunable list (and optionally nudge) at the end of OpenAI Responses API input.
150-
* Appends a new user message at the end.
151-
*/
152-
export function injectPrunableListResponses(
153-
input: any[],
154-
injection: string
155-
): boolean {
156-
if (!injection) return false
157-
input.push({ type: 'message', role: 'user', content: injection })
158-
return true
159-
}

lib/fetch-wrapper/tool-tracker.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Tool tracker for tracking tool results and managing nudge frequency.
3+
*/
4+
5+
export interface ToolTracker {
6+
seenToolResultIds: Set<string>
7+
toolResultCount: number // Tools since last prune
8+
skipNextIdle: boolean
9+
getToolName?: (callId: string) => string | undefined
10+
}
11+
12+
export function createToolTracker(): ToolTracker {
13+
return { seenToolResultIds: new Set(), toolResultCount: 0, skipNextIdle: false }
14+
}
15+
16+
export function resetToolTrackerCount(tracker: ToolTracker): void {
17+
tracker.toolResultCount = 0
18+
}

0 commit comments

Comments
 (0)