@tduyng/pi-cost 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-cost",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Session spend, live provider rate-limit windows, footer segments, and per-message timestamps.",
5
5
  "keywords": [
6
6
  "pi",
package/src/cost/index.ts CHANGED
@@ -86,9 +86,12 @@ function errorText(cause: unknown): string {
86
86
  return cause instanceof Error ? cause.message : String(cause)
87
87
  }
88
88
 
89
- export async function claudeToken(pi: ExtensionAPI): Promise<string | undefined> {
89
+ export async function claudeToken(
90
+ pi: ExtensionAPI,
91
+ platform: NodeJS.Platform = process.platform
92
+ ): Promise<string | undefined> {
90
93
  let raw: string | undefined
91
- if (process.platform === 'darwin') {
94
+ if (platform === 'darwin') {
92
95
  const result = await pi.exec('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], {
93
96
  cwd: os.homedir(),
94
97
  timeout: KEYCHAIN_TIMEOUT_MS
@@ -166,11 +169,18 @@ function modelRates(ctx: ExtensionCommandContext): ModelRates | undefined {
166
169
  : undefined
167
170
  }
168
171
 
169
- function sessionSpend(ctx: ExtensionCommandContext): SessionSpend {
172
+ function sessionSpend(ctx: ExtensionCommandContext, overheadRecords: ReturnType<typeof readOverhead>): SessionSpend {
170
173
  const turns: SessionTurn[] = []
171
174
  let costUsd = 0
172
175
  let collapsed = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
173
- const rates = modelRates(ctx)
176
+ const currentRates = modelRates(ctx)
177
+ const ratesFor = (provider: string, model: string): ModelRates | undefined => {
178
+ const found = ctx.modelRegistry.find(provider, model)
179
+ const cost = found?.cost
180
+ return cost
181
+ ? { input: cost.input, output: cost.output, cacheRead: cost.cacheRead, cacheWrite: cost.cacheWrite }
182
+ : undefined
183
+ }
174
184
  for (const entry of ctx.sessionManager.getBranch()) {
175
185
  if (entry.type === 'branch_summary') {
176
186
  if (entry.usage) {
@@ -181,7 +191,7 @@ function sessionSpend(ctx: ExtensionCommandContext): SessionSpend {
181
191
  cacheRead: collapsed.cacheRead + usage.cacheRead,
182
192
  cacheWrite: collapsed.cacheWrite + usage.cacheWrite
183
193
  }
184
- costUsd = addTurnCost(costUsd, usage, rates)
194
+ costUsd = addTurnCost(costUsd, usage, currentRates)
185
195
  }
186
196
  continue
187
197
  }
@@ -191,7 +201,7 @@ function sessionSpend(ctx: ExtensionCommandContext): SessionSpend {
191
201
  continue
192
202
  }
193
203
  const usage = entry.message.usage
194
- costUsd = addTurnCost(costUsd, usage, rates)
204
+ costUsd = addTurnCost(costUsd, usage, ratesFor(entry.message.provider, entry.message.model) ?? currentRates)
195
205
  turns.push({ usage, timestamp: entry.timestamp })
196
206
  }
197
207
  const totals = sessionTotals(turns)
@@ -199,17 +209,14 @@ function sessionSpend(ctx: ExtensionCommandContext): SessionSpend {
199
209
  totals.output += collapsed.output
200
210
  totals.cacheRead += collapsed.cacheRead
201
211
  totals.cacheWrite += collapsed.cacheWrite
202
- const overhead = sumOverhead(readOverhead(getAgentDir(), ctx.sessionManager.getSessionId()))
203
- return { costUsd, totals, overhead }
212
+ return { costUsd, totals, overhead: sumOverhead(overheadRecords) }
204
213
  }
205
214
 
206
215
  function sessionLines(session: SessionSpend, theme: Theme): string[] {
207
216
  const label = (text: string) => theme.fg('dim', text.padEnd(LABEL_WIDTH))
208
217
  const totals = session.totals
209
218
  const hit = formatCacheHit(totals)
210
- const extensions = [...session.overhead.byExtension]
211
- .map(([name, spend]) => `${name} ${spend.calls}`)
212
- .join(', ')
219
+ const extensions = [...session.overhead.byExtension].map(([name, spend]) => `${name} ${spend.calls}`).join(', ')
213
220
  return [
214
221
  ` ${label('Cost')}${formatCostUsd(session.costUsd)}`,
215
222
  ...(session.overhead.calls === 0
@@ -370,6 +377,7 @@ export default function costExtension(pi: ExtensionAPI): void {
370
377
  container.addChild(border)
371
378
 
372
379
  const usage = ctx.getContextUsage()
380
+ const overheadRecords = readOverhead(getAgentDir(), ctx.sessionManager.getSessionId())
373
381
  const header = [
374
382
  ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
375
383
  usage?.percent != null ? `context ${Math.round(usage.percent)}%` : undefined
@@ -387,7 +395,7 @@ export default function costExtension(pi: ExtensionAPI): void {
387
395
  }
388
396
 
389
397
  render = () => {
390
- const session = sessionSpend(ctx)
398
+ const session = sessionSpend(ctx, overheadRecords)
391
399
  lines = [
392
400
  `${theme.fg('accent', theme.bold('Cost and limits'))} ${theme.fg('dim', header)}`,
393
401
  '',
@@ -57,6 +57,7 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
57
57
  })
58
58
 
59
59
  pi.on('agent_settled', () => {
60
+ if (!enabled) return
60
61
  void refreshGit()
61
62
  })
62
63
 
@@ -73,7 +74,7 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
73
74
 
74
75
  async function refreshGit(): Promise<void> {
75
76
  const ctx = mountedCtx
76
- if (!ctx) return
77
+ if (!ctx || !enabled) return
77
78
  try {
78
79
  const status = await pi.exec('git', ['status', '--porcelain'], { cwd: ctx.cwd })
79
80
  changedFiles = status.stdout.split('\n').filter((line) => line.trim().length > 0).length
@@ -87,7 +88,7 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
87
88
 
88
89
  async function refreshPr(): Promise<void> {
89
90
  const ctx = mountedCtx
90
- if (!ctx || !showPr) return
91
+ if (!ctx || !showPr || !enabled) return
91
92
  try {
92
93
  const pr = await pi.exec('gh', ['pr', 'view', '--json', 'number', '-q', '.number'], { cwd: ctx.cwd })
93
94
  const parsed = Number.parseInt(pr.stdout.trim(), 10)
@@ -122,7 +123,7 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
122
123
  }
123
124
 
124
125
  function mount(ctx: ExtensionContext): void {
125
- if (!ctx.hasUI) return
126
+ if (!ctx.hasUI || !enabled) return
126
127
  mountedCtx = ctx
127
128
 
128
129
  void refreshGit()
@@ -37,8 +37,6 @@ export function isToolCallMessage(message: { stopReason?: string; content?: { ty
37
37
  )
38
38
  }
39
39
 
40
- const DUPLICATE_WINDOW_MS = 2_000
41
-
42
40
  export function hasDuplicateTimestampEntry(
43
41
  entries: readonly unknown[],
44
42
  role: 'user' | 'assistant',
@@ -49,7 +47,7 @@ export function hasDuplicateTimestampEntry(
49
47
  const entry = entries[i] as { type?: string; customType?: string; data?: TimestampEntryData } | undefined
50
48
  if (!entry || entry.type !== 'custom' || entry.customType !== ENTRY_TYPE) continue
51
49
  if (!entry.data || entry.data.role !== role) continue
52
- if (Math.abs((entry.data.timestamp ?? Number.NaN) - timestamp) <= DUPLICATE_WINDOW_MS) return true
50
+ if (entry.data.timestamp === timestamp) return true
53
51
  }
54
52
  return false
55
53
  }