Skip to content

Commit f54ee3a

Browse files
fix(copilot): thread cursor across content blocks when restoring sim_key tags
1 parent af5bfc8 commit f54ee3a

2 files changed

Lines changed: 29 additions & 6 deletions

File tree

apps/sim/lib/copilot/chat/sim-key-redaction.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,25 @@ describe('sim-key-redaction', () => {
120120
expect(restoreRevealedSimKeysForMessage(msg, cache)).toBe(msg)
121121
})
122122

123+
it('threads the cursor across separate content blocks so each block gets its matching key', () => {
124+
const cache = new Map<string, string[]>([['msg-1', ['sk-sim-A', 'sk-sim-B']]])
125+
const msg: ChatMessage = {
126+
id: 'msg-1',
127+
role: 'assistant',
128+
content: `first ${redacted} (tool ran) second ${redacted}`,
129+
contentBlocks: [
130+
{ type: 'text', content: `first ${redacted}` },
131+
{ type: 'tool_call', content: '' },
132+
{ type: 'text', content: `second ${redacted}` },
133+
],
134+
}
135+
const restored = restoreRevealedSimKeysForMessage(msg, cache)
136+
expect(restored.contentBlocks?.[0].content).toContain('"sk-sim-A"')
137+
expect(restored.contentBlocks?.[0].content).not.toContain('"sk-sim-B"')
138+
expect(restored.contentBlocks?.[2].content).toContain('"sk-sim-B"')
139+
expect(restored.contentBlocks?.[2].content).not.toContain('"sk-sim-A"')
140+
})
141+
123142
it('isolates revealed values by message id (multiple keys across messages)', () => {
124143
const cache = new Map<string, string[]>([
125144
['msg-1', ['sk-sim-A']],

apps/sim/lib/copilot/chat/sim-key-redaction.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,17 @@ export function captureRevealedSimKeys(
194194

195195
function restoreInString(
196196
content: string,
197-
revealedValues: string[]
197+
revealedValues: string[],
198+
startCursor: number
198199
): {
199200
next: string
200201
changed: boolean
202+
cursor: number
201203
} {
202204
if (!content.includes('<credential>') || revealedValues.length === 0) {
203-
return { next: content, changed: false }
205+
return { next: content, changed: false, cursor: startCursor }
204206
}
205-
let cursor = 0
207+
let cursor = startCursor
206208
let changed = false
207209
const next = content.replace(CREDENTIAL_TAG_PATTERN, (match, body: string) => {
208210
const parsed = parseCredentialBody(body)
@@ -216,7 +218,7 @@ function restoreInString(
216218
}
217219
return match
218220
})
219-
return { next, changed }
221+
return { next, changed, cursor }
220222
}
221223

222224
/**
@@ -239,11 +241,13 @@ export function restoreRevealedSimKeysForMessage(
239241
return message
240242
}
241243

242-
const restoredContent = restoreInString(message.content, revealed)
244+
const restoredContent = restoreInString(message.content, revealed, 0)
243245
let blocksChanged = false
246+
let blockCursor = 0
244247
const nextBlocks: ContentBlock[] | undefined = message.contentBlocks?.map((block) => {
245248
if (!hasRedactedSimKeyTag(block.content)) return block
246-
const restored = restoreInString(block.content as string, revealed)
249+
const restored = restoreInString(block.content as string, revealed, blockCursor)
250+
blockCursor = restored.cursor
247251
if (!restored.changed) return block
248252
blocksChanged = true
249253
return { ...block, content: restored.next }

0 commit comments

Comments
 (0)