@tduyng/pi-context 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cache-state.ts +1 -0
- package/src/cached-compaction.ts +23 -19
- package/src/cached-request.ts +2 -10
package/package.json
CHANGED
package/src/cache-state.ts
CHANGED
|
@@ -54,6 +54,7 @@ function refreshAt(entry: SessionEntry): number | null {
|
|
|
54
54
|
}
|
|
55
55
|
if (entry.type !== 'message') return null
|
|
56
56
|
if (entry.message.role !== 'assistant') return null
|
|
57
|
+
if (entry.message.stopReason === 'error' || entry.message.stopReason === 'aborted') return null
|
|
57
58
|
const usage = entry.message.usage
|
|
58
59
|
const read = usage.cacheRead ?? 0
|
|
59
60
|
const write = usage.cacheWrite ?? 0
|
package/src/cached-compaction.ts
CHANGED
|
@@ -14,14 +14,23 @@ import {
|
|
|
14
14
|
import type { Usage } from '@earendil-works/pi-ai'
|
|
15
15
|
|
|
16
16
|
import { cacheState, formatExpiry, resolveRetention } from './cache-state.js'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
assistantText,
|
|
19
|
+
forget,
|
|
20
|
+
keptStartQuote,
|
|
21
|
+
summaryInstruction,
|
|
22
|
+
walkTrailing,
|
|
23
|
+
type CapturedRequest,
|
|
24
|
+
type CaptureStore,
|
|
25
|
+
type TranscriptMessage
|
|
26
|
+
} from './cached-request.js'
|
|
18
27
|
import { checkpointSummary } from './summary.js'
|
|
19
28
|
|
|
20
29
|
const MAX_TOKENS_SHARE = 0.8
|
|
21
30
|
|
|
22
31
|
type Preparation = SessionBeforeCompactEvent['preparation']
|
|
23
32
|
|
|
24
|
-
type Decision = { kind: 'cached'; summary: string; tokens: number } | { kind: 'fallback'; reason: string }
|
|
33
|
+
type Decision = { kind: 'cached'; summary: string; tokens: number; usage: Usage } | { kind: 'fallback'; reason: string }
|
|
25
34
|
|
|
26
35
|
function traceFile(): string {
|
|
27
36
|
return join(getAgentDir(), 'cached-compaction.jsonl')
|
|
@@ -51,11 +60,15 @@ async function cachedSummary(
|
|
|
51
60
|
const keptStart = keptStartQuote(event.branchEntries, event.preparation.firstKeptEntryId)
|
|
52
61
|
const instruction = summaryInstruction(keptStart, event.customInstructions)
|
|
53
62
|
const transcript = convertToLlm([...captured.transcript, ...trailing, instruction])
|
|
54
|
-
const stream = ctx.modelRegistry.streamSimple(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
const stream = ctx.modelRegistry.streamSimple(
|
|
64
|
+
captured.model,
|
|
65
|
+
{ messages: transcript },
|
|
66
|
+
{
|
|
67
|
+
maxTokens: Math.min(Math.floor(event.preparation.settings.reserveTokens * MAX_TOKENS_SHARE), 32_000),
|
|
68
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
69
|
+
cacheRetention: resolveRetention(process.env)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
59
72
|
for await (const _ of stream) {
|
|
60
73
|
// drain: the final assistant message carries text, usage and the stop reason
|
|
61
74
|
}
|
|
@@ -65,7 +78,7 @@ async function cachedSummary(
|
|
|
65
78
|
}
|
|
66
79
|
const summary = assistantText(assistant)
|
|
67
80
|
if (summary.trim() === '') return { kind: 'fallback', reason: 'the summary came back empty' }
|
|
68
|
-
return { kind: 'cached', summary, tokens: assistant.usage.input }
|
|
81
|
+
return { kind: 'cached', summary, tokens: assistant.usage.input, usage: assistant.usage }
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
function decideFallback(reason: string): SessionBeforeCompactResult {
|
|
@@ -94,7 +107,6 @@ let pendingTranscript: TranscriptMessage[] = []
|
|
|
94
107
|
|
|
95
108
|
export default function cachedCompactionExtension(pi: ExtensionAPI): void {
|
|
96
109
|
const store: CaptureStore = { captured: null }
|
|
97
|
-
let retentionApplied = false
|
|
98
110
|
|
|
99
111
|
pi.on('context_with_system', (event) => {
|
|
100
112
|
pendingTranscript = event.messages
|
|
@@ -109,10 +121,6 @@ export default function cachedCompactionExtension(pi: ExtensionAPI): void {
|
|
|
109
121
|
at: Date.now()
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
|
-
if (!retentionApplied && ctx.hasUI && process.env['PI_CACHE_RETENTION'] === undefined) {
|
|
113
|
-
retentionApplied = true
|
|
114
|
-
process.env['PI_CACHE_RETENTION'] = 'long'
|
|
115
|
-
}
|
|
116
124
|
})
|
|
117
125
|
|
|
118
126
|
pi.on('model_select', () => {
|
|
@@ -140,13 +148,9 @@ export default function cachedCompactionExtension(pi: ExtensionAPI): void {
|
|
|
140
148
|
const decision = await cachedSummary(captured, event, ctx)
|
|
141
149
|
if (decision.kind === 'fallback') return decideFallback(decision.reason)
|
|
142
150
|
const previous = getLatestCompactionEntry(event.branchEntries)
|
|
143
|
-
const prepared = checkpointSummary(
|
|
144
|
-
decision.summary,
|
|
145
|
-
event.preparation.fileOps,
|
|
146
|
-
previous?.details
|
|
147
|
-
)
|
|
151
|
+
const prepared = checkpointSummary(decision.summary, event.preparation.fileOps, previous?.details)
|
|
148
152
|
trace({ decision: 'cached', tokens: decision.tokens })
|
|
149
|
-
return runSummary(event.preparation, prepared.summary, prepared.details,
|
|
153
|
+
return runSummary(event.preparation, prepared.summary, prepared.details, decision.usage)
|
|
150
154
|
} catch (error) {
|
|
151
155
|
const reason = error instanceof Error ? error.message : String(error)
|
|
152
156
|
return decideFallback(reason)
|
package/src/cached-request.ts
CHANGED
|
@@ -81,15 +81,13 @@ export function walkTrailing(captured: CapturedRequest, branchEntries: SessionEn
|
|
|
81
81
|
|
|
82
82
|
export interface KeptStart {
|
|
83
83
|
text: string
|
|
84
|
-
repeats: number
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
const QUOTE_CHARS = 120
|
|
88
87
|
|
|
89
88
|
/**
|
|
90
89
|
* The opening of the first message pi keeps verbatim, quoted back to the summarizer. The
|
|
91
|
-
* quote marks where the kept window begins.
|
|
92
|
-
* so the quote names the last of them.
|
|
90
|
+
* quote marks where the kept window begins.
|
|
93
91
|
*/
|
|
94
92
|
export function keptStartQuote(branchEntries: SessionEntry[], firstKeptEntryId: string): KeptStart | null {
|
|
95
93
|
const start = branchEntries.findIndex((entry) => entry.id === firstKeptEntryId)
|
|
@@ -100,12 +98,7 @@ export function keptStartQuote(branchEntries: SessionEntry[], firstKeptEntryId:
|
|
|
100
98
|
.filter((text) => text !== '')
|
|
101
99
|
const first = quotes[0]
|
|
102
100
|
if (first === undefined) return null
|
|
103
|
-
|
|
104
|
-
let repeats = 0
|
|
105
|
-
for (const quote of quotes) {
|
|
106
|
-
if (quote.startsWith(text)) repeats++
|
|
107
|
-
}
|
|
108
|
-
return { text, repeats }
|
|
101
|
+
return { text: first.slice(0, QUOTE_CHARS) }
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
/**
|
|
@@ -118,7 +111,6 @@ export function summaryInstruction(keptStart: KeptStart | null, customFocus: str
|
|
|
118
111
|
keptStart === null
|
|
119
112
|
? 'Summarize the conversation so far.'
|
|
120
113
|
: `Summarize the conversation only up to the message beginning "${keptStart.text}".\n` +
|
|
121
|
-
'A message with that opening may appear more than once: the boundary is the last of them.\n' +
|
|
122
114
|
'Do not include anything after the boundary.'
|
|
123
115
|
const message: BranchMessage = {
|
|
124
116
|
role: 'user',
|