dsh-fast 0.1.2 → 0.2.0

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/src/estimate.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { EpochHeader } from '@deepseek-ai/dsh-session'
12
+ import type { SystemPromptBreakdown } from './model.ts'
12
13
 
13
14
  /** Fixed text-density estimate (chars per token). */
14
15
  const CHARS_PER_TOKEN = 4
@@ -19,9 +20,60 @@ const ROLE_OVERHEAD = 4
19
20
  /** Per-block structural overhead for JSON framing and type tags. */
20
21
  const BLOCK_OVERHEAD = 4
21
22
 
23
+ /** One named system-prompt section (name + resolved text). */
24
+ export interface SystemSection {
25
+ name: string
26
+ text: string
27
+ }
28
+
29
+ /** Which tracked bucket a section name belongs to. */
30
+ function bucketOf(name: string): 'agentsMd' | 'skills' | 'persona' | 'other' {
31
+ if (name === 'deployment:persona') return 'persona'
32
+ if (name.toLowerCase().includes('skill')) return 'skills'
33
+ if (/agent|instructions?/iu.test(name)) return 'agentsMd'
34
+ return 'other'
35
+ }
36
+
37
+ /** An empty breakdown (all buckets zeroed). */
38
+ function emptyBreakdown(): SystemPromptBreakdown {
39
+ return {
40
+ agentsMd: { tokens: 0, chars: 0, share: 0 },
41
+ skills: { tokens: 0, chars: 0, share: 0 },
42
+ persona: { tokens: 0, chars: 0, share: 0 },
43
+ other: { tokens: 0, chars: 0, share: 0 },
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Classify the named system-prompt sections into the AGENTS.md / skills /
49
+ * persona / other buckets, with heuristic tokens and per-bucket shares. The
50
+ * caller supplies either the real assembled sections (via the optional
51
+ * `systemPrompt` service) or a single synthetic `{ name: 'other' }` section
52
+ * carrying the rendered prompt, so the breakdown always sums to the system
53
+ * prompt.
54
+ * @param sections - named sections (never empty in practice).
55
+ * @returns the per-bucket breakdown.
56
+ */
57
+ export function classifySystemSections(sections: readonly SystemSection[]): SystemPromptBreakdown {
58
+ const breakdown = emptyBreakdown()
59
+ for (const section of sections) {
60
+ const chars = section.text.length
61
+ if (chars === 0) continue
62
+ const bucket = breakdown[bucketOf(section.name)]
63
+ bucket.chars += chars
64
+ bucket.tokens += Math.ceil(chars / CHARS_PER_TOKEN)
65
+ }
66
+ const total = breakdown.agentsMd.tokens + breakdown.skills.tokens + breakdown.persona.tokens + breakdown.other.tokens
67
+ if (total <= 0) return breakdown
68
+ for (const bucket of [breakdown.agentsMd, breakdown.skills, breakdown.persona, breakdown.other] as const) {
69
+ bucket.share = bucket.tokens / total
70
+ }
71
+ return breakdown
72
+ }
73
+
22
74
  /**
23
- * Price the assembled system prompt (AGENTS.md + skill directory + persona +
24
- * harness instructions).
75
+ * Price the assembled system prompt (harness identity + persona + tool
76
+ * guidance + plugin sections).
25
77
  * @param header - canonical request envelope, or undefined before any request.
26
78
  * @returns heuristic system-prompt tokens; 0 when absent.
27
79
  */
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ import type {} from '@deepseek-ai/dsh-commands'
21
21
  import { Config, resolveConfig } from './config.ts'
22
22
  import { FastCollector } from './collector.ts'
23
23
  import type { MeasureFn, TokenMeasurement } from './collector.ts'
24
+ import type { SystemSection } from './estimate.ts'
24
25
  import { buildReport, renderFastText } from './analyze.ts'
25
26
  import type { FastReport } from './model.ts'
26
27
  import { fastDomainSpec, appendSample } from './store.ts'
@@ -43,8 +44,11 @@ export type {
43
44
  ContextStats,
44
45
  CacheStats,
45
46
  StoredSample,
47
+ PromptBucket,
48
+ SystemPromptBreakdown,
46
49
  } from './model.ts'
47
50
  export { FastCollector, detectSpilledResult, flattenToolResultText, sharesOf, hitRateOf } from './collector.ts'
51
+ export { classifySystemSections, type SystemSection } from './estimate.ts'
48
52
  export { buildReport, buildSuggestions, renderFastText } from './analyze.ts'
49
53
  export { fastDomainSpec, appendSample, historySchema } from './store.ts'
50
54
 
@@ -53,6 +57,11 @@ interface TokenMeterService {
53
57
  measure(session: Session): TokenMeasurement
54
58
  }
55
59
 
60
+ /** The structural surface of the optional `ctx.systemPrompt` service (section assembly). */
61
+ interface SystemPromptService {
62
+ assemble(): Promise<{ sections: readonly { name: string; text: string }[] }>
63
+ }
64
+
56
65
  /**
57
66
  * Mount the diagnostics. The resolved config is validated first (fail loud);
58
67
  * with `enabled: false` the plugin registers nothing and stays inert.
@@ -84,9 +93,22 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
84
93
  }
85
94
  }
86
95
 
96
+ /** Assemble the named system-prompt sections for the per-section breakdown. */
97
+ const assembleSections = async (): Promise<readonly SystemSection[] | undefined> => {
98
+ const systemPrompt = ctx.get('systemPrompt') as unknown as SystemPromptService | undefined
99
+ if (systemPrompt === undefined) return undefined
100
+ try {
101
+ const assembly = await systemPrompt.assemble()
102
+ return assembly.sections.map(section => ({ name: section.name, text: section.text }))
103
+ } catch (error) {
104
+ logger.warn(`system-prompt section assembly failed: ${error instanceof Error ? error.message : String(error)}`)
105
+ return undefined
106
+ }
107
+ }
108
+
87
109
  /** Build the complete report for one session. */
88
- const reportFor = (session: Session): FastReport => {
89
- const snapshot = collector.snapshot(session, measure)
110
+ const reportFor = async (session: Session): Promise<FastReport> => {
111
+ const snapshot = collector.snapshot(session, measure, await assembleSections())
90
112
  return buildReport(
91
113
  snapshot,
92
114
  {
@@ -100,8 +122,8 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
100
122
  }
101
123
 
102
124
  /** Append one snapshot to the session's durable history (fire-and-forget). */
103
- const persist = (session: Session): void => {
104
- const snapshot = collector.snapshot(session, measure)
125
+ const persist = async (session: Session): Promise<void> => {
126
+ const snapshot = collector.snapshot(session, measure, await assembleSections())
105
127
  const next = appendSample(sessions.get(session.id), { at: Date.now(), snapshot }, resolved.maxHistorySamples)
106
128
  void sessions.put(session.id, next).catch((error: unknown) => {
107
129
  logger.warn(`session "${session.id}": persist failed: ${error instanceof Error ? error.message : String(error)}`)
@@ -112,8 +134,8 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
112
134
  ctx.commands.register({
113
135
  name: 'fast',
114
136
  description: 'Print the dsh-fast performance report for the active session.',
115
- handler: (invocation) => {
116
- const report = reportFor(invocation.agent.session)
137
+ async handler(invocation) {
138
+ const report = await reportFor(invocation.agent.session)
117
139
  return { kind: 'success', text: renderFastText(report) }
118
140
  },
119
141
  })
@@ -171,6 +193,17 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
171
193
  systemShare: { type: 'number', required: true },
172
194
  toolsShare: { type: 'number', required: true },
173
195
  surfaceShare: { type: 'number', required: true },
196
+ systemBreakdown: {
197
+ type: 'object',
198
+ properties: {
199
+ agentsMd: { type: 'object', properties: { tokens: { type: 'number', required: true }, chars: { type: 'number', required: true }, share: { type: 'number', required: true } }, additionalProperties: false, required: true },
200
+ skills: { type: 'object', properties: { tokens: { type: 'number', required: true }, chars: { type: 'number', required: true }, share: { type: 'number', required: true } }, additionalProperties: false, required: true },
201
+ persona: { type: 'object', properties: { tokens: { type: 'number', required: true }, chars: { type: 'number', required: true }, share: { type: 'number', required: true } }, additionalProperties: false, required: true },
202
+ other: { type: 'object', properties: { tokens: { type: 'number', required: true }, chars: { type: 'number', required: true }, share: { type: 'number', required: true } }, additionalProperties: false, required: true },
203
+ },
204
+ additionalProperties: false,
205
+ required: true,
206
+ },
174
207
  },
175
208
  additionalProperties: false,
176
209
  required: true,
@@ -199,7 +232,7 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
199
232
  if (session === undefined) {
200
233
  throw new Error('fast_report requires an agent-owned session')
201
234
  }
202
- return reportFor(session)
235
+ return await reportFor(session)
203
236
  },
204
237
  }))
205
238
 
@@ -224,7 +257,7 @@ export async function apply(ctx: Context, config: Config = {}): Promise<void> {
224
257
  for (const session of collector.liveSessions()) {
225
258
  if (!collector.isDirty(session)) continue
226
259
  collector.markClean(session)
227
- persist(session)
260
+ void persist(session)
228
261
  }
229
262
  }, resolved.snapshotIntervalMs)
230
263
  return async () => {
package/src/model.ts CHANGED
@@ -44,11 +44,33 @@ export interface CompactionStats {
44
44
  shadowedTokens: number
45
45
  }
46
46
 
47
+ /** One named bucket of the injected system prompt. */
48
+ export interface PromptBucket {
49
+ /** Heuristic token estimate for this bucket (`ceil(chars / 4)`). */
50
+ tokens: number
51
+ /** Raw character count of the bucket's sections. */
52
+ chars: number
53
+ /** Fraction of the total system-prompt bucket tokens (0 when the total is 0). */
54
+ share: number
55
+ }
56
+
57
+ /** The system-prompt breakdown: AGENTS.md / skills / persona / other named sections. */
58
+ export interface SystemPromptBreakdown {
59
+ /** Sections that carry agent instructions (AGENTS.md-style). */
60
+ agentsMd: PromptBucket
61
+ /** Sections that carry skill content. */
62
+ skills: PromptBucket
63
+ /** The `deployment:persona` section. */
64
+ persona: PromptBucket
65
+ /** Harness identity, tool guidance, and any unclassified sections. */
66
+ other: PromptBucket
67
+ }
68
+
47
69
  /** Context-injection volume: where the request context's tokens live. */
48
70
  export interface ContextStats {
49
71
  /** Canonical current pressure (token-meter total), or the heuristic header+surface sum. */
50
72
  totalTokens: number
51
- /** Assembled system-prompt tokens — AGENTS.md, skill directory, persona, and harness instructions. */
73
+ /** Assembled system-prompt tokens — harness identity, persona, tool guidance, and plugin sections. */
52
74
  systemTokens: number
53
75
  /** Tool-schema tokens. */
54
76
  toolSchemaTokens: number
@@ -60,6 +82,8 @@ export interface ContextStats {
60
82
  toolsShare: number
61
83
  /** `surfaceTokens / totalTokens` (0 when the total is 0). */
62
84
  surfaceShare: number
85
+ /** Per-section system-prompt breakdown (AGENTS.md / skills / persona / other). */
86
+ systemBreakdown: SystemPromptBreakdown
63
87
  }
64
88
 
65
89
  /** LLM cache accounting aggregated from `assistant/message` usage. */
package/src/store.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Durable metric storage over the harness storage domain. The `dsh-fast`
3
3
  * domain keeps one bounded history per session, so `/fast` and `fast_report`
4
4
  * metrics survive a restart and the trend stays queryable without touching the
5
- * session log (the rc.8 `Session.append` offers no `ignorable` marker and no
5
+ * session log (the rc.2 `Session.append` offers no `ignorable` marker and no
6
6
  * external event-registration surface, so a custom session event would make
7
7
  * the persistence coordinator refuse the log on restore).
8
8
  * @module dsh-fast/store
@@ -33,6 +33,21 @@ const compactionSchema = z.object({
33
33
  shadowedTokens: z.number().int().nonnegative(),
34
34
  })
35
35
 
36
+ /** Zod schema for one prompt bucket. */
37
+ const bucketSchema = z.object({
38
+ tokens: z.number().nonnegative(),
39
+ chars: z.number().nonnegative(),
40
+ share: z.number(),
41
+ })
42
+
43
+ /** Zod schema for the system-prompt breakdown. */
44
+ const breakdownSchema = z.object({
45
+ agentsMd: bucketSchema,
46
+ skills: bucketSchema,
47
+ persona: bucketSchema,
48
+ other: bucketSchema,
49
+ })
50
+
36
51
  /** Zod schema for the context section. */
37
52
  const contextSchema = z.object({
38
53
  totalTokens: z.number().nonnegative(),
@@ -42,6 +57,7 @@ const contextSchema = z.object({
42
57
  systemShare: z.number(),
43
58
  toolsShare: z.number(),
44
59
  surfaceShare: z.number(),
60
+ systemBreakdown: breakdownSchema,
45
61
  })
46
62
 
47
63
  /** Zod schema for the cache section. */
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Single-source plugin version, bumped by `scripts/release.mjs`. @module dsh-fast/version */
2
- export const VERSION = '0.1.2'
2
+ export const VERSION = '0.2.0'