@@ -16,8 +16,15 @@ const URL_REGEX = /https?:\/\/[^\s)"',]+\.html\b/
1616const ID_REGEX = / \b i d : \s * ( [ A - Z a - z 0 - 9 _ - ] + ) /
1717const EXPIRES_REGEX = / \b e x p i r e s : \s * ( [ 0 - 9 T : . Z + - ] + ) /
1818
19+ type ArtifactToolResult = {
20+ content : unknown
21+ is_error ?: boolean
22+ }
23+
1924export function extractArtifacts ( messages : Message [ ] ) : ArtifactInfo [ ] {
2025 const results : ArtifactInfo [ ] = [ ]
26+ let artifactUseCount = 0
27+ let indexedResults : Map < string , ArtifactToolResult > | null = null
2128
2229 for ( const message of messages ) {
2330 if ( message . type !== 'assistant' ) continue
@@ -35,7 +42,19 @@ export function extractArtifacts(messages: Message[]): ArtifactInfo[] {
3542 const input = b . input as { file_path ?: string } | undefined
3643 const filePath = input ?. file_path ?? '<unknown>'
3744
38- const resultBlock = findToolResult ( messages , toolUseId )
45+ artifactUseCount ++
46+ if ( artifactUseCount === 2 ) {
47+ // One direct lookup is already linear and avoids allocating an index
48+ // for the common single-artifact case. Starting with the second use,
49+ // index tool results once instead of rescanning the transcript for
50+ // every artifact (which becomes quadratic in artifact-heavy sessions).
51+ indexedResults = indexToolResults ( messages )
52+ }
53+
54+ const resultBlock = indexedResults
55+ ? ( indexedResults . get ( toolUseId ) ?? null )
56+ : findToolResult ( messages , toolUseId )
57+
3958 if ( ! resultBlock ) continue
4059
4160 const rawContent =
@@ -78,7 +97,7 @@ export function extractArtifacts(messages: Message[]): ArtifactInfo[] {
7897function findToolResult (
7998 messages : Message [ ] ,
8099 toolUseId : string ,
81- ) : { content : unknown ; is_error ?: boolean } | null {
100+ ) : ArtifactToolResult | null {
82101 for ( const message of messages ) {
83102 if ( message . type !== 'user' ) continue
84103 const content = message . message ?. content
@@ -95,3 +114,32 @@ function findToolResult(
95114 }
96115 return null
97116}
117+
118+ function indexToolResults (
119+ messages : Message [ ] ,
120+ ) : Map < string , ArtifactToolResult > {
121+ const results = new Map < string , ArtifactToolResult > ( )
122+
123+ for ( const message of messages ) {
124+ if ( message . type !== 'user' ) continue
125+ const content = message . message ?. content
126+ if ( ! Array . isArray ( content ) ) continue
127+
128+ for ( const block of content ) {
129+ if ( typeof block !== 'object' || block === null ) continue
130+ if ( ! ( 'type' in block ) ) continue
131+ const b = block as unknown as Record < string , unknown >
132+ if ( b . type !== 'tool_result' ) continue
133+ const toolUseId = b . tool_use_id as string
134+ if ( results . has ( toolUseId ) ) continue
135+
136+ // Match findToolResult's first-result-wins behavior for duplicate IDs.
137+ results . set ( toolUseId , {
138+ content : b . content ,
139+ is_error : b . is_error as boolean | undefined ,
140+ } )
141+ }
142+ }
143+
144+ return results
145+ }
0 commit comments