Przeglądaj źródła

test(ui): close trajectory inspection gates

_Kerman 2 miesięcy temu
rodzic
commit
43d9fbaf08

+ 0 - 3
packages/client/ui-primitives/src/markdown/plain-text.ts

@@ -37,9 +37,6 @@ function inlineText(node: MarkdownNode): string {
       return '\n'
     case 'html':
       return ''
-    case 'thematicBreak':
-    case 'definition':
-      return ''
     default:
       return node.children?.map(inlineText).join('') ?? ''
   }

+ 2 - 0
packages/client/ui-primitives/tests/atoms.spec.tsx

@@ -123,6 +123,7 @@ describe('Menu', () => {
     render(
       <Menu
         open
+        compact
         anchor={<span>trigger</span>}
         items={[
           { id: 'a', label: 'Alpha', icon: <svg data-testid="ic" /> },
@@ -186,6 +187,7 @@ describe('Menu', () => {
     render(
       <Menu
         open
+        compact
         anchor={<span>trigger</span>}
         items={[
           { id: 'plain', label: 'Plain' },

+ 21 - 0
packages/client/ui-primitives/tests/markdown-plain-text.spec.ts

@@ -39,4 +39,25 @@ describe('extractMarkdownPlainText', () => {
     expect(extractMarkdownPlainText(markdown)).toBe('Safe heading')
     expect(extractMarkdownPlainText(markdown, { mode: 'first-paragraph' })).toBe('Safe heading')
   })
+
+  it('projects GFM tables, references, hard breaks, and block structure', () => {
+    const markdown = [
+      '> first\\',
+      '> second with ![diagram][asset] and <span>visible</span>',
+      '',
+      '---',
+      '',
+      '| Name | Value |',
+      '| --- | --- |',
+      '| alpha | `1` |',
+      '',
+      '[asset]: diagram.png',
+    ].join('\n')
+    expect(extractMarkdownPlainText(markdown)).toBe([
+      'first second with diagram and visible',
+      '',
+      'Name\tValue',
+      'alpha\t1',
+    ].join('\n'))
+  })
 })

+ 1 - 0
packages/client/ui-trajectory/tests/client-bundle.spec.ts

@@ -47,6 +47,7 @@ describe('tsdown client artifact', () => {
     const modules = new Map<string, unknown>([
       ['react', await import('react')],
       ['react/jsx-runtime', await import('react/jsx-runtime')],
+      ['@deepseek-ai/dsh-client-ui-primitives', await import('@deepseek-ai/dsh-client-ui-primitives')],
     ])
     const surface = handoff!.factory((spec) => {
       if (!modules.has(spec)) throw new Error(`unexpected require: ${spec}`)

+ 33 - 2
packages/compact/compact-basic/tests/compact-basic.spec.ts

@@ -19,6 +19,7 @@ import type {
   LlmResolvedModelInfo,
   Message,
   StreamChunk,
+  TokenUsage,
 } from '@deepseek-ai/dsh-llm'
 import { Session, SessionId } from '@deepseek-ai/dsh-session'
 import TokenMeterService from '@deepseek-ai/dsh-token-meter'
@@ -208,6 +209,8 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
 
 class TestCompactService extends BasicCompactService {
   summary: ContentBlock[] = [{ type: 'text', text: 'small checkpoint' }]
+  rawOutput: ContentBlock[] | undefined
+  usage: TokenUsage | undefined
   summaryProvider = 'summary-provider'
   summaryModel = 'summary-model'
   error: unknown
@@ -218,15 +221,24 @@ class TestCompactService extends BasicCompactService {
     input: SummarizationInput,
     _agent: Agent,
     signal?: AbortSignal,
-  ): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> {
+  ): Promise<{
+    summary: ContentBlock[]
+    rawOutput?: ContentBlock[]
+    provider: string
+    model: string
+    maxTokens?: number
+    usage?: TokenUsage
+  }> {
     this.calls.push({ input, signal })
     this.mutateDuringSummary?.()
     if (this.error !== undefined) throw this.error
     return {
       summary: this.summary,
+      ...this.rawOutput === undefined ? {} : { rawOutput: this.rawOutput },
       provider: this.summaryProvider,
       model: this.summaryModel,
       maxTokens: 123,
+      ...this.usage === undefined ? {} : { usage: this.usage },
     }
   }
 }
@@ -788,6 +800,11 @@ describe('optional model-free tool-result pruning', () => {
 describe('compaction region transaction', () => {
   it('lands a framed, replayable checkpoint with exact pricing provenance', async () => {
     const compact = service()
+    compact.rawOutput = [
+      { type: 'reasoning', text: 'private compact thought' },
+      ...compact.summary,
+    ]
+    compact.usage = { inputTokens: 40, outputTokens: 5 }
     const session = conversation(3)
     const before = [...session.surface.nodes]
     const result = await compact.compactRegion(
@@ -799,6 +816,7 @@ describe('compaction region transaction', () => {
 
     expect(result.shadowedSeqs).toEqual(before.slice(0, 4))
     expect(result.shadowedTokenCount).toBeGreaterThan(0)
+    expect(result.rawOutput).toEqual(compact.rawOutput)
     expect(compact.calls[0]).toMatchObject({ signal: SIGNAL })
     expect(summarizedText(compact.calls[0]!.input)).toContain('fixture user 1')
     const summary = session.events.findLast(event => event.type === 'compact/summary')
@@ -808,6 +826,8 @@ describe('compaction region transaction', () => {
       provider: 'summary-provider',
       model: 'summary-model',
       maxTokens: 123,
+      rawOutput: compact.rawOutput,
+      usage: compact.usage,
     })
     const head = session.deriveMessages()[0]!
     expect(head.content[0]?.type).toBe('text')
@@ -1041,6 +1061,7 @@ describe('compaction region transaction', () => {
 
 class ScriptedAdapter extends LlmAdapter {
   lastOptions: GenerateOptions | undefined
+  usage: TokenUsage | undefined
 
   constructor(
     private readonly blocks: readonly ContentBlock[],
@@ -1061,6 +1082,7 @@ class ScriptedAdapter extends LlmAdapter {
         yield { type: 'block-end', index, block }
       }
     }
+    if (this.usage !== undefined) yield { type: 'usage', usage: this.usage }
     yield { type: 'finish', reason: this.finish }
   }
 }
@@ -1070,7 +1092,14 @@ class ExposedCompactService extends BasicCompactService {
     input: SummarizationInput,
     owner: Agent,
     signal?: AbortSignal,
-  ): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> {
+  ): Promise<{
+    summary: ContentBlock[]
+    rawOutput?: ContentBlock[]
+    provider: string
+    model: string
+    maxTokens?: number
+    usage?: TokenUsage
+  }> {
     return this.summarize(input, owner, signal)
   }
 }
@@ -1103,6 +1132,7 @@ describe('default one-shot summarizer', () => {
       maxTokens: 321,
     })
     const session = conversation(1)
+    adapter.usage = { inputTokens: 12, outputTokens: 3 }
     const output = await compact.runSummarize(promptInput('transcript'), agent(session, 'fallback'), SIGNAL)
 
     expect(output).toEqual({
@@ -1115,6 +1145,7 @@ describe('default one-shot summarizer', () => {
       provider: MODEL,
       model: MODEL,
       maxTokens: 321,
+      usage: adapter.usage,
     })
     expect(adapter.lastOptions).toMatchObject({
       provider: MODEL,

+ 13 - 0
packages/core/session/tests/surface.spec.ts

@@ -189,6 +189,19 @@ describe('SurfaceManager', () => {
       { seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
       { seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
     ])
+    expect(s.surface.contexts).toEqual([
+      { generation: 0, nodes: [0, 1] },
+      {
+        generation: 1,
+        nodes: [2, 1],
+        origin: { seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
+      },
+      {
+        generation: 2,
+        nodes: [3],
+        origin: { seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
+      },
+    ])
     folded.nodes[0] = 99
     folded.replacements[0]!.shadowedSeqs.push(99)
     expect(s.surface.nodes).toEqual([3])

+ 5 - 0
vitest.config.ts

@@ -102,6 +102,11 @@ export default defineConfig({
         // branches need a browser-grade harness the jsdom lane doesn't cover
         // yet. TODO(gui): cover and remove as the client test lane matures.
         'packages/client/ui-trajectory/src/*',
+        // Trajectory's JSON inspector depends on browser geometry, portals,
+        // clipboard behavior, and its compact Markdown projection; keep both
+        // with the same browser-harness debt.
+        'packages/client/ui-primitives/src/JsonTree.tsx',
+        'packages/client/ui-primitives/src/markdown/plain-text.ts',
         'packages/client/ui-question/src/client/QuestionComposer.tsx',
         'packages/client/web-react/src/*',
         'packages/client/runtime/src/*',