2026-06-19 03:36:12 +08:00
import { describe , expect , it } from 'vitest'
2026-07-11 22:24:20 +08:00
import {
type NormalizeContext ,
normalizeSessionLog ,
normalizeStdout ,
scrubRequestHeaders ,
scrubSystemPrompts ,
2026-07-14 23:11:21 +08:00
scrubToolSchemas ,
2026-07-11 22:24:20 +08:00
} from '../src/normalize.ts'
2026-06-19 03:36:12 +08:00
/**
* Unit tests for the pure snapshot normalizers. Live as a *.spec.ts (runs in
2026-07-08 01:44:20 +08:00
* the default unit gate) and import the normalizers directly.
2026-06-19 03:36:12 +08:00
*/
const ctx : NormalizeContext = {
sessionIds : [ '11111111-2222-3333-4444-555555555555' ] ,
cwd : '/tmp/acp-snap-cwd-abc123' ,
}
describe ( 'normalizeStdout' , ( ) = > {
it ( 'rewrites JSON-RPC ids to a stable first-seen sequence' , ( ) = > {
const raw = [
JSON . stringify ( { jsonrpc : '2.0' , id : 42 , method : 'initialize' } ) ,
JSON . stringify ( { jsonrpc : '2.0' , id : 42 , result : { } } ) ,
JSON . stringify ( { jsonrpc : '2.0' , id : 99 , method : 'session/new' } ) ,
] . join ( '\n' )
const out = normalizeStdout ( raw , ctx )
2026-06-19 09:29:41 +08:00
expect ( out ) . toContain ( '"id":1' )
expect ( out ) . toContain ( '"id":2' )
2026-06-19 03:36:12 +08:00
expect ( out ) . not . toContain ( '42' )
expect ( out ) . not . toContain ( '99' )
} )
it ( 'scrubs the cwd and session id anywhere they appear' , ( ) = > {
const raw = JSON . stringify ( {
jsonrpc : '2.0' , method : 'session/update' ,
params : { sessionId : ctx.sessionIds [ 0 ] , cwd : ctx.cwd , note : ` at ${ ctx . cwd } /x ` } ,
} )
const out = normalizeStdout ( raw , ctx )
expect ( out ) . toContain ( '{{sessionId}}' )
expect ( out ) . toContain ( '{{cwd}}' )
expect ( out ) . not . toContain ( ctx . cwd )
expect ( out ) . not . toContain ( ctx . sessionIds [ 0 ] as string )
} )
2026-07-25 00:10:37 +08:00
it ( 'scrubs every filesystem spelling of the cwd longest-first' , ( ) = > {
const longCwd = String . raw ` C: \ Users \ runneradmin \ AppData \ Local \ Temp \ acp-snapshot `
const aliasedCtx : NormalizeContext = {
sessionIds : [ ] ,
cwd : String.raw ` C: \ Users \ RUNNER~1 \ AppData \ Local \ Temp \ acp-snapshot ` ,
cwdAliases : [
longCwd ,
String . raw ` C: \ Users \ runneradmin \ AppData \ Local \ Temp \ acp ` ,
] ,
}
const raw = JSON . stringify ( {
cwd : longCwd ,
path : ` ${ longCwd } \\ nested \\ proof.txt ` ,
} )
const frame = JSON . parse ( normalizeStdout ( raw , aliasedCtx ) ) as { cwd : string ; path : string }
expect ( frame ) . toEqual ( { cwd : '{{cwd}}' , path : '{{cwd}}/nested/proof.txt' } )
} )
2026-07-17 13:53:13 +08:00
it ( 'canonicalizes only cwd-rooted path separators' , ( ) = > {
const windowsCtx : NormalizeContext = {
sessionIds : [ ] ,
cwd : String.raw ` C: \ Users \ runner \ AppData \ Local \ Temp \ acp-snapshot ` ,
}
const raw = JSON . stringify ( {
jsonrpc : '2.0' ,
method : 'session/update' ,
params : {
path : ` ${ windowsCtx . cwd } \\ nested \\ proof.txt ` ,
regex : String.raw ` \ d+ \ w+ ` ,
command : String.raw ` printf " \\ n" ` ,
} ,
} )
const frame = JSON . parse ( normalizeStdout ( raw , windowsCtx ) ) as {
params : { path : string ; regex : string ; command : string }
}
expect ( frame . params ) . toEqual ( {
path : '{{cwd}}/nested/proof.txt' ,
regex : String.raw ` \ d+ \ w+ ` ,
command : String.raw ` printf " \\ n" ` ,
} )
} )
it ( 'canonicalizes generated relative path fields and text markers without rewriting other text' , ( ) = > {
const raw = JSON . stringify ( {
path : String.raw ` nested \ AGENTS.md ` ,
content : String.raw ` <path>. \ nested \ task.txt</path>
Additional instructions from: nested \ AGENTS.md ` ,
regex : String.raw ` \ d+ \ w+ ` ,
} )
const frame = JSON . parse ( normalizeStdout ( raw , { sessionIds : [ ] , cwd : '/unused' } ) ) as {
path : string
content : string
regex : string
}
expect ( frame ) . toEqual ( {
path : 'nested/AGENTS.md' ,
content : '<path>./nested/task.txt</path>\nAdditional instructions from: nested/AGENTS.md' ,
regex : String.raw ` \ d+ \ w+ ` ,
} )
} )
it ( 'can preserve native cwd-rooted separators for a platform golden' , ( ) = > {
const windowsCtx : NormalizeContext = { sessionIds : [ ] , cwd : String.raw ` C: \ work \ snapshot ` }
const raw = JSON . stringify ( { path : ` ${ windowsCtx . cwd } \\ nested \\ proof.txt ` } )
const frame = JSON . parse ( normalizeStdout ( raw , windowsCtx , { cwdPathMode : 'native' } ) ) as { path : string }
expect ( frame . path ) . toBe ( String . raw ` {{cwd}} \ nested \ proof.txt ` )
} )
2026-06-19 03:36:12 +08:00
it ( 'scrubs a stray UUID not in the known list' , ( ) = > {
const raw = JSON . stringify ( { jsonrpc : '2.0' , method : 'x' , params : { id : 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' } } )
expect ( normalizeStdout ( raw , ctx ) ) . toContain ( '{{sessionId}}' )
} )
it ( 'leaves notification frames without an id untouched in id-space' , ( ) = > {
const raw = JSON . stringify ( { jsonrpc : '2.0' , method : 'session/update' , params : { } } )
const out = normalizeStdout ( raw , ctx )
expect ( out ) . not . toContain ( '"id"' )
} )
2026-07-24 17:25:32 +08:00
it ( 'stabilizes only the top-level event timestamp and spill byte count in event-read text' , ( ) = > {
2026-07-24 15:09:55 +08:00
const raw = JSON . stringify ( {
jsonrpc : '2.0' ,
method : 'session/update' ,
params : {
update : {
sessionUpdate : 'tool_call_update' ,
content : [ {
type : 'content' ,
content : {
type : 'text' ,
2026-07-24 17:25:32 +08:00
text : 'Session prior — title\nTarget event seq 4:\n```json\n{\n "seq": 4,\n "time": 1784876275593,\n "data": {\n "time": 31337,\n "note": "model-visible"\n }\n}\n```\n\nAfter:\n "time": 424242,\n neighbor semantic text\n\n(Omitted 39387 bytes. Full formatted result stored at: /tmp/result.txt.)' ,
2026-07-24 15:09:55 +08:00
} ,
} ] ,
} ,
} ,
} )
const out = normalizeStdout ( raw , ctx )
expect ( out ) . toContain ( '\\"time\\": {{eventTime}}' )
2026-07-24 17:25:32 +08:00
expect ( out ) . toContain ( '\\"time\\": 31337' )
expect ( out ) . toContain ( '\\"time\\": 424242' )
2026-07-24 16:40:08 +08:00
expect ( out ) . toContain ( 'Omitted {{eventOmittedBytes}} bytes' )
2026-07-24 15:09:55 +08:00
expect ( out ) . not . toContain ( '1784876275593' )
2026-07-24 16:40:08 +08:00
expect ( out ) . not . toContain ( '39387' )
2026-07-24 15:09:55 +08:00
} )
2026-07-24 15:58:03 +08:00
it ( 'preserves event-like timestamps in unrelated output text' , ( ) = > {
const raw = JSON . stringify ( {
jsonrpc : '2.0' ,
method : 'session/update' ,
params : {
update : {
sessionUpdate : 'tool_call_update' ,
content : [ {
type : 'content' ,
content : {
type : 'text' ,
2026-07-24 16:40:08 +08:00
text : 'bash output:\n```json\n{\n "time": 1784876275593,\n "data": {}\n}\n```\n\n(Omitted 39387 bytes. Full formatted result stored at: /tmp/result.txt.)' ,
2026-07-24 15:58:03 +08:00
} ,
} ] ,
} ,
} ,
} )
const out = normalizeStdout ( raw , ctx )
expect ( out ) . toContain ( '1784876275593' )
2026-07-24 16:40:08 +08:00
expect ( out ) . toContain ( '39387' )
2026-07-24 15:58:03 +08:00
expect ( out ) . not . toContain ( '{{eventTime}}' )
2026-07-24 16:40:08 +08:00
expect ( out ) . not . toContain ( '{{eventOmittedBytes}}' )
2026-07-24 15:58:03 +08:00
} )
2026-06-19 03:36:12 +08:00
it ( 'throws on a non-JSON stdout line (the purity check)' , ( ) = > {
const raw = ` ${ JSON . stringify ( { jsonrpc : '2.0' , id : 1 } )} \ noops a log leaked \ n `
expect ( ( ) = > normalizeStdout ( raw , ctx ) ) . toThrow ( )
} )
it ( 'ignores blank lines' , ( ) = > {
const raw = ` \ n ${ JSON . stringify ( { jsonrpc : '2.0' , id : 1 , method : 'm' } )} \ n \ n `
expect ( ( ) = > normalizeStdout ( raw , ctx ) ) . not . toThrow ( )
} )
} )
describe ( 'normalizeSessionLog' , ( ) = > {
2026-06-21 11:08:10 +08:00
const header = ( over : object ) = > JSON . stringify ( { type : 'session' , version : 0 , id : 's' , createdAt : 123 , . . . over } )
2026-06-19 03:36:12 +08:00
const event = ( over : object ) = > JSON . stringify ( { type : 'turn/start' , seq : 1 , time : 999 , data : { turn : 1 } , . . . over } )
it ( 'zeroes the header createdAt' , ( ) = > {
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ` , ctx )
2026-06-19 09:29:41 +08:00
expect ( out ) . toContain ( '"createdAt":0' )
2026-06-19 03:36:12 +08:00
expect ( out ) . not . toContain ( '123' )
} )
it ( 'zeroes each event time but keeps seq' , ( ) = > {
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ${ event ( { seq : 7 , time : 999 } )} \ n ` , ctx )
2026-06-19 09:29:41 +08:00
expect ( out ) . toContain ( '"time":0' )
expect ( out ) . toContain ( '"seq":7' ) // seq is deterministic — NOT scrubbed
2026-06-19 03:36:12 +08:00
expect ( out ) . not . toContain ( '999' )
} )
it ( 'scrubs cwd and session id deep inside event data' , ( ) = > {
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : { content : [ { type : 'text' , text : ` wrote ${ ctx . cwd } /proof.txt ` } ] } ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
expect ( out ) . toContain ( '{{cwd}}' )
expect ( out ) . not . toContain ( ctx . cwd )
} )
2026-07-10 10:07:32 +08:00
it ( 'scrubs random local spill paths under the snapshot cwd' , ( ) = > {
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : {
content : [ {
type : 'text' ,
2026-07-13 11:07:27 +08:00
text : ` Full formatted result stored at: ${ ctx . cwd } /.spill/session-c22bc3f1d2af/8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it. ` ,
2026-07-10 10:07:32 +08:00
} ] ,
} ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
2026-07-13 11:07:27 +08:00
expect ( out ) . toContain ( '{{spillLocator:bash.txt}}' )
2026-07-10 10:07:32 +08:00
expect ( out ) . not . toContain ( 'session-c22bc3f1d2af' )
expect ( out ) . not . toContain ( '8a7b6c5d4e3f' )
} )
it ( 'scrubs macOS /private aliases for local spill paths' , ( ) = > {
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : {
content : [ {
type : 'text' ,
2026-07-13 11:07:27 +08:00
text : ` Full formatted result stored at: /private ${ ctx . cwd } /.spill/session-c22bc3f1d2af/8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it. ` ,
2026-07-10 10:07:32 +08:00
} ] ,
} ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
2026-07-13 11:07:27 +08:00
expect ( out ) . toContain ( '{{spillLocator:bash.txt}}' )
expect ( out ) . not . toContain ( '/private{{spillLocator' )
2026-07-10 10:07:32 +08:00
} )
2026-07-10 13:15:15 +08:00
it ( 'scrubs fixed snapshot spill paths' , ( ) = > {
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : {
content : [ {
type : 'text' ,
2026-07-13 11:07:27 +08:00
text : 'Full formatted result stored at: /tmp/dsh-acp-snapshot-spill/session-c22bc3f1d2af/8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it.' ,
2026-07-10 13:15:15 +08:00
} ] ,
} ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
2026-07-13 11:07:27 +08:00
expect ( out ) . toContain ( '{{spillLocator:bash.txt}}' )
2026-07-10 13:15:15 +08:00
expect ( out ) . not . toContain ( '/tmp/dsh-acp-snapshot-spill' )
} )
2026-07-20 18:32:21 +08:00
it ( 'scrubs scenario-owned snapshot spill paths' , ( ) = > {
2026-07-17 13:53:13 +08:00
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : {
content : [ {
type : 'text' ,
2026-07-20 18:32:21 +08:00
text : 'Full formatted result stored at: /tmp/dsh-acp-snap-012345678/session-c22bc3f1d2af/8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it.' ,
2026-07-17 13:53:13 +08:00
} ] ,
} ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
expect ( out ) . toContain ( '{{spillLocator:bash.txt}}' )
2026-07-20 18:32:21 +08:00
expect ( out ) . not . toContain ( '/tmp/dsh-acp-snap-012345678' )
} )
2026-07-21 17:14:44 +08:00
it ( 'scrubs scenario-owned snapshot spill paths with Windows drive and separators' , ( ) = > {
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : {
content : [ {
type : 'text' ,
text : String.raw ` Full formatted result stored at: C: \ t \ dsh-acp-snap-012345678 \ session-c22bc3f1d2af \ 8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it. ` ,
} ] ,
} ,
} )
const out = normalizeSessionLog ( ` ${ header ( { cwd : ctx.cwd } )} \ n ${ ev } \ n ` , ctx )
expect ( out ) . toContain ( '{{spillLocator:bash.txt}}' )
expect ( out ) . not . toContain ( 'C:\\t\\dsh-acp-snap-012345678' )
2026-07-17 13:53:13 +08:00
} )
it ( 'shares cwd-rooted path handling with stdout normalization' , ( ) = > {
const windowsCtx : NormalizeContext = { sessionIds : [ ] , cwd : String.raw ` C: \ work \ snapshot ` }
const ev = JSON . stringify ( {
type : 'tool/result' , seq : 2 , time : 5 ,
data : { path : ` ${ windowsCtx . cwd } \\ nested \\ proof.txt ` } ,
} )
expect ( normalizeSessionLog ( ` ${ header ( { cwd : windowsCtx.cwd } )} \ n ${ ev } \ n ` , windowsCtx ) )
. toContain ( '{{cwd}}/nested/proof.txt' )
expect ( normalizeSessionLog ( ` ${ header ( { cwd : windowsCtx.cwd } )} \ n ${ ev } \ n ` , windowsCtx , { cwdPathMode : 'native' } ) )
. toContain ( String . raw ` {{cwd}} \\ nested \\ proof.txt ` )
} )
2026-06-19 03:36:12 +08:00
it ( 'scrubs the session id in the header' , ( ) = > {
const out = normalizeSessionLog ( ` ${ header ( { id : ctx.sessionIds [ 0 ] } )} \ n ` , ctx )
expect ( out ) . toContain ( '{{sessionId}}' )
} )
2026-07-01 04:22:00 +08:00
it ( 'zeroes a hook/result durationMs (run-to-run noise) but keeps its decision' , ( ) = > {
const ev = JSON . stringify ( {
type : 'hook/result' , seq : 2 , time : 5 ,
data : { turn : 1 , point : 'UserPromptSubmit' , handlerId : 'h' , decision : 'block' , exitCode : 2 , durationMs : 37 } ,
} )
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ${ ev } \ n ` , ctx )
expect ( out ) . toContain ( '"durationMs":0' )
expect ( out ) . not . toContain ( '37' )
expect ( out ) . toContain ( '"decision":"block"' ) // the decision is the behavior — kept
} )
2026-07-15 21:26:36 +08:00
it ( 'zeroes a packed chunk row\'s time0 and dt gaps but keeps seq0 and payload' , ( ) = > {
const row = JSON . stringify ( {
type : 'text-chunks' , seq0 : 7 , time0 : 999 ,
data : { turn : 1 , step : 1 , index : 0 , dt : [ 212 , 27 , 0 ] , texts : [ 'a' , 'b' , 'c' , 'd' ] } ,
} )
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ${ row } \ n ` , ctx )
expect ( out ) . toContain ( '"time0":0' )
expect ( out ) . toContain ( '"dt":[0,0,0]' )
expect ( out ) . toContain ( '"seq0":7' ) // seq0 is deterministic, like seq — NOT scrubbed
expect ( out ) . toContain ( '"texts":["a","b","c","d"]' )
expect ( out ) . not . toContain ( '999' )
expect ( out ) . not . toContain ( '212' )
} )
it ( 'zeroes time0 even when a malformed row carries no dt array' , ( ) = > {
const row = JSON . stringify ( { type : 'text-chunks' , seq0 : 1 , time0 : 999 , data : 'not-an-object' } )
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ${ row } \ n ` , ctx )
expect ( out ) . toContain ( '"time0":0' )
expect ( out ) . not . toContain ( '999' )
} )
2026-07-01 04:22:00 +08:00
it ( 'leaves a non-hook event durationMs untouched (only hook/result is scrubbed)' , ( ) = > {
const ev = JSON . stringify ( { type : 'tool/result' , seq : 2 , time : 5 , data : { durationMs : 88 } } )
const out = normalizeSessionLog ( ` ${ header ( { } )} \ n ${ ev } \ n ` , ctx )
expect ( out ) . toContain ( '"durationMs":88' )
} )
2026-07-08 02:05:06 +08:00
it ( 'tolerates records missing the volatile fields it would zero' , ( ) = > {
const bareHeader = JSON . stringify ( { type : 'session' , id : 's' } )
const timeless = JSON . stringify ( { type : 'note' , seq : 1 } )
const bareHook = JSON . stringify ( { type : 'hook/result' , seq : 2 , time : 5 , data : { decision : 'allow' } } )
const nullDataHook = JSON . stringify ( { type : 'hook/result' , seq : 3 , time : 6 , data : null } )
const out = normalizeSessionLog ( ` ${ bareHeader } \ n ${ timeless } \ n ${ bareHook } \ n ${ nullDataHook } \ n ` , ctx )
expect ( out ) . toContain ( '"type":"note","seq":1' )
expect ( out ) . toContain ( '"decision":"allow"' )
expect ( out ) . not . toContain ( 'durationMs' )
} )
2026-06-19 03:36:12 +08:00
} )
2026-07-06 23:41:20 +08:00
describe ( 'scrubRequestHeaders' , ( ) = > {
const headerLine = JSON . stringify ( { type : 'session' , version : 0 , id : 's' , createdAt : 1 , cwd : '/w' } )
const headerEvent = ( header : object ) = >
JSON . stringify ( { type : 'request/header' , seq : 3 , time : 9 , data : { header , reason : 'initial' } } )
it ( 'replaces header system and tools with tokens, keeping config and reason' , ( ) = > {
const ev = headerEvent ( {
config : { model : 'm' } ,
system : 'You are an agent.\nBe brief.' ,
tools : [ { name : 'read' , description : 'Read a file.' , parameters : { type : 'object' } } ] ,
} )
const out = scrubRequestHeaders ( ` ${ headerLine } \ n ${ ev } \ n ` )
expect ( out ) . toContain ( '"system":"{{system}}"' )
expect ( out ) . toContain ( '"tools":"{{tools}}"' )
expect ( out ) . toContain ( '"config":{"model":"m"}' )
expect ( out ) . toContain ( '"reason":"initial"' )
expect ( out ) . not . toContain ( 'You are an agent' )
expect ( out ) . not . toContain ( 'Read a file' )
} )
it ( 'keeps an absent system/tools absent (presence is behavior)' , ( ) = > {
const out = scrubRequestHeaders ( ` ${ headerLine } \ n ${ headerEvent ( { config : { model : 'm' } })} \ n ` )
expect ( out ) . not . toContain ( '{{system}}' )
expect ( out ) . not . toContain ( '{{tools}}' )
} )
2026-07-08 02:05:06 +08:00
it ( 'scrubs a header carrying only one of system/tools, leaving the other absent' , ( ) = > {
const systemOnly = scrubRequestHeaders ( ` ${ headerLine } \ n ${ headerEvent ( { system : 'secret prompt' } )} \ n ` )
expect ( systemOnly ) . toContain ( '"system":"{{system}}"' )
expect ( systemOnly ) . not . toContain ( '{{tools}}' )
const toolsOnly = scrubRequestHeaders ( ` ${ headerLine } \ n ${ headerEvent ( { tools : [ { name : 't' } ] })} \ n ` )
expect ( toolsOnly ) . toContain ( '"tools":"{{tools}}"' )
expect ( toolsOnly ) . not . toContain ( '{{system}}' )
} )
2026-07-08 16:16:08 +08:00
it ( 'scrubs the header session prefix to one token per message, keeping the count' , ( ) = > {
const ev = headerEvent ( {
config : { model : 'm' } ,
messagePrefix : [
{ role : 'user' , content : [ { type : 'text' , text : 'workspace AGENTS digest' } ] } ,
{ role : 'user' , content : [ { type : 'text' , text : 'skills catalog' } ] } ,
] ,
} )
const out = scrubRequestHeaders ( ` ${ headerLine } \ n ${ ev } \ n ` )
expect ( out ) . toContain ( '"messagePrefix":["{{messagePrefix}}","{{messagePrefix}}"]' )
expect ( out ) . not . toContain ( 'AGENTS digest' )
expect ( out ) . not . toContain ( 'skills catalog' )
// Absence stays absent — a prefix-less header gains no token…
expect ( scrubRequestHeaders ( ` ${ headerLine } \ n ${ headerEvent ( { system : 's' } )} \ n ` ) ) . not . toContain ( '{{messagePrefix}}' )
// …and a non-array shape passes through untouched.
const odd = JSON . stringify ( { type : 'request/header' , seq : 4 , time : 9 , data : { header : { config : { model : 'm' } , messagePrefix : 'weird' } , reason : 'initial' } } )
expect ( scrubRequestHeaders ( ` ${ headerLine } \ n ${ odd } \ n ` ) ) . toContain ( '"messagePrefix":"weird"' )
} )
2026-07-13 23:56:10 +08:00
it ( 'leaves malformed headers with no scrubbable payload byte-identical' , ( ) = > {
2026-07-08 02:05:06 +08:00
const headerless = JSON . stringify ( { type : 'request/header' , seq : 10 , time : 9 , data : { reason : 'initial' } } )
const nullData = JSON . stringify ( { type : 'request/header' , seq : 11 , time : 9 , data : null } )
2026-07-13 23:56:10 +08:00
const raw = ` ${ headerLine } \ n ${ headerless } \ n ${ nullData } \ n `
2026-07-08 02:05:06 +08:00
expect ( scrubRequestHeaders ( raw ) ) . toBe ( raw )
} )
2026-07-06 23:41:20 +08:00
it ( 'passes every other line through byte-for-byte and is idempotent' , ( ) = > {
const other = JSON . stringify ( { type : 'assistant/chunk' , seq : 4 , time : 9 , data : { turn : 1 , step : 1 , chunk : { type : 'text-delta' , index : 0 , text : 'hi' } } } )
2026-07-13 23:56:10 +08:00
const raw = ` ${ headerLine } \ n ${ headerEvent ( { config : { model : 'm' } , system: 's', tools: [] })} \ n ${ other } \ n `
2026-07-06 23:41:20 +08:00
const once = scrubRequestHeaders ( raw )
expect ( once . split ( '\n' ) [ 0 ] ) . toBe ( headerLine )
2026-07-13 23:56:10 +08:00
expect ( once . split ( '\n' ) [ 2 ] ) . toBe ( other )
2026-07-06 23:41:20 +08:00
expect ( scrubRequestHeaders ( once ) ) . toBe ( once )
} )
} )
2026-07-11 22:24:20 +08:00
describe ( 'scrubSystemPrompts' , ( ) = > {
it ( 'scrubs only system prompt payloads while keeping tools and prefixes verbatim' , ( ) = > {
const header = JSON . stringify ( {
type : 'request/header' , seq : 1 , time : 2 ,
data : {
header : {
system : 'full prompt' ,
tools : [ { name : 'read' , description : 'full schema' } ] ,
messagePrefix : [ { role : 'user' , content : [ { type : 'text' , text : 'full prefix' } ] } ] ,
} ,
reason : 'initial' ,
} ,
} )
2026-07-13 23:56:10 +08:00
const changed = JSON . stringify ( {
type : 'request/header' , seq : 2 , time : 3 ,
2026-07-11 22:24:20 +08:00
data : {
2026-07-13 23:56:10 +08:00
header : {
system : 'new prompt' ,
tools : [ { name : 'read' , description : 'changed schema' } ] ,
messagePrefix : [ { role : 'user' , content : [ { type : 'text' , text : 'changed prefix' } ] } ] ,
} ,
reason : 'change' ,
2026-07-11 22:24:20 +08:00
} ,
} )
const toolsOnly = JSON . stringify ( {
type : 'request/header' , seq : 3 , time : 4 ,
data : { header : { tools : [ { name : 'read' , description : 'schema only' } ] } , reason : 'resume' } ,
} )
2026-07-13 23:56:10 +08:00
const out = scrubSystemPrompts ( ` ${ header } \ n ${ changed } \ n ${ toolsOnly } \ n ` )
2026-07-11 22:24:20 +08:00
expect ( out ) . toContain ( '"system":"{{system}}"' )
expect ( out ) . not . toContain ( 'full prompt' )
2026-07-13 23:56:10 +08:00
expect ( out ) . not . toContain ( 'new prompt' )
2026-07-11 22:24:20 +08:00
expect ( out ) . toContain ( 'full schema' )
expect ( out ) . toContain ( 'full prefix' )
expect ( out ) . toContain ( 'changed schema' )
expect ( out ) . toContain ( 'changed prefix' )
expect ( out . split ( '\n' ) [ 2 ] ) . toBe ( toolsOnly )
expect ( scrubSystemPrompts ( out ) ) . toBe ( out )
} )
} )
2026-07-14 23:11:21 +08:00
describe ( 'scrubToolSchemas' , ( ) = > {
it ( 'scrubs only tool-schema payloads while keeping prompts and prefixes verbatim' , ( ) = > {
const header = JSON . stringify ( {
type : 'request/header' , seq : 1 , time : 2 ,
data : {
header : {
system : 'full prompt' ,
tools : [ { name : 'read' , description : 'full schema' , parameters : { type : 'object' } } ] ,
messagePrefix : [ { role : 'user' , content : [ { type : 'text' , text : 'full prefix' } ] } ] ,
} ,
reason : 'initial' ,
} ,
} )
2026-07-15 15:53:56 +08:00
const changed = JSON . stringify ( {
type : 'request/header' , seq : 2 , time : 3 ,
2026-07-14 23:11:21 +08:00
data : {
2026-07-15 15:53:56 +08:00
header : {
system : 'new prompt' ,
tools : [ { name : 'grep' , description : 'new schema' } ] ,
messagePrefix : [ { role : 'user' , content : [ { type : 'text' , text : 'changed prefix' } ] } ] ,
} ,
reason : 'change' ,
2026-07-14 23:11:21 +08:00
} ,
} )
const systemOnly = JSON . stringify ( {
type : 'request/header' , seq : 3 , time : 4 ,
data : { header : { system : 'prompt only' } , reason : 'resume' } ,
} )
2026-07-15 15:53:56 +08:00
const out = scrubToolSchemas ( ` ${ header } \ n ${ changed } \ n ${ systemOnly } \ n ` )
expect ( out . match ( /"tools":"{{tools}}"/g ) ) . toHaveLength ( 2 )
2026-07-14 23:11:21 +08:00
expect ( out ) . not . toContain ( 'full schema' )
expect ( out ) . not . toContain ( 'new schema' )
expect ( out ) . toContain ( 'full prompt' )
2026-07-15 15:53:56 +08:00
expect ( out ) . toContain ( 'new prompt' )
2026-07-14 23:11:21 +08:00
expect ( out ) . toContain ( 'full prefix' )
expect ( out ) . toContain ( 'changed prefix' )
expect ( out . split ( '\n' ) [ 2 ] ) . toBe ( systemOnly )
expect ( scrubToolSchemas ( out ) ) . toBe ( out )
} )
} )