Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/commands/artifacts/__tests__/scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,44 @@ describe('extractArtifacts', () => {

expect(result.map(r => r.basename)).toEqual(['b.html', 'a.html'])
})

test('indexes multiple artifacts while preserving first-result-wins semantics', () => {
const messages: Message[] = [
assistantToolUse('tu1', { file_path: '/tmp/a.html' }),
assistantToolUse('tu2', { file_path: '/tmp/b.html' }),
userToolResult(
'tu1',
'https://x.test/first.html (id: first, expires: 2026-06-27T10:00:00.000Z)',
),
userToolResult(
'tu1',
'https://x.test/duplicate.html (id: duplicate, expires: 2026-06-28T10:00:00.000Z)',
),
userToolResult(
'tu2',
'https://x.test/second.html (id: second, expires: 2026-06-29T10:00:00.000Z)',
),
]

const result = extractArtifacts(messages)

expect(result.map(r => r.hash)).toEqual(['second', 'first'])
})

test('still pairs a tool_result that appears before its artifact tool_use', () => {
const messages: Message[] = [
userToolResult(
'tu1',
'https://x.test/a.html (id: a, expires: 2026-06-27T10:00:00.000Z)',
),
userToolResult(
'tu2',
'https://x.test/b.html (id: b, expires: 2026-06-28T10:00:00.000Z)',
),
assistantToolUse('tu1', { file_path: '/tmp/a.html' }),
assistantToolUse('tu2', { file_path: '/tmp/b.html' }),
]

expect(extractArtifacts(messages).map(r => r.hash)).toEqual(['b', 'a'])
})
})
52 changes: 50 additions & 2 deletions src/commands/artifacts/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ const URL_REGEX = /https?:\/\/[^\s)"',]+\.html\b/
const ID_REGEX = /\bid:\s*([A-Za-z0-9_-]+)/
const EXPIRES_REGEX = /\bexpires:\s*([0-9T:.Z+-]+)/

type ArtifactToolResult = {
content: unknown
is_error?: boolean
}

export function extractArtifacts(messages: Message[]): ArtifactInfo[] {
const results: ArtifactInfo[] = []
let artifactUseCount = 0
let indexedResults: Map<string, ArtifactToolResult> | null = null

for (const message of messages) {
if (message.type !== 'assistant') continue
Expand All @@ -35,7 +42,19 @@ export function extractArtifacts(messages: Message[]): ArtifactInfo[] {
const input = b.input as { file_path?: string } | undefined
const filePath = input?.file_path ?? '<unknown>'

const resultBlock = findToolResult(messages, toolUseId)
artifactUseCount++
if (artifactUseCount === 2) {
// One direct lookup is already linear and avoids allocating an index
// for the common single-artifact case. Starting with the second use,
// index tool results once instead of rescanning the transcript for
// every artifact (which becomes quadratic in artifact-heavy sessions).
indexedResults = indexToolResults(messages)
}

const resultBlock = indexedResults
? (indexedResults.get(toolUseId) ?? null)
: findToolResult(messages, toolUseId)

if (!resultBlock) continue

const rawContent =
Expand Down Expand Up @@ -78,7 +97,7 @@ export function extractArtifacts(messages: Message[]): ArtifactInfo[] {
function findToolResult(
messages: Message[],
toolUseId: string,
): { content: unknown; is_error?: boolean } | null {
): ArtifactToolResult | null {
for (const message of messages) {
if (message.type !== 'user') continue
const content = message.message?.content
Expand All @@ -95,3 +114,32 @@ function findToolResult(
}
return null
}

function indexToolResults(
messages: Message[],
): Map<string, ArtifactToolResult> {
const results = new Map<string, ArtifactToolResult>()

for (const message of messages) {
if (message.type !== 'user') continue
const content = message.message?.content
if (!Array.isArray(content)) continue

for (const block of content) {
if (typeof block !== 'object' || block === null) continue
if (!('type' in block)) continue
const b = block as unknown as Record<string, unknown>
if (b.type !== 'tool_result') continue
const toolUseId = b.tool_use_id as string
if (results.has(toolUseId)) continue

// Match findToolResult's first-result-wins behavior for duplicate IDs.
results.set(toolUseId, {
content: b.content,
is_error: b.is_error as boolean | undefined,
})
Comment on lines +128 to +140

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/types/message.ts --items all
rg -n -C 4 '\btool_use_id\b|\bid\b' \
  src/types/message.ts \
  src/commands/artifacts/scanner.ts

Repository: claude-code-best/claude-code

Length of output: 2464


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- scanner.ts ---'
sed -n '1,180p' src/commands/artifacts/scanner.ts

printf '%s\n' '--- message type definitions and relevant references ---'
rg -n -C 5 'ContentBlock|ToolResult|tool_use_id|tool_use' src/types src/commands/artifacts src --glob '*.{ts,tsx}' | head -n 500

printf '%s\n' '--- scanner tests and call sites ---'
rg -n -C 4 'findToolResult|build.*Index|ArtifactToolResult|scanArtifacts|tool_use_id' src --glob '*.{ts,tsx}'

Repository: claude-code-best/claude-code

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- scanner.ts ---'
cat -n src/commands/artifacts/scanner.ts | sed -n '1,175p'

printf '%s\n' '--- scanner symbols and local tests ---'
rg -n -C 8 'ArtifactToolResult|findToolResult|results\.|scanArtifacts|scanner' \
  src/commands/artifacts --glob '*.{ts,tsx}'

printf '%s\n' '--- message.ts relevant declarations ---'
rg -n -C 8 'ContentBlock|ToolResultBlock|tool_use_id|Message' src/types/message.ts

printf '%s\n' '--- package/dependency declarations for SDK block types ---'
rg -n -C 4 'ToolResultBlock|tool_use_id' package.json bun.lockb bun.lock yarn.lock pnpm-lock.yaml node_modules/@anthropic-ai 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 18996


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- scanner tests ---'
cat -n src/commands/artifacts/__tests__/scanner.test.ts | sed -n '1,260p'

printf '%s\n' '--- model-provider type sources in the repository ---'
git ls-files | rg '(^|/)(model-provider|.*message.*|.*content.*)\.(ts|tsx|d\.ts)$' | head -n 200

printf '%s\n' '--- all scanner casts and transcript construction ---'
rg -n -C 3 'as string|as unknown as|tool_use_id' src/commands/artifacts src/types src/utils/sessionStorage.ts src/utils/conversationRecovery.ts --glob '*.{ts,tsx}' | head -n 400

Repository: claude-code-best/claude-code

Length of output: 21571


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- model-provider message types ---'
rg -n -C 12 'ToolResult|tool_result|ToolUse|tool_use|ContentBlock' packages/@ant/model-provider/src/types/message.ts

printf '%s\n' '--- Message input boundary and artifact command callers ---'
rg -n -C 8 'extractArtifacts\(|context\.messages|messages\?:|messages:' \
  src/commands/artifacts packages/@ant/model-provider/src --glob '*.{ts,tsx}'

printf '%s\n' '--- malformed-block test feasibility ---'
rg -n -C 5 'as unknown as Message|tool_use_id: [^'\''"]|tool_use_id: null|tool_use_id: [0-9]' \
  src/commands/artifacts src --glob '*.{test.ts,test.tsx}' | head -n 300

Repository: claude-code-best/claude-code

Length of output: 50034


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const results = new Map()
const malformedToolResult = { type: 'tool_result', tool_use_id: 42, content: 'payload' }
const assertedToolUseId = malformedToolResult.tool_use_id
results.set(assertedToolUseId, { content: malformedToolResult.content })

console.log({
  storedKeyType: typeof [...results.keys()][0],
  lookupWithStringId: results.get('42') ?? null,
  lookupWithNumberId: results.get(42) ?? null,
})
JS

Repository: claude-code-best/claude-code

Length of output: 270


Narrow tool_use_id before indexing.

If a malformed tool_result contains a non-string tool_use_id, the assertion stores a non-string key and string lookups fail. Skip the block unless typeof toolUseId === 'string'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/artifacts/scanner.ts` around lines 128 - 140, The tool_result
scan must ignore malformed entries with non-string tool_use_id values. In the
loop processing blocks in scanner.ts, validate b.tool_use_id with typeof ===
'string' and continue before checking results or calling results.set; retain the
existing first-result-wins behavior for valid string IDs.

Source: Coding guidelines

}
}

return results
}