@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tduyng/pi-context",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Context-window suite: see what fills it, prune spent tool results, retire reads an edit made wrong, and recover what compaction dropped.",
5
5
  "keywords": [
6
6
  "pi",
@@ -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
@@ -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 { assistantText, forget, keptStartQuote, summaryInstruction, walkTrailing, type CapturedRequest, type CaptureStore, type TranscriptMessage } from './cached-request.js'
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(captured.model, { messages: transcript }, {
55
- maxTokens: Math.min(Math.floor(event.preparation.settings.reserveTokens * MAX_TOKENS_SHARE), 32_000),
56
- sessionId: ctx.sessionManager.getSessionId(),
57
- cacheRetention: resolveRetention(process.env)
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, undefined)
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)
@@ -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. A repeated opening is counted from the end,
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
- const text = first.slice(0, QUOTE_CHARS)
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',