Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762const std = @import("std");const image_data = @import("../images/image_data.zig");const session = @import("session.zig");const session_usage = @import("session_usage.zig");const session_permission_state = @import("../permissions/session_permission_state.zig");const mem_utils = @import("../shared/mem_utils.zig");const types = @import("../shared/types.zig");const captured_command = @import("../tooling/captured_command.zig");const model_provider = @import("../config/model_provider.zig");const context_limits = @import("../config/context_limits.zig");const credential_authority = @import("../auth/credential_authority.zig");
const Allocator = std.mem.Allocator;const Sha256 = std.crypto.hash.sha2.Sha256;
pub const DurableSessionPreferences = struct { provider: model_provider.ProviderId = .gateway, model: []u8, effort: types.ReasoningEffort, fast_mode: bool,
pub fn deinit(self: *DurableSessionPreferences, alloc: Allocator) void { alloc.free(self.model); self.* = undefined; }
pub fn dupe(self: DurableSessionPreferences, alloc: Allocator) !DurableSessionPreferences { return .{ .provider = self.provider, .model = try alloc.dupe(u8, self.model), .effort = self.effort, .fast_mode = self.fast_mode, }; }};
/// Decodes persisted preferences; the caller owns the returned model.pub fn parse_preferences(alloc: Allocator, value: std.json.Value) !DurableSessionPreferences { const raw = try requireObject(value); const legacy = raw.contains("connection_id") or raw.contains("model_id"); const object = if (legacy) try exactObject(value, &.{ "connection_id", "model_id", "effort", "fast_mode" }) else if (raw.contains("provider")) try exactObject(value, &.{ "provider", "model", "effort", "fast_mode" }) else try exactObject(value, &.{ "model", "effort", "fast_mode" }); const provider: model_provider.ProviderId = if (legacy) blk: { if (!std.mem.eql(u8, try requireString(object, "connection_id"), "vercel")) return error.InvalidDurableField; break :blk .gateway; } else if (object.contains("provider")) model_provider.parse_saved(object.get("provider").?) catch return error.InvalidDurableField else .gateway; const model = try requireString(object, if (legacy) "model_id" else "model"); try validateModel(model); const effort = types.ReasoningEffort.parse(try requireString(object, "effort")) orelse return error.InvalidDurableField; const fast_mode = try requireBool(object, "fast_mode"); return .{ .provider = provider, .model = try alloc.dupe(u8, model), .effort = effort, .fast_mode = fast_mode };}
test "legacy connection preferences decode through the shared state codec" { const alloc = std.testing.allocator; const state_json = "{\"id\":\"legacy\",\"origin_workspace_root\":\"/workspace\",\"workspace_root\":\"/workspace\",\"created_at_ms\":1,\"updated_at_ms\":2,\"conversation_language\":\"en\",\"preferences\":{\"connection_id\":\"vercel\",\"model_id\":\"test/model\",\"effort\":\"high\",\"fast_mode\":true},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}"; try std.testing.checkAllAllocationFailures(alloc, struct { fn check(a: Allocator, bytes: []const u8) !void { var source = std.Io.Reader.fixed(bytes); var state = try decodeState(a, &source, .{}); defer state.deinit(a); try std.testing.expectEqual(model_provider.ProviderId.gateway, state.preferences.provider); try std.testing.expectEqualStrings("test/model", state.preferences.model); try std.testing.expectEqualStrings("high", state.preferences.effort.label()); try std.testing.expect(state.preferences.fast_mode); } }.check, .{state_json});}
test "legacy connection preferences reject unknown identities and mixed fields" { const alloc = std.testing.allocator; for ([_][]const u8{ "{\"connection_id\":\"custom\",\"model_id\":\"test\",\"effort\":\"auto\",\"fast_mode\":false}", "{\"connection_id\":\"vercel\",\"model_id\":\"test\",\"model\":\"other\",\"effort\":\"auto\",\"fast_mode\":false}", "{\"connection_id\":\"vercel\",\"model_id\":\"test\",\"provider\":\"codex\",\"effort\":\"auto\",\"fast_mode\":false}", "{\"connection_id\":\"vercel\",\"model_id\":\"\",\"effort\":\"auto\",\"fast_mode\":false}", }) |bytes| { var parsed = try std.json.parseFromSlice(std.json.Value, alloc, bytes, .{}); defer parsed.deinit(); if (parse_preferences(alloc, parsed.value)) |value| { var owned = value; owned.deinit(alloc); return error.TestUnexpectedResult; } else |err| try std.testing.expect(err == error.InvalidSessionFormat or err == error.InvalidDurableField); }}
pub const RecoveryToolState = enum { none, proven_unexecuted, confirmed, uncertain,};
pub const TurnAuthority = struct { provider: model_provider.ProviderId, model: []u8, credential_source: ?types.CredentialSource = null, credential_identity: ?credential_authority.Identity = null,
pub fn deinit(self: *TurnAuthority, alloc: Allocator) void { alloc.free(self.model); self.* = undefined; }
pub fn dupe(self: TurnAuthority, alloc: Allocator) !TurnAuthority { return .{ .provider = self.provider, .model = try alloc.dupe(u8, self.model), .credential_source = self.credential_source, .credential_identity = self.credential_identity, }; }};
pub const RecoveryCheckpoint = struct { version: u8 = 2, disposition: enum { continuable, history_only } = .continuable, turn_id: u64, user: session.UserTurn, assistant_source: []u8, execution: session.ExecutionMemory = .{}, cause: types.ModelRecoveryCause, action: types.ModelRecoveryAction, tool_state: RecoveryToolState = .none, authority: TurnAuthority, requested_fast_mode: bool, fast_mode: bool, max_provider_attempts: usize, consumed_provider_attempts: usize, outstanding_reservation: bool = false,
/// Borrows the checkpoint's exact user and uncommitted response suffix. pub fn interruptedTurn(self: RecoveryCheckpoint) types.HistoryTurn { return .{ .interrupted = .{ .user = self.user, .assistant = if (self.assistant_source.len > 0) self.assistant_source else null, .execution = self.execution, .terminal_reason = .failed, } }; }
pub fn deinit(self: *RecoveryCheckpoint, alloc: Allocator) void { session.freeUserTurn(alloc, self.user); alloc.free(self.assistant_source); session.freeExecutionMemory(alloc, self.execution); self.authority.deinit(alloc); self.* = undefined; }
pub fn dupe(self: RecoveryCheckpoint, alloc: Allocator) !RecoveryCheckpoint { const user = try session.dupeUserTurn(alloc, self.user); errdefer session.freeUserTurn(alloc, user); const assistant_source = try alloc.dupe(u8, self.assistant_source); errdefer alloc.free(assistant_source); const execution = try types.dupeExecutionMemory(alloc, self.execution); errdefer session.freeExecutionMemory(alloc, execution); const authority = try self.authority.dupe(alloc); return .{ .version = self.version, .disposition = self.disposition, .turn_id = self.turn_id, .user = user, .assistant_source = assistant_source, .execution = execution, .cause = self.cause, .action = self.action, .tool_state = self.tool_state, .authority = authority, .requested_fast_mode = self.requested_fast_mode, .fast_mode = self.fast_mode, .max_provider_attempts = self.max_provider_attempts, .consumed_provider_attempts = self.consumed_provider_attempts, .outstanding_reservation = self.outstanding_reservation, }; }};
pub const DurableSessionState = struct { id: []u8, origin_workspace_root: []u8, workspace_root: []u8, created_at_ms: i64, updated_at_ms: i64, conversation_language: session.ConversationLanguage, preferences: DurableSessionPreferences, history: []session.HistoryTurn, context_history_start: usize = 0, total_input_tokens: u64, total_output_tokens: u64, permission_state: session_permission_state.State = .{}, /// Last ordinary history commit correlated to durable subagent work. This /// control-only marker is not included in model history. last_subagent_work_id: ?[]u8 = null, /// True only for a parent-owned subagent session. This durable identity /// keeps the session private even if owner sidecar publication is /// interrupted immediately after creation. subagent_child: bool = false, /// Null only for sessions written before durable usage accounting. usage: ?session_usage.Snapshot = null, /// Active control state. It is never projected into model history and a /// restored checkpoint requires explicit host authority before any send. recovery_checkpoint: ?RecoveryCheckpoint = null,
pub fn deinit(self: *DurableSessionState, alloc: Allocator) void { alloc.free(self.id); alloc.free(self.origin_workspace_root); alloc.free(self.workspace_root); self.preferences.deinit(alloc); session.freeHistoryTurnSlice(alloc, self.history); self.permission_state.deinit(alloc); if (self.last_subagent_work_id) |id| alloc.free(id); if (self.usage) |*usage| usage.deinit(alloc); if (self.recovery_checkpoint) |*checkpoint| checkpoint.deinit(alloc); self.* = undefined; }
/// Retains legacy recovery evidence without granting permission to replay its route. pub fn archive_legacy_recovery(self: *DurableSessionState, alloc: Allocator) !bool { const checkpoint = if (self.recovery_checkpoint) |*value| value else return false; if (checkpoint.disposition != .history_only) return false; const turn = try session.dupeHistoryTurn(alloc, checkpoint.interruptedTurn()); errdefer session.freeHistoryTurn(alloc, turn); const len = try std.math.add(usize, self.history.len, 1); self.history = try alloc.realloc(self.history, len); self.history[len - 1] = turn; checkpoint.deinit(alloc); self.recovery_checkpoint = null; return true; }
pub fn dupe(self: DurableSessionState, alloc: Allocator) !DurableSessionState { const id = try alloc.dupe(u8, self.id); errdefer alloc.free(id); const origin_workspace_root = try alloc.dupe(u8, self.origin_workspace_root); errdefer alloc.free(origin_workspace_root); const workspace_root = try alloc.dupe(u8, self.workspace_root); errdefer alloc.free(workspace_root); const preferences = try self.preferences.dupe(alloc); errdefer { var owned_preferences = preferences; owned_preferences.deinit(alloc); } const history = try dupeHistory(alloc, self.history); errdefer session.freeHistoryTurnSlice(alloc, history); const last_subagent_work_id = if (self.last_subagent_work_id) |work_id| try alloc.dupe(u8, work_id) else null; errdefer if (last_subagent_work_id) |work_id| alloc.free(work_id); var usage = if (self.usage) |snapshot| try session_usage.dupeSnapshotOwned(alloc, snapshot) else null; errdefer if (usage) |*snapshot| snapshot.deinit(alloc); const recovery_checkpoint = if (self.recovery_checkpoint) |checkpoint| try checkpoint.dupe(alloc) else null; const permission_state = try session_permission_state.dupe( alloc, self.permission_state, );
return .{ .id = id, .origin_workspace_root = origin_workspace_root, .workspace_root = workspace_root, .created_at_ms = self.created_at_ms, .updated_at_ms = self.updated_at_ms, .conversation_language = self.conversation_language, .preferences = preferences, .history = history, .context_history_start = self.context_history_start, .total_input_tokens = self.total_input_tokens, .total_output_tokens = self.total_output_tokens, .permission_state = permission_state, .last_subagent_work_id = last_subagent_work_id, .subagent_child = self.subagent_child, .usage = usage, .recovery_checkpoint = recovery_checkpoint, }; }};
pub const session_metadata_schema_version: u8 = 4;pub const max_session_metadata_bytes: usize = 64 * 1024;pub const max_session_title_bytes: usize = 240;
pub const SessionMetadata = struct { schema_version: u8 = session_metadata_schema_version, id: []const u8, origin_workspace_root: []const u8, workspace_root: []const u8, created_at_ms: i64, updated_at_ms: i64, conversation_language: []const u8, provider: model_provider.ProviderId, model: []const u8, effort: []const u8, fast_mode: bool, title: ?[]const u8 = null, subagent_child: bool = false,};
pub const DecodedSessionMetadata = std.json.Parsed(SessionMetadata);pub const max_permission_state_bytes: usize = 1024 * 1024;pub const max_recovery_checkpoint_bytes: usize = context_limits.emergency_ceiling_bytes;
pub fn encodeSessionMetadata( alloc: Allocator, metadata: SessionMetadata,) ![]u8 { try validateSessionMetadata(metadata); var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); try std.json.Stringify.value(metadata, .{}, &out.writer); if (out.written().len == 0 or out.written().len > max_session_metadata_bytes) { return error.SessionMetadataTooLarge; } return out.toOwnedSlice() catch return error.OutOfMemory;}
pub fn decodeSessionMetadata( alloc: Allocator, bytes: []const u8,) !DecodedSessionMetadata { if (bytes.len == 0 or bytes.len > max_session_metadata_bytes) { return error.SessionMetadataTooLarge; } var parsed = std.json.parseFromSlice(SessionMetadata, alloc, bytes, .{ .allocate = .alloc_always, .ignore_unknown_fields = false, .max_value_len = max_session_metadata_bytes, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidSessionMetadata, }; errdefer parsed.deinit(); validateSessionMetadata(parsed.value) catch return error.InvalidSessionMetadata; return parsed;}
pub fn encodePermissionState( alloc: Allocator, state: session_permission_state.State,) ![]u8 { try session_permission_state.validate(state); var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); try writePermissionState(&out.writer, state); if (out.written().len == 0 or out.written().len > max_permission_state_bytes) { return error.PermissionStateTooLarge; } return out.toOwnedSlice() catch return error.OutOfMemory;}
pub fn decodePermissionState( alloc: Allocator, bytes: []const u8,) !session_permission_state.State { if (bytes.len == 0 or bytes.len > max_permission_state_bytes) { return error.PermissionStateTooLarge; } var parsed = std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .max_value_len = max_permission_state_bytes, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidPermissionState, }; defer parsed.deinit(); return parsePermissionState(alloc, parsed.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.InvalidPermissionState, };}
pub fn encodeRecoveryCheckpoint( alloc: Allocator, checkpoint: RecoveryCheckpoint,) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); try writeRecoveryCheckpoint(&out.writer, checkpoint); if (out.written().len == 0 or out.written().len > max_recovery_checkpoint_bytes) { return error.RecoveryCheckpointTooLarge; } return out.toOwnedSlice() catch return error.OutOfMemory;}
fn validateSessionMetadata(metadata: SessionMetadata) !void { if (metadata.schema_version != session_metadata_schema_version) { return error.UnsupportedSessionSchema; } try validateSessionId(metadata.id); try validateWorkspaceRoot(metadata.origin_workspace_root); try validateWorkspaceRoot(metadata.workspace_root); if (metadata.created_at_ms < 0 or metadata.updated_at_ms < metadata.created_at_ms) { return error.InvalidSessionMetadata; } try validateConversationLanguageBytes(metadata.conversation_language); if (metadata.provider == .configured and metadata.provider.configured.binding == null) return error.InvalidSessionMetadata; try validateModel(metadata.model); if (types.ReasoningEffort.parse(metadata.effort) == null) { return error.InvalidSessionMetadata; } if (metadata.title) |title| { if (title.len == 0 or title.len > max_session_title_bytes or !std.unicode.utf8ValidateSlice(title)) { return error.InvalidSessionMetadata; } }}
pub const EncodeSummary = struct { encoded_bytes: u64, sha256: [Sha256.digest_length]u8,};
pub const DecodeLimits = struct { max_history_turns: usize = std.math.maxInt(usize), max_value_bytes: usize = std.math.maxInt(usize) / 2,};
const CountingSha256 = struct { sha256: Sha256 = Sha256.init(.{}), bytes: u64 = 0,
pub fn update(self: *CountingSha256, bytes: []const u8) void { self.sha256.update(bytes); self.bytes += bytes.len; }};
pub fn writeDurableBytes(writer: *std.Io.Writer, bytes: []const u8) !void { if (std.unicode.utf8ValidateSlice(bytes)) { return writeJsonString(writer, bytes); }
try writer.writeAll("{\"encoding\":\"base64\",\"data\":\""); try std.base64.standard.Encoder.encodeWriter(writer, bytes); try writer.writeAll("\"}");}
pub fn parseDurableBytes(alloc: Allocator, value: std.json.Value) ![]u8 { switch (value) { .string => |text| return try alloc.dupe(u8, text), .object => |object| { if (object.count() != 2) return error.InvalidDurableBytes; var iterator = object.iterator(); const encoding_entry = iterator.next() orelse return error.InvalidDurableBytes; const data_entry = iterator.next() orelse return error.InvalidDurableBytes; if (!std.mem.eql(u8, encoding_entry.key_ptr.*, "encoding") or !std.mem.eql(u8, data_entry.key_ptr.*, "data")) { return error.InvalidDurableBytes; } if (encoding_entry.value_ptr.* != .string or !std.mem.eql(u8, encoding_entry.value_ptr.string, "base64") or data_entry.value_ptr.* != .string) { return error.InvalidDurableBytes; }
const encoded = data_entry.value_ptr.string; const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(encoded) catch return error.InvalidDurableBytes; const decoded = try alloc.alloc(u8, decoded_len); errdefer alloc.free(decoded); std.base64.standard.Decoder.decode(decoded, encoded) catch return error.InvalidDurableBytes; if (std.unicode.utf8ValidateSlice(decoded)) return error.InvalidDurableBytes;
const canonical_len = std.base64.standard.Encoder.calcSize(decoded.len); const canonical = try alloc.alloc(u8, canonical_len); defer alloc.free(canonical); const written = std.base64.standard.Encoder.encode(canonical, decoded); if (!std.mem.eql(u8, written, encoded)) return error.InvalidDurableBytes; return decoded; }, else => return error.InvalidDurableBytes, }}
pub fn encodeState(state: DurableSessionState, writer: *std.Io.Writer) !EncodeSummary { try validateState(state);
var hash_buffer: [4096]u8 = undefined; var hashed = std.Io.Writer.hashed(writer, CountingSha256{}, &hash_buffer); try writeState(&hashed.writer, state); try hashed.writer.flush();
return .{ .encoded_bytes = hashed.hasher.bytes, .sha256 = hashed.hasher.sha256.finalResult(), };}
pub fn decodeState(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits) !DurableSessionState { return decodeStateWithUsageContract(alloc, source, limits, false);}
/// Reads an old persisted state without requiring modern cache-token totals./// Caller owns the state; all non-usage validation remains unchanged.pub fn decodeLegacyState(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits) !DurableSessionState { return decodeStateWithUsageContract(alloc, source, limits, true);}
fn decodeStateWithUsageContract(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits, legacy_usage: bool) !DurableSessionState { return decodeStateImpl(alloc, source, limits, legacy_usage) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.InvalidDurableField => return error.InvalidDurableField, error.InvalidDurableBytes => return error.InvalidDurableBytes, else => return error.InvalidSessionFormat, };}
pub fn writeHistoryTurn(writer: *std.Io.Writer, turn: session.HistoryTurn) !void { switch (turn) { .compacted_summary => |entry| { try writer.writeAll("{\"kind\":\"compacted_summary\",\"summary\":"); try writeDurableBytes(writer, entry.summary); try writer.print(",\"removed_turn_count\":{d},\"compaction_count\":{d}", .{ entry.removed_turn_count, entry.compaction_count, }); try writer.writeByte('}'); }, .assistant => |entry| { try writer.writeAll("{\"kind\":\"assistant\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeDurableBytes(writer, entry.assistant); try writer.writeAll(",\"provider_replay\":"); try std.json.Stringify.value(entry.provider_replay, .{}, writer); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); try writer.writeByte('}'); }, .interrupted => |entry| { try writer.writeAll("{\"kind\":\"interrupted\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeOptionalDurableBytes(writer, entry.assistant); try writer.writeAll(",\"tool_call\":"); if (entry.tool_call) |tool_call| { try writeToolCall(writer, tool_call); } else { try writer.writeAll("null"); } try writer.writeAll(",\"completed_tool_names\":["); for (entry.completed_tool_names, 0..) |name, i| { if (i > 0) try writer.writeByte(','); try writeDurableBytes(writer, name); } try writer.writeByte(']'); try writer.writeAll(",\"terminal_reason\":"); try writeJsonString(writer, @tagName(entry.terminal_reason)); if (entry.cancellation_origin == .compaction) { try writer.writeAll(",\"cancellation_origin\":\"compaction\""); } if (hasDurableExecutionMemory(entry.execution)) { try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); } if (entry.cancelled_command) |presentation| { try writer.writeAll(",\"cancelled_command\":"); try writeCancelledCommandPresentation(writer, presentation); } try writer.writeByte('}'); }, }}
fn formatLegacyBackgroundAssistant( alloc: Allocator, assistant: ?[]const u8, log_path: []const u8, url: ?[]const u8,) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); if (assistant) |text| { if (text.len != 0) { try out.writer.writeAll(text); if (!std.mem.endsWith(u8, text, "\n")) try out.writer.writeByte('\n'); } } try out.writer.writeAll( "[Historical command record: fx no longer owns or controls this process", ); if (log_path.len != 0) try out.writer.print("; former log={s}", .{log_path}); if (url) |value| try out.writer.print("; recorded url={s}", .{value}); try out.writer.writeAll("]"); return out.toOwnedSlice();}
pub fn parseHistoryTurn(alloc: Allocator, value: std.json.Value) !session.HistoryTurn { const kind = try requireString(try requireObject(value), "kind"); if (std.mem.eql(u8, kind, "compacted_summary")) { const raw_object = try requireObject(value); const has_root_user_messages = raw_object.get("root_user_messages") != null; const has_completeness = raw_object.get("root_user_messages_complete") != null; const has_permission_feedback = raw_object.get("permission_feedback") != null; const has_feedback_completeness = raw_object.get("permission_feedback_complete") != null; if (has_permission_feedback != has_feedback_completeness) { return error.InvalidSessionFormat; } const object = if (has_feedback_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete", "permission_feedback", "permission_feedback_complete" }) else if (has_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete" }) else if (has_root_user_messages) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages" }) else try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count" }); const summary = try parseRequiredDurableBytes(alloc, object, "summary"); errdefer mem_utils.free(alloc, summary); const root_user_messages: [][]u8 = if (has_root_user_messages) try parseDurableBytesArray( alloc, object.get("root_user_messages") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freeCompletedToolNames(alloc, root_user_messages); const permission_feedback: [][]u8 = if (has_permission_feedback) try parseDurableBytesArray( alloc, object.get("permission_feedback") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freePermissionFeedback(alloc, permission_feedback); const removed_turn_count = try requireUsize(object, "removed_turn_count"); return .{ .compacted_summary = .{ .summary = summary, .removed_turn_count = removed_turn_count, .compaction_count = try requireUsize(object, "compaction_count"), .root_user_messages = root_user_messages, .root_user_messages_complete = if (has_completeness) try requireBool(object, "root_user_messages_complete") else has_root_user_messages or removed_turn_count == 0, .permission_feedback = permission_feedback, .permission_feedback_complete = if (has_feedback_completeness) try requireBool(object, "permission_feedback_complete") else removed_turn_count == 0, } }; } if (std.mem.eql(u8, kind, "assistant")) { const shape = try exactVariantObject(value, &.{ "kind", "user", "assistant", "execution" }, &.{ "kind", "user", "assistant", "execution", "provider_replay" }); const object = shape.object; const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseRequiredDurableBytes(alloc, object, "assistant"); errdefer mem_utils.free(alloc, assistant); const execution = try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat); errdefer session.freeExecutionMemory(alloc, execution); const replay = try parseProviderReplay(alloc, object.get("provider_replay") orelse .null); return .{ .assistant = .{ .user = user, .assistant = assistant, .execution = execution, .provider_replay = replay, } }; } if (std.mem.eql(u8, kind, "background_command")) { const shape = try exactVariantObject( value, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id" }, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id", "assistant", "execution" }, ); const object = shape.object; const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const legacy_assistant = if (shape.extended) try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat) else null; defer if (legacy_assistant) |owned| mem_utils.free(alloc, owned); const execution = if (shape.extended) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const log_path = try parseRequiredDurableBytes(alloc, object, "log_path"); defer mem_utils.free(alloc, log_path); const url = try parseOptionalDurableBytes(alloc, object.get("url") orelse return error.InvalidSessionFormat); defer if (url) |owned| mem_utils.free(alloc, owned); _ = try requireBool(object, "expect_url"); _ = try parseOptionalIdentifier(object.get("background_record_id") orelse return error.InvalidSessionFormat); const assistant = try formatLegacyBackgroundAssistant( alloc, legacy_assistant, log_path, url, ); return .{ .assistant = .{ .user = user, .assistant = assistant, .execution = execution, } }; } if (std.mem.eql(u8, kind, "interrupted")) { const source = try requireObject(value); const has_execution = source.get("execution") != null; const has_presentation = source.get("cancelled_command") != null; const has_terminal_reason = source.get("terminal_reason") != null; const has_cancellation_origin = source.get("cancellation_origin") != null; const object = try exactInterruptedHistoryObject( value, has_execution, has_presentation, has_terminal_reason, has_cancellation_origin, ); const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat); errdefer if (assistant) |owned| mem_utils.free(alloc, owned); const tool_call = try parseOptionalToolCall(alloc, object.get("tool_call") orelse return error.InvalidSessionFormat); errdefer if (tool_call) |call| session.freeToolCall(alloc, call); const completed_tool_names = try parseDurableBytesArray( alloc, object.get("completed_tool_names") orelse return error.InvalidSessionFormat, ); errdefer session.freeCompletedToolNames(alloc, completed_tool_names); const terminal_reason = if (has_terminal_reason) try parseInterruptedTerminalReason(object, "terminal_reason") else types.InterruptedTerminalReason.cancelled; const cancellation_origin: types.CancellationOrigin = if (has_cancellation_origin) std.meta.stringToEnum(types.CancellationOrigin, try requireString(object, "cancellation_origin")) orelse return error.InvalidSessionFormat else .turn; const execution = if (has_execution) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const cancelled_command = if (has_presentation) try parseCancelledCommandPresentation( alloc, object.get("cancelled_command") orelse return error.InvalidSessionFormat, ) else null; errdefer if (cancelled_command) |presentation| { types.freeCancelledCommandPresentation(alloc, presentation); }; if (cancelled_command != null and (terminal_reason != .cancelled or tool_call == null or !try isCapturedCommandToolCall(alloc, tool_call.?))) { return error.InvalidSessionFormat; } return .{ .interrupted = .{ .user = user, .assistant = assistant, .tool_call = tool_call, .completed_tool_names = completed_tool_names, .execution = execution, .cancelled_command = cancelled_command, .terminal_reason = terminal_reason, .cancellation_origin = cancellation_origin, } }; } return error.InvalidSessionFormat;}
fn isCapturedCommandToolCall(alloc: Allocator, call: types.ToolCall) !bool { return captured_command.isToolCall(alloc, call.name, call.arguments_json);}
pub fn validateState(state: DurableSessionState) !void { return validateStateWithPermissionMigration(state, false);}
fn validateStateWithPermissionMigration( state: DurableSessionState, allow_legacy_permission_state: bool,) !void { try validateSessionId(state.id); try validateWorkspaceRoot(state.origin_workspace_root); try validateWorkspaceRoot(state.workspace_root); if (state.created_at_ms < 0 or state.updated_at_ms < 0) return error.InvalidDurableField; try validateConversationLanguage(state.conversation_language); try validateModel(state.preferences.model); if (state.context_history_start > state.history.len) return error.InvalidDurableField; if (state.last_subagent_work_id) |id| { session.validateWorkId(id) catch return error.InvalidDurableField; } for (state.history) |turn| { if (session.historyTurnWorkId(turn)) |id| { session.validateWorkId(id) catch return error.InvalidDurableField; } if (types.historyTurnSummary(turn)) |summary| { if (summary.started_at_ms < 0 or summary.completed_at_ms < summary.started_at_ms) { return error.InvalidDurableField; } } } if (state.usage) |usage| try session_usage.validateSnapshot(usage); if (allow_legacy_permission_state and state.permission_state.version == 1) { session_permission_state.validateSchema(state.permission_state, 1) catch return error.InvalidDurableField; } else { session_permission_state.validate(state.permission_state) catch return error.InvalidDurableField; } if (state.recovery_checkpoint) |checkpoint| { if (checkpoint.version != 2 or checkpoint.turn_id == 0 or checkpoint.max_provider_attempts == 0 or checkpoint.consumed_provider_attempts > checkpoint.max_provider_attempts or (checkpoint.outstanding_reservation and checkpoint.consumed_provider_attempts >= checkpoint.max_provider_attempts)) { return error.InvalidDurableField; } try validateModel(checkpoint.authority.model); if (checkpoint.authority.credential_identity != null and checkpoint.authority.credential_source == null) { return error.InvalidDurableField; } if (checkpoint.authority.credential_source) |source| { if (!model_provider.authorizesCredential(checkpoint.authority.provider, source)) { return error.InvalidDurableField; } } }}
pub fn validateModelPreference(value: []const u8) !void { try validateModel(value);}
pub fn parseConversationLanguage(raw: []const u8) !session.ConversationLanguage { try validateConversationLanguageBytes(raw); return session.ConversationLanguage.fromSlice(raw) catch error.InvalidDurableField;}
fn writeState(writer: *std.Io.Writer, state: DurableSessionState) !void { try writer.writeAll("{\"id\":"); try writeJsonString(writer, state.id); try writer.writeAll(",\"origin_workspace_root\":"); try writeJsonString(writer, state.origin_workspace_root); try writer.writeAll(",\"workspace_root\":"); try writeJsonString(writer, state.workspace_root); try writer.print(",\"created_at_ms\":{d},\"updated_at_ms\":{d}", .{ state.created_at_ms, state.updated_at_ms, }); try writer.writeAll(",\"conversation_language\":"); try writeJsonString(writer, state.conversation_language.view()); try writer.writeAll(",\"preferences\":{\"model\":"); try writeJsonString(writer, state.preferences.model); try writer.writeAll(",\"effort\":"); try writeJsonString(writer, state.preferences.effort.label()); try writer.print(",\"fast_mode\":{s},\"provider\":", .{ if (state.preferences.fast_mode) "true" else "false", }); try std.json.Stringify.value(state.preferences.provider, .{}, writer); try writer.writeAll("},\"history\":["); for (state.history, 0..) |turn, i| { if (i > 0) try writer.writeByte(','); try writeHistoryTurn(writer, turn); } try writer.print("],\"total_input_tokens\":{d},\"total_output_tokens\":{d},\"context_history_start\":{d}", .{ state.total_input_tokens, state.total_output_tokens, state.context_history_start, }); try writer.writeAll(",\"permission_state\":"); try writePermissionState(writer, state.permission_state); if (state.usage) |usage| { try writer.writeAll(",\"usage\":"); try session_usage.writeSnapshot(writer, usage); } if (state.last_subagent_work_id) |id| { try writer.writeAll(",\"last_subagent_work_id\":"); try writeJsonString(writer, id); } if (state.subagent_child) { try writer.writeAll(",\"subagent_child\":true"); } if (state.recovery_checkpoint) |checkpoint| { try writer.writeAll(",\"recovery_checkpoint\":"); try writeRecoveryCheckpoint(writer, checkpoint); } try writer.writeByte('}');}
fn writePermissionState( writer: *std.Io.Writer, state: session_permission_state.State,) !void { try writer.print("{{\"schema_version\":{d},\"next_generation\":{d},\"rules\":[", .{ state.version, state.next_generation, }); for (state.rules.items, 0..) |rule, index| { if (index > 0) try writer.writeByte(','); try writer.print("{{\"id\":{d},\"kind\":", .{rule.id.value}); try writeJsonString(writer, @tagName(rule.key.kind)); try writer.writeAll(",\"canonical\":"); try writeDurableBytes(writer, rule.key.canonical); try writer.writeAll(",\"display_identity\":"); try writeDurableBytes(writer, rule.display_identity); try writer.writeAll(",\"decision\":"); try writeJsonString(writer, @tagName(rule.decision)); try writer.print(",\"generation\":{d}}}", .{rule.generation}); } try writer.writeAll("]}");}
fn parsePermissionState( alloc: Allocator, value: std.json.Value,) !session_permission_state.State { const object = try exactObject(value, &.{ "schema_version", "next_generation", "rules", }); const version = std.math.cast( u8, try requireU64(object, "schema_version"), ) orelse return error.InvalidDurableField; const rules_value = object.get("rules") orelse return error.InvalidSessionFormat; if (rules_value != .array) return error.InvalidSessionFormat;
var state: session_permission_state.State = .{ .version = version, .next_generation = try requireU64(object, "next_generation"), }; errdefer state.deinit(alloc); try state.rules.ensureTotalCapacity( alloc, @min(rules_value.array.items.len, session_permission_state.max_rules), ); for (rules_value.array.items) |rule_value| { if (state.rules.items.len == session_permission_state.max_rules) { return error.InvalidDurableField; } const rule_object = try exactObject(rule_value, &.{ "id", "kind", "canonical", "display_identity", "decision", "generation", }); const kind = std.meta.stringToEnum( session_permission_state.RuleKey.Kind, try requireString(rule_object, "kind"), ) orelse return error.InvalidDurableField; const canonical = try parseRequiredDurableBytes( alloc, rule_object, "canonical", ); errdefer alloc.free(canonical); const key = session_permission_state.RuleKey.init( kind, canonical, ) catch return error.InvalidDurableField; const display_identity = try parseRequiredDurableBytes( alloc, rule_object, "display_identity", ); errdefer alloc.free(display_identity); const decision = std.meta.stringToEnum( session_permission_state.Decision, try requireString(rule_object, "decision"), ) orelse return error.InvalidDurableField; try state.rules.append(alloc, .{ .id = .{ .value = try requireU64(rule_object, "id") }, .key = key, .display_identity = display_identity, .decision = decision, .generation = try requireU64(rule_object, "generation"), }); } session_permission_state.validateSchema(state, version) catch return error.InvalidDurableField; return state;}
pub fn writeRecoveryCheckpoint(writer: *std.Io.Writer, checkpoint: RecoveryCheckpoint) !void { if (checkpoint.disposition == .history_only) return error.InvalidDurableField; try writer.print("{{\"version\":{d},\"turn_id\":{d},\"user\":", .{ checkpoint.version, checkpoint.turn_id, }); try writeUserTurn(writer, checkpoint.user); try writer.writeAll(",\"assistant_source\":"); try writeDurableBytes(writer, checkpoint.assistant_source); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, checkpoint.execution); try writer.writeAll(",\"cause\":"); try writeJsonString(writer, @tagName(checkpoint.cause)); try writer.writeAll(",\"action\":"); try writeJsonString(writer, @tagName(checkpoint.action)); try writer.writeAll(",\"tool_state\":"); try writeJsonString(writer, @tagName(checkpoint.tool_state)); try writer.writeAll(",\"authority\":{\"provider\":"); try std.json.Stringify.value(checkpoint.authority.provider, .{}, writer); try writer.writeAll(",\"model\":"); try writeDurableBytes(writer, checkpoint.authority.model); try writer.writeAll(",\"credential_source\":"); if (checkpoint.authority.credential_source) |source| { try writeJsonString(writer, @tagName(source)); } else { try writer.writeAll("null"); } try writer.writeAll(",\"credential_identity\":"); if (checkpoint.authority.credential_identity) |identity| { const hex = std.fmt.bytesToHex(identity.bytes, .lower); try writeJsonString(writer, &hex); } else { try writer.writeAll("null"); } try writer.writeByte('}'); try writer.print(",\"requested_fast_mode\":{s},\"fast_mode\":{s},\"max_provider_attempts\":{d},\"consumed_provider_attempts\":{d},\"outstanding_reservation\":{s}}}", .{ if (checkpoint.requested_fast_mode) "true" else "false", if (checkpoint.fast_mode) "true" else "false", checkpoint.max_provider_attempts, checkpoint.consumed_provider_attempts, if (checkpoint.outstanding_reservation) "true" else "false", });}
fn decodeStateImpl(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits, legacy_usage: bool) !DurableSessionState { var json_reader = std.json.Reader.init(alloc, source); defer json_reader.deinit();
try expectToken(try json_reader.next(), .object_begin); try expectKey(&json_reader, alloc, "id"); const id = try readStringOwned(&json_reader, alloc, 255); errdefer mem_utils.free(alloc, id); try expectKey(&json_reader, alloc, "origin_workspace_root"); const origin_workspace_root = try readStringOwned(&json_reader, alloc, std.Io.Dir.max_path_bytes); errdefer mem_utils.free(alloc, origin_workspace_root); try expectKey(&json_reader, alloc, "workspace_root"); const workspace_root = try readStringOwned(&json_reader, alloc, std.Io.Dir.max_path_bytes); errdefer mem_utils.free(alloc, workspace_root); try expectKey(&json_reader, alloc, "created_at_ms"); const created_at_ms = try readI64(&json_reader, alloc); try expectKey(&json_reader, alloc, "updated_at_ms"); const updated_at_ms = try readI64(&json_reader, alloc); try expectKey(&json_reader, alloc, "conversation_language"); const language_raw = try readStringOwned(&json_reader, alloc, session.ConversationLanguage.max_len); defer mem_utils.free(alloc, language_raw); const conversation_language = try parseConversationLanguage(language_raw);
try expectKey(&json_reader, alloc, "preferences"); var preferences = preferences: { var buffer: [8 * 1024]u8 = undefined; var fixed = std.heap.FixedBufferAllocator.init(&buffer); const value = std.json.Value.jsonParse(fixed.allocator(), &json_reader, .{ .max_value_len = 1024, .allocate = .alloc_always, }) catch |err| switch (err) { error.OutOfMemory => return error.InvalidSessionFormat, else => return err, }; break :preferences try parse_preferences(alloc, value); }; errdefer preferences.deinit(alloc);
try expectKey(&json_reader, alloc, "history"); try expectToken(try json_reader.next(), .array_begin); var history: std.ArrayList(session.HistoryTurn) = .empty; errdefer { for (history.items) |turn| session.freeHistoryTurn(alloc, turn); mem_utils.deinitList(alloc, &history); } while (try json_reader.peekNextTokenType() != .array_end) { if (history.items.len == limits.max_history_turns) return error.InvalidSessionFormat; var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const value = try std.json.Value.jsonParse(arena.allocator(), &json_reader, .{ .max_value_len = limits.max_value_bytes, .allocate = .alloc_always, .parse_numbers = false, }); const turn = try parseHistoryTurn(alloc, value); errdefer session.freeHistoryTurn(alloc, turn); try history.append(alloc, turn); } try expectToken(try json_reader.next(), .array_end);
try expectKey(&json_reader, alloc, "total_input_tokens"); const total_input_tokens = try readU64(&json_reader, alloc); try expectKey(&json_reader, alloc, "total_output_tokens"); const total_output_tokens = try readU64(&json_reader, alloc); var context_history_start: usize = 0; var context_seen = false; var permission_state: session_permission_state.State = .{}; errdefer permission_state.deinit(alloc); var permission_state_seen = false; var usage: ?session_usage.Snapshot = null; errdefer if (usage) |*snapshot| snapshot.deinit(alloc); var usage_seen = false; var last_subagent_work_id: ?[]u8 = null; errdefer if (last_subagent_work_id) |work_id| mem_utils.free(alloc, work_id); var subagent_child = false; var subagent_child_seen = false; var recovery_checkpoint: ?RecoveryCheckpoint = null; errdefer if (recovery_checkpoint) |*checkpoint| checkpoint.deinit(alloc); while (try json_reader.peekNextTokenType() != .object_end) { const key = try readStringOwned(&json_reader, alloc, 64); defer mem_utils.free(alloc, key); if (std.mem.eql(u8, key, "context_history_start")) { if (context_seen or permission_state_seen or usage_seen or last_subagent_work_id != null or subagent_child_seen or recovery_checkpoint != null) { return error.InvalidSessionFormat; } const raw = try readU64(&json_reader, alloc); context_history_start = std.math.cast(usize, raw) orelse return error.InvalidSessionFormat; context_seen = true; } else if (std.mem.eql(u8, key, "permission_state")) { if (permission_state_seen or usage_seen or last_subagent_work_id != null or subagent_child_seen or recovery_checkpoint != null) { return error.InvalidSessionFormat; } var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const value = try std.json.Value.jsonParse(arena.allocator(), &json_reader, .{ .max_value_len = limits.max_value_bytes, .allocate = .alloc_always, .parse_numbers = false, }); permission_state = try parsePermissionState(alloc, value); permission_state_seen = true; } else if (std.mem.eql(u8, key, "usage")) { if (usage_seen or last_subagent_work_id != null or subagent_child_seen or recovery_checkpoint != null) return error.InvalidSessionFormat; const parse_limit = @min( limits.max_value_bytes, session_usage.max_snapshot_bytes, ); const parse_buffer = try alloc.alloc(u8, parse_limit); defer mem_utils.free(alloc, parse_buffer); var fixed = std.heap.FixedBufferAllocator.init(parse_buffer); const value = try std.json.Value.jsonParse(fixed.allocator(), &json_reader, .{ .max_value_len = parse_limit, .allocate = .alloc_always, .parse_numbers = false, }); usage = if (legacy_usage) try session_usage.parseLegacySnapshotValue(alloc, value) else try session_usage.parseSnapshotValue(alloc, value); usage_seen = true; } else if (std.mem.eql(u8, key, "last_subagent_work_id")) { if (last_subagent_work_id != null or subagent_child_seen or recovery_checkpoint != null) return error.InvalidSessionFormat; last_subagent_work_id = try readStringOwned(&json_reader, alloc, 128); } else if (std.mem.eql(u8, key, "subagent_child")) { if (subagent_child_seen or recovery_checkpoint != null) return error.InvalidSessionFormat; subagent_child = try readBool(&json_reader); if (!subagent_child) return error.InvalidDurableField; subagent_child_seen = true; } else if (std.mem.eql(u8, key, "recovery_checkpoint")) { if (recovery_checkpoint != null) return error.InvalidSessionFormat; var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const value = try std.json.Value.jsonParse(arena.allocator(), &json_reader, .{ .max_value_len = limits.max_value_bytes, .allocate = .alloc_always, .parse_numbers = false, }); recovery_checkpoint = try parseRecoveryCheckpoint(alloc, value); } else return error.InvalidSessionFormat; } try expectToken(try json_reader.next(), .object_end); try expectToken(try json_reader.next(), .end_of_document);
const owned_history = try history.toOwnedSlice(alloc); errdefer session.freeHistoryTurnSlice(alloc, owned_history); const state = DurableSessionState{ .id = id, .origin_workspace_root = origin_workspace_root, .workspace_root = workspace_root, .created_at_ms = created_at_ms, .updated_at_ms = updated_at_ms, .conversation_language = conversation_language, .preferences = preferences, .history = owned_history, .context_history_start = context_history_start, .total_input_tokens = total_input_tokens, .total_output_tokens = total_output_tokens, .permission_state = permission_state, .last_subagent_work_id = last_subagent_work_id, .subagent_child = subagent_child, .usage = usage, .recovery_checkpoint = recovery_checkpoint, }; try validateStateWithPermissionMigration(state, true); return state;}
pub fn parseRecoveryCheckpoint(alloc: Allocator, value: std.json.Value) !RecoveryCheckpoint { const raw_object = try requireObject(value); const version = try requireU64(raw_object, "version"); const legacy_route = raw_object.contains("route_identity"); const object = if (legacy_route) legacy: { try validate_legacy_recovery_route(raw_object.get("route_identity").?, version); const delivery = try requireString(raw_object, "delivery"); if (!std.mem.eql(u8, delivery, "possibly_sent") and !std.mem.eql(u8, delivery, "definitely_unsent")) return error.InvalidDurableField; break :legacy try exactObject(value, &.{ "version", "route_identity", "delivery", "turn_id", "user", "assistant_source", "execution", "cause", "action", "tool_state", "route_model", "requested_fast_mode", "fast_mode", "max_provider_attempts", "consumed_provider_attempts", "outstanding_reservation", }); } else switch (version) { 1 => if (raw_object.get("route_provider") != null) try exactObject(value, &.{ "version", "turn_id", "user", "assistant_source", "execution", "cause", "action", "tool_state", "route_model", "route_provider", "requested_fast_mode", "fast_mode", "max_provider_attempts", "consumed_provider_attempts", "outstanding_reservation", }) else try exactObject(value, &.{ "version", "turn_id", "user", "assistant_source", "execution", "cause", "action", "tool_state", "route_model", "requested_fast_mode", "fast_mode", "max_provider_attempts", "consumed_provider_attempts", "outstanding_reservation", }), 2 => try exactObject(value, &.{ "version", "turn_id", "user", "assistant_source", "execution", "cause", "action", "tool_state", "authority", "requested_fast_mode", "fast_mode", "max_provider_attempts", "consumed_provider_attempts", "outstanding_reservation", }), else => return error.InvalidDurableField, }; const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant_source = try parseDurableBytes(alloc, object.get("assistant_source") orelse return error.InvalidSessionFormat); errdefer alloc.free(assistant_source); const execution = try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat); errdefer session.freeExecutionMemory(alloc, execution); const authority = if (legacy_route) TurnAuthority{ .provider = .gateway, .model = try parseDurableBytes(alloc, object.get("route_model") orelse return error.InvalidSessionFormat), } else if (version == 1) legacy: { const model = try parseDurableBytes(alloc, object.get("route_model") orelse return error.InvalidSessionFormat); break :legacy TurnAuthority{ .provider = if (object.get("route_provider")) |provider_value| blk: { if (provider_value != .string) return error.InvalidDurableField; break :blk model_provider.parse(provider_value.string) orelse return error.InvalidDurableField; } else .gateway, .model = model, }; } else try parseTurnAuthority(alloc, object.get("authority") orelse return error.InvalidSessionFormat); errdefer { var owned_authority = authority; owned_authority.deinit(alloc); } const max_provider_attempts = std.math.cast(usize, try requireU64(object, "max_provider_attempts")) orelse return error.InvalidDurableField; const consumed_provider_attempts = std.math.cast(usize, try requireU64(object, "consumed_provider_attempts")) orelse return error.InvalidDurableField; return .{ .version = 2, .disposition = if (legacy_route) .history_only else .continuable, .turn_id = try requireU64(object, "turn_id"), .user = user, .assistant_source = assistant_source, .execution = execution, .cause = std.meta.stringToEnum(types.ModelRecoveryCause, try requireString(object, "cause")) orelse return error.InvalidDurableField, .action = std.meta.stringToEnum(types.ModelRecoveryAction, try requireString(object, "action")) orelse return error.InvalidDurableField, .tool_state = std.meta.stringToEnum(RecoveryToolState, try requireString(object, "tool_state")) orelse return error.InvalidDurableField, .authority = authority, .requested_fast_mode = try requireBool(object, "requested_fast_mode"), .fast_mode = try requireBool(object, "fast_mode"), .max_provider_attempts = max_provider_attempts, .consumed_provider_attempts = consumed_provider_attempts, .outstanding_reservation = try requireBool(object, "outstanding_reservation"), };}
test "legacy route checkpoints retain history without resumable authority" { const alloc = std.testing.allocator; const routes = [_][]const u8{ "{\"connection_id\":\"vercel\",\"adapter_kind\":\"vercel_ai_gateway\",\"permission_review_model_id\":\"review\"}", "{\"connection_id\":\"vercel\",\"adapter_kind\":\"vercel_ai_gateway\",\"permission_review_model_id\":\"review\",\"vision_model_id\":\"vision\",\"subagent_model_id\":\"child\"}", "{\"version\":1,\"connection_id\":\"vercel\",\"adapter_kind\":\"vercel_ai_gateway\",\"endpoint\":\"https://unused.invalid\",\"protocol\":\"vercel_ai_gateway\",\"credential_ref\":\"unused\",\"permission_review_model_id\":\"review\",\"vision_model_id\":\"vision\",\"subagent_model_id\":\"child\"}", }; const fields = "\"turn_id\":1,\"user\":{\"text\":\"saved request\",\"images\":[]},\"assistant_source\":\"saved partial\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]},\"cause\":\"response_interrupted\",\"action\":\"continuing_response\",\"tool_state\":\"uncertain\",\"route_model\":\"test/model\",\"requested_fast_mode\":false,\"fast_mode\":false,\"max_provider_attempts\":3,\"consumed_provider_attempts\":0,\"outstanding_reservation\":false}"; for (routes, 2..) |route, version| { for ([_][]const u8{ "possibly_sent", "definitely_unsent" }) |delivery| { const bytes = try std.fmt.allocPrint(alloc, "{{\"version\":{d},\"route_identity\":{s},\"delivery\":\"{s}\",{s}", .{ version, route, delivery, fields }); defer alloc.free(bytes); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, bytes, .{}); defer parsed.deinit(); var checkpoint = try parseRecoveryCheckpoint(alloc, parsed.value); defer checkpoint.deinit(alloc); try std.testing.expectEqual(.history_only, checkpoint.disposition); try std.testing.expect(checkpoint.authority.credential_source == null); try std.testing.expect(checkpoint.authority.credential_identity == null); var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try std.testing.expectError(error.InvalidDurableField, writeRecoveryCheckpoint(&out.writer, checkpoint)); try std.testing.expectEqual(@as(usize, 0), out.written().len); const borrowed = DurableSessionState{ .id = @constCast("legacy"), .origin_workspace_root = @constCast("/workspace"), .workspace_root = @constCast("/workspace"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = .literal("en"), .preferences = .{ .model = @constCast("test/model"), .effort = .auto, .fast_mode = false }, .history = &.{}, .total_input_tokens = 0, .total_output_tokens = 0, .recovery_checkpoint = checkpoint, }; try std.testing.checkAllAllocationFailures(alloc, struct { fn check(a: Allocator, original: DurableSessionState) !void { var state = try original.dupe(a); defer state.deinit(a); try std.testing.expect(try state.archive_legacy_recovery(a)); try std.testing.expect(!(try state.archive_legacy_recovery(a))); try std.testing.expect(state.recovery_checkpoint == null); try std.testing.expectEqual(@as(usize, 1), state.history.len); try std.testing.expectEqualStrings("saved request", state.history[0].interrupted.user.text); try std.testing.expectEqualStrings("saved partial", state.history[0].interrupted.assistant.?); } }.check, .{borrowed}); } }}
fn validate_legacy_recovery_route(value: std.json.Value, version: u64) !void { const object = switch (version) { 2 => try exactObject(value, &.{ "connection_id", "adapter_kind", "permission_review_model_id" }), 3 => try exactObject(value, &.{ "connection_id", "adapter_kind", "permission_review_model_id", "vision_model_id", "subagent_model_id" }), 4 => try exactObject(value, &.{ "version", "connection_id", "adapter_kind", "endpoint", "protocol", "credential_ref", "permission_review_model_id", "vision_model_id", "subagent_model_id" }), else => return error.InvalidDurableField, }; if (!std.mem.eql(u8, try requireString(object, "connection_id"), "vercel") or !std.mem.eql(u8, try requireString(object, "adapter_kind"), "vercel_ai_gateway")) return error.InvalidDurableField; if (version == 4 and (try requireU64(object, "version") != 1 or !std.mem.eql(u8, try requireString(object, "protocol"), "vercel_ai_gateway"))) return error.InvalidDurableField; var fields = object.iterator(); while (fields.next()) |field| { if (std.mem.eql(u8, field.key_ptr.*, "version")) continue; if (field.value_ptr.* != .string or field.value_ptr.string.len > 4096 or !std.unicode.utf8ValidateSlice(field.value_ptr.string)) return error.InvalidDurableField; }}
fn parseTurnAuthority(alloc: Allocator, value: std.json.Value) !TurnAuthority { const object = try exactObject(value, &.{ "provider", "model", "credential_source", "credential_identity", }); const provider = model_provider.parse_saved(object.get("provider").?) catch return error.InvalidDurableField; const model = try parseDurableBytes(alloc, object.get("model") orelse return error.InvalidSessionFormat); errdefer alloc.free(model); const credential_source = if (object.get("credential_source")) |source| switch (source) { .null => null, .string => |text| types.parseRuntimeCredentialSource(text) orelse return error.InvalidDurableField, else => return error.InvalidDurableField, } else return error.InvalidSessionFormat; const credential_identity = if (object.get("credential_identity")) |identity| switch (identity) { .null => null, .string => |hex| identity: { var bytes: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; _ = std.fmt.hexToBytes(&bytes, hex) catch return error.InvalidDurableField; const canonical = std.fmt.bytesToHex(bytes, .lower); if (!std.mem.eql(u8, &canonical, hex)) return error.InvalidDurableField; break :identity credential_authority.Identity{ .bytes = bytes }; }, else => return error.InvalidDurableField, } else return error.InvalidSessionFormat; if (credential_identity != null and credential_source == null) { return error.InvalidDurableField; } return .{ .provider = provider, .model = model, .credential_source = credential_source, .credential_identity = credential_identity, };}
fn writeUserTurn(writer: *std.Io.Writer, user: session.UserTurn) !void { try writer.writeAll("{\"text\":"); try writeDurableBytes(writer, user.text); try writer.writeAll(",\"images\":["); for (user.images, 0..) |image, i| { if (i > 0) try writer.writeByte(','); try writer.print("{{\"id\":{d},\"path\":", .{image.id}); try writeDurableBytes(writer, image.path); try writer.writeAll(",\"media_type\":"); try writeDurableBytes(writer, image.media_type); try writer.writeAll(",\"snapshot_path\":"); try writeSnapshotLocator(writer, image.snapshot_path); try writer.writeAll(",\"snapshot_sha256\":"); try writeOptionalDurableBytes(writer, image.snapshot_sha256); try writer.writeByte('}'); } try writer.writeByte(']'); if (user.work_id) |work_id| { try writer.writeAll(",\"work_id\":"); try writeJsonString(writer, work_id); } try writer.writeByte('}');}
fn writeSnapshotLocator(writer: *std.Io.Writer, value: ?[]const u8) !void { const path = value orelse { try writer.writeAll("null"); return; }; var locator_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; const locator = try session.projectSnapshotLocator(&locator_buffer, path); try writeDurableBytes(writer, locator);}
fn writeExecutionMemory(writer: *std.Io.Writer, execution: session.ExecutionMemory) !void { try writer.writeAll("{\"schema_version\":10,\"tool_steps\":["); for (execution.tool_steps, 0..) |step, i| { if (i > 0) try writer.writeByte(','); try writer.writeAll("{\"assistant\":"); try writeOptionalDurableBytes(writer, step.assistant); try writer.writeAll(",\"provider_replay\":"); try std.json.Stringify.value(step.provider_replay, .{}, writer); try writer.writeAll(",\"tool_calls\":["); for (step.tool_calls, 0..) |tool_call, call_index| { if (call_index > 0) try writer.writeByte(','); try writeToolCall(writer, tool_call); } try writer.writeAll("],\"tool_results\":["); for (step.tool_results, 0..) |result, result_index| { if (result_index > 0) try writer.writeByte(','); try writePersistedToolResult(writer, result); } try writer.writeAll("]}"); } try writer.writeAll("],\"files\":["); for (execution.files, 0..) |file, i| { if (i > 0) try writer.writeByte(','); try writeFileEvidence(writer, file); } try writer.writeAll("],\"steering\":["); for (execution.steering, 0..) |steering, i| { if (i > 0) try writer.writeByte(','); try writer.writeAll("{\"text\":"); try writeDurableBytes(writer, steering.text); try writer.writeAll(",\"assistant_prefix\":"); try writeOptionalDurableBytes(writer, steering.assistant_prefix); try writer.print(",\"after_tool_step_count\":{d}}}", .{steering.after_tool_step_count}); } try writer.writeAll("],\"turn_summary\":"); if (execution.turn_summary) |summary| { try writeTurnSummary(writer, summary); } else { try writer.writeAll("null"); } try writer.writeByte('}');}
fn hasDurableExecutionMemory(execution: session.ExecutionMemory) bool { return !execution.isEmpty() or execution.turn_summary != null;}
fn writeTurnSummary(writer: *std.Io.Writer, summary: types.TurnSummary) !void { try writer.print( "{{\"started_at_ms\":{d},\"completed_at_ms\":{d},\"thinking_duration_ms\":{d},\"turn_duration_ms\":{d},\"token_progress\":{{\"input_tokens\":{d},\"output_tokens\":{d},\"input_exact\":{s},\"output_exact\":{s}}}}}", .{ summary.started_at_ms, summary.completed_at_ms, summary.thinking_duration_ms, summary.turn_duration_ms, summary.token_progress.input_tokens, summary.token_progress.output_tokens, if (summary.token_progress.input_exact) "true" else "false", if (summary.token_progress.output_exact) "true" else "false", }, );}
fn writeToolCall(writer: *std.Io.Writer, tool_call: session.ToolCall) !void { try writer.writeAll("{\"id\":"); try writeDurableBytes(writer, tool_call.id); try writer.writeAll(",\"name\":"); try writeDurableBytes(writer, tool_call.name); try writer.writeAll(",\"arguments_json\":"); try writeDurableBytes(writer, tool_call.arguments_json); try writer.writeAll(",\"provider_result\":"); try writeOptionalDurableBytes(writer, tool_call.provider_result); try writer.writeByte('}');}
fn writePersistedToolResult(writer: *std.Io.Writer, result: session.PersistedToolResult) !void { if (result.review_feedback and (result.status != .failure or result.provider_native)) { return error.InvalidSessionFormat; } try writer.writeAll("{\"tool_call_id\":"); try writeDurableBytes(writer, result.tool_call_id); try writer.writeAll(",\"tool_name\":"); try writeDurableBytes(writer, result.tool_name); try writer.writeAll(",\"status\":"); try writeJsonString(writer, @tagName(result.status)); try writer.writeAll(",\"output\":"); try writeDurableBytes(writer, result.output); try writer.writeAll(",\"output_handle\":"); try writeOptionalDurableBytes(writer, result.output_handle); try writer.writeAll(",\"preview\":"); try writeOptionalDurableBytes(writer, result.preview); try writer.print( ",\"output_bytes\":{d},\"stored_output_bytes\":{d},\"truncated\":{s},\"provider_native\":{s},\"review_feedback\":{s},\"created_at_ms\":{d}", .{ result.output_bytes, result.stored_output_bytes, if (result.truncated) "true" else "false", if (result.provider_native) "true" else "false", if (result.review_feedback) "true" else "false", result.created_at_ms, }, ); try writer.writeAll(",\"permission_feedback\":["); for (result.permission_feedback, 0..) |feedback, i| { if (i > 0) try writer.writeByte(','); try writeDurableBytes(writer, feedback); } try writer.writeAll("],\"committed_file_presentation\":"); if (result.committed_file_presentation) |presentation| { try writeCommittedFilePresentation(writer, presentation); } else { try writer.writeAll("null"); } try writer.writeAll(",\"command_output_replay\":"); try writeOptionalCommandOutputReplay(writer, result.command_output_replay); try writer.writeAll(",\"command_process_presentation\":"); try writeOptionalCommandProcessPresentation( writer, result.command_process_presentation, ); try writer.writeAll(",\"terminal_action_presentation\":"); try writeOptionalTerminalActionPresentation( writer, result.terminal_action_presentation, ); if (result.tool_image_handle) |handle| { try writer.writeAll(",\"tool_image_handle\":"); try writeDurableBytes(writer, handle); } else if (result.tool_images.len > 0) { try writer.writeAll(",\"tool_images\":["); for (result.tool_images, 0..) |image, index| { if (index > 0) try writer.writeByte(','); try writer.writeAll("{\"type\":\"image\",\"mimeType\":"); try std.json.Stringify.value(image.mime_type, .{}, writer); try writer.writeAll(",\"data\":"); try std.json.Stringify.value(image.data, .{}, writer); try writer.writeByte('}'); } try writer.writeByte(']'); } try writer.writeByte('}');}
fn writeCancelledCommandPresentation( writer: *std.Io.Writer, presentation: session.CancelledCommandPresentation,) !void { try writer.writeAll("{\"output_replay\":"); try writeOptionalCommandOutputReplay(writer, presentation.output_replay); try writer.writeAll(",\"command_artifact_handle\":"); try writeOptionalDurableBytes(writer, presentation.command_artifact_handle); try writer.writeByte('}');}
fn parseCancelledCommandPresentation( alloc: Allocator, value: std.json.Value,) !session.CancelledCommandPresentation { const object = try exactObject(value, &.{ "output_replay", "command_artifact_handle", }); const output_replay = try parseOptionalCommandOutputReplay( alloc, object.get("output_replay") orelse return error.InvalidSessionFormat, ); errdefer if (output_replay) |replay| types.freeCommandOutputReplay(alloc, replay); return .{ .output_replay = output_replay, .command_artifact_handle = try parseOptionalDurableBytes( alloc, object.get("command_artifact_handle") orelse return error.InvalidSessionFormat, ), };}
fn writeOptionalCommandOutputReplay( writer: *std.Io.Writer, replay: ?types.CommandOutputReplay,) !void { const value = replay orelse { try writer.writeAll("null"); return; }; switch (value) { .available => |descriptor| { try writer.writeAll("{\"kind\":\"available\",\"handle\":"); try writeDurableBytes(writer, descriptor.handle); try writer.print(",\"framed_bytes\":{d}}}", .{descriptor.framed_bytes}); }, .unavailable => try writer.writeAll("{\"kind\":\"unavailable\"}"), }}
fn writeOptionalCommandProcessPresentation( writer: *std.Io.Writer, presentation: ?types.CommandProcessPresentation,) !void { const value = presentation orelse { try writer.writeAll("null"); return; }; switch (value) { .exit_code => |exit_code| try writer.print( "{{\"kind\":\"exit_code\",\"value\":{d}}}", .{exit_code}, ), .signal => |signal| try writer.print( "{{\"kind\":\"signal\",\"value\":{d}}}", .{signal}, ), .timed_out => try writer.writeAll("{\"kind\":\"timed_out\",\"value\":null}"), .output_capture_failed => try writer.writeAll("{\"kind\":\"output_capture_failed\",\"value\":null}"), }}
fn writeOptionalTerminalActionPresentation( writer: *std.Io.Writer, presentation: ?types.TerminalActionPresentation,) !void { const value = presentation orelse { try writer.writeAll("null"); return; }; switch (value) { .returned => |returned| { try writer.writeAll("{\"kind\":\"returned\",\"outcome\":"); try writeTerminalReturnPresentation(writer, returned); try writer.writeByte('}'); }, .failed => |failed| { try writer.writeAll("{\"kind\":\"failed\",\"code\":"); try writeJsonString(writer, @tagName(failed)); try writer.writeByte('}'); }, }}
fn writeTerminalReturnPresentation( writer: *std.Io.Writer, outcome: types.TerminalReturnPresentation,) !void { switch (outcome) { .started, .condition_met, .safety_ceiling, .cancelled => { try writer.writeAll("{\"kind\":"); try writeJsonString(writer, @tagName(outcome)); try writer.writeAll(",\"value\":null}"); }, .exited => |code| try writer.print( "{{\"kind\":\"exited\",\"value\":{d}}}", .{code}, ), .signal => |signal| try writer.print( "{{\"kind\":\"signal\",\"value\":{d}}}", .{signal}, ), }}
fn writeCommittedFilePresentation( writer: *std.Io.Writer, presentation: types.CommittedFilePresentation,) !void { try writer.writeAll("{\"path\":"); try writeDurableBytes(writer, presentation.path); try writer.writeAll(",\"kind\":"); try writeJsonString(writer, @tagName(presentation.kind)); try writer.writeAll(",\"lines\":["); for (presentation.lines, 0..) |line, i| { if (i > 0) try writer.writeByte(','); try writer.writeAll("{\"kind\":"); try writeJsonString(writer, @tagName(line.kind)); try writer.writeAll(",\"old_line\":"); try writeOptionalU32(writer, line.old_line); try writer.writeAll(",\"new_line\":"); try writeOptionalU32(writer, line.new_line); try writer.writeAll(",\"text\":"); try writeDurableBytes(writer, line.text); try writer.writeByte('}'); } try writer.print( "],\"additions\":{d},\"deletions\":{d},\"truncated\":{s},\"previous_content\":", .{ presentation.additions, presentation.deletions, if (presentation.truncated) "true" else "false", }, ); try writeOptionalDurableBytes(writer, presentation.previous_content); try writer.writeAll(",\"after_content\":"); try writeOptionalDurableBytes(writer, presentation.after_content); try writer.writeAll(",\"lifecycle_id\":"); if (presentation.lifecycle_id) |lifecycle_id| { try writer.print("{{\"turn_id\":{d},\"call_id\":", .{lifecycle_id.turn_id}); try writeDurableBytes(writer, lifecycle_id.call_id); try writer.writeByte('}'); } else { try writer.writeAll("null"); } try writer.writeByte('}');}
fn writeFileEvidence(writer: *std.Io.Writer, file: session.FileEvidence) !void { try writer.writeAll("{\"path\":"); try writeDurableBytes(writer, file.path); try writer.writeAll(",\"new_path\":"); try writeOptionalDurableBytes(writer, file.new_path); try writer.writeAll(",\"tool_call_id\":"); try writeDurableBytes(writer, file.tool_call_id); try writer.writeAll(",\"tool_name\":"); try writeDurableBytes(writer, file.tool_name); try writer.writeAll(",\"action\":"); try writeJsonString(writer, @tagName(file.action)); try writer.writeAll(",\"status\":"); try writeJsonString(writer, @tagName(file.status)); try writer.print(",\"model_view_covers_full_file\":{s},\"stale\":{s}", .{ if (file.model_view_covers_full_file) "true" else "false", if (file.stale) "true" else "false", }); try writer.writeByte('}');}
fn parseUserTurn(alloc: Allocator, value: std.json.Value) !session.UserTurn { const source = try requireObject(value); const object = if (source.count() == 2) try exactObject(value, &.{ "text", "images" }) else try exactObject(value, &.{ "text", "images", "work_id" }); const text = try parseRequiredDurableBytes(alloc, object, "text"); errdefer mem_utils.free(alloc, text); const work_id = if (object.get("work_id")) |_| try alloc.dupe(u8, try requireString(object, "work_id")) else null; errdefer if (work_id) |id| mem_utils.free(alloc, id); if (work_id) |id| session.validateWorkId(id) catch return error.InvalidSessionFormat; const images_value = object.get("images") orelse return error.InvalidSessionFormat; if (images_value != .array) return error.InvalidSessionFormat; if (images_value.array.items.len == 0) return .{ .text = text, .work_id = work_id };
const images = try alloc.alloc(session.ImageAttachment, images_value.array.items.len); errdefer mem_utils.free(alloc, images); var parsed_count: usize = 0; errdefer { for (images[0..parsed_count]) |image| { types.freeImageAttachment(alloc, image); } } for (images_value.array.items, 0..) |image_value, i| { const image = try imageAttachmentObject(image_value); const path = try parseRequiredDurableBytes(alloc, image, "path"); errdefer mem_utils.free(alloc, path); const media_type = try parseRequiredDurableBytes(alloc, image, "media_type"); errdefer mem_utils.free(alloc, media_type); const snapshot_path = try parseOptionalDurableBytes( alloc, image.get("snapshot_path") orelse .null, ); errdefer if (snapshot_path) |path_bytes| mem_utils.free(alloc, path_bytes); const snapshot_sha256 = try parseOptionalDurableBytes( alloc, image.get("snapshot_sha256") orelse .null, ); errdefer if (snapshot_sha256) |sha256_bytes| mem_utils.free(alloc, sha256_bytes); if ((snapshot_path == null) != (snapshot_sha256 == null)) return error.InvalidSessionFormat; images[i] = .{ .id = try requireUsize(image, "id"), .path = path, .media_type = media_type, .snapshot_path = snapshot_path, .snapshot_sha256 = snapshot_sha256, }; parsed_count += 1; } return .{ .text = text, .images = images, .work_id = work_id };}
fn imageAttachmentObject(value: std.json.Value) !std.json.ObjectMap { if (value != .object) return error.InvalidSessionFormat; var has_id = false; var has_path = false; var has_media_type = false; var iterator = value.object.iterator(); while (iterator.next()) |entry| { if (std.mem.eql(u8, entry.key_ptr.*, "id")) { has_id = true; } else if (std.mem.eql(u8, entry.key_ptr.*, "path")) { has_path = true; } else if (std.mem.eql(u8, entry.key_ptr.*, "media_type")) { has_media_type = true; } else if (std.mem.eql(u8, entry.key_ptr.*, "snapshot_path") or std.mem.eql(u8, entry.key_ptr.*, "snapshot_sha256")) { continue; } else { return error.InvalidSessionFormat; } } if (!has_id or !has_path or !has_media_type) return error.InvalidSessionFormat; return value.object;}
fn parseExecutionMemory(alloc: Allocator, value: std.json.Value) !session.ExecutionMemory { const source = try requireObject(value); const schema_version = try requireU64(source, "schema_version"); const object = switch (schema_version) { 1...4 => try exactObject(value, &.{ "schema_version", "tool_steps", "files" }), 5 => try exactObject(value, &.{ "schema_version", "tool_steps", "files", "turn_summary" }), 6 => try exactObject(value, &.{ "schema_version", "tool_steps", "files", "steering", "turn_summary" }), 7...10 => try exactObject(value, &.{ "schema_version", "tool_steps", "files", "steering", "turn_summary" }), else => return error.InvalidSessionFormat, }; const tool_steps = try parseToolSteps( alloc, object.get("tool_steps") orelse return error.InvalidSessionFormat, schema_version, ); errdefer session.freeExecutionMemory(alloc, .{ .tool_steps = tool_steps }); const files = try parseFiles(alloc, object.get("files") orelse return error.InvalidSessionFormat); errdefer types.freeFileEvidenceSlice(alloc, files); const steering: []types.PersistedSteering = if (schema_version >= 6) try parsePersistedSteering( alloc, object.get("steering") orelse return error.InvalidSessionFormat, schema_version, tool_steps.len, ) else &.{}; errdefer types.freePersistedSteering(alloc, steering); const turn_summary = if (schema_version >= 5) try parseOptionalTurnSummary(object.get("turn_summary").?) else null; return .{ .tool_steps = tool_steps, .files = files, .steering = steering, .turn_summary = turn_summary, };}
fn parsePersistedSteering( alloc: Allocator, value: std.json.Value, schema_version: u64, tool_step_count: usize,) ![]types.PersistedSteering { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const steering = try alloc.alloc(types.PersistedSteering, value.array.items.len); errdefer alloc.free(steering); var parsed_count: usize = 0; errdefer for (steering[0..parsed_count]) |item| { alloc.free(item.text); if (item.assistant_prefix) |prefix| alloc.free(prefix); }; var prior_tool_step_count: usize = 0; for (value.array.items, 0..) |item, index| { const parsed = if (schema_version == 6) types.PersistedSteering{ .text = try parseDurableBytes(alloc, item), .after_tool_step_count = tool_step_count, } else blk: { const object = try exactObject(item, &.{ "text", "assistant_prefix", "after_tool_step_count" }); const after_tool_step_count = try requireUsize(object, "after_tool_step_count"); if (after_tool_step_count > tool_step_count or (index > 0 and after_tool_step_count < prior_tool_step_count)) { return error.InvalidSessionFormat; } const text = try parseDurableBytes( alloc, object.get("text") orelse return error.InvalidSessionFormat, ); const assistant_prefix = parseOptionalDurableBytes( alloc, object.get("assistant_prefix") orelse return error.InvalidSessionFormat, ) catch |err| { alloc.free(text); return err; }; break :blk types.PersistedSteering{ .text = text, .assistant_prefix = assistant_prefix, .after_tool_step_count = after_tool_step_count, }; }; steering[index] = parsed; parsed_count += 1; prior_tool_step_count = parsed.after_tool_step_count; } return steering;}
fn parseOptionalTurnSummary(value: std.json.Value) !?types.TurnSummary { if (value == .null) return null; const object = try exactObject(value, &.{ "started_at_ms", "completed_at_ms", "thinking_duration_ms", "turn_duration_ms", "token_progress", }); const token_progress = try exactObject( object.get("token_progress") orelse return error.InvalidSessionFormat, &.{ "input_tokens", "output_tokens", "input_exact", "output_exact" }, ); const summary = types.TurnSummary{ .started_at_ms = try requireI64(object, "started_at_ms"), .completed_at_ms = try requireI64(object, "completed_at_ms"), .thinking_duration_ms = try requireU64(object, "thinking_duration_ms"), .turn_duration_ms = try requireU64(object, "turn_duration_ms"), .token_progress = .{ .input_tokens = try requireU64(token_progress, "input_tokens"), .output_tokens = try requireU64(token_progress, "output_tokens"), .input_exact = try requireBool(token_progress, "input_exact"), .output_exact = try requireBool(token_progress, "output_exact"), }, }; if (summary.started_at_ms < 0 or summary.completed_at_ms < summary.started_at_ms) { return error.InvalidSessionFormat; } return summary;}
fn parseToolSteps( alloc: Allocator, value: std.json.Value, schema_version: u64,) ![]session.ToolExecutionStep { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const steps = try alloc.alloc(session.ToolExecutionStep, value.array.items.len); errdefer mem_utils.free(alloc, steps); var parsed_count: usize = 0; errdefer for (steps[0..parsed_count]) |step| { if (step.assistant) |assistant| mem_utils.free(alloc, assistant); if (step.provider_replay) |replay| types.freeProviderReplay(alloc, replay); session.freeToolCallSlice(alloc, step.tool_calls); session.freePersistedToolResults(alloc, step.tool_results); }; for (value.array.items, 0..) |step_value, i| { const object = if (schema_version >= 9) try exactObject(step_value, &.{ "assistant", "tool_calls", "tool_results", "provider_replay" }) else try exactObject(step_value, &.{ "assistant", "tool_calls", "tool_results" }); const assistant = try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat); errdefer if (assistant) |owned| mem_utils.free(alloc, owned); const tool_calls = try parseToolCalls(alloc, object.get("tool_calls") orelse return error.InvalidSessionFormat); errdefer session.freeToolCallSlice(alloc, tool_calls); const tool_results = try parseToolResults( alloc, object.get("tool_results") orelse return error.InvalidSessionFormat, schema_version, ); errdefer session.freePersistedToolResults(alloc, tool_results); try session.repairPersistedToolArguments(alloc, tool_calls, tool_results, .schema_v3); const replay = try parseProviderReplay(alloc, object.get("provider_replay") orelse .null); steps[i] = .{ .assistant = assistant, .provider_replay = replay, .tool_calls = tool_calls, .tool_results = tool_results, }; parsed_count += 1; } return steps;}
fn parseProviderReplay(alloc: Allocator, value: std.json.Value) !?types.ProviderReplay { if (value == .null) return null; const parsed = std.json.parseFromValue(types.ProviderReplay, alloc, value, .{}) catch |err| switch (err) { error.OutOfMemory => return err, else => return error.InvalidSessionFormat, }; defer parsed.deinit(); const replay = parsed.value; if (types.ConversationIdentity.invalidReason(replay.source.model) != null or replay.parts_json.len == 0 or replay.parts_json.len > types.ProviderReplay.max_bytes or !std.unicode.utf8ValidateSlice(replay.parts_json)) return error.InvalidSessionFormat; return try types.dupeProviderReplay(alloc, replay);}
fn parseToolCalls(alloc: Allocator, value: std.json.Value) ![]session.ToolCall { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const calls = try alloc.alloc(session.ToolCall, value.array.items.len); errdefer mem_utils.free(alloc, calls); var parsed_count: usize = 0; errdefer for (calls[0..parsed_count]) |call| session.freeToolCall(alloc, call); for (value.array.items, 0..) |call_value, i| { calls[i] = try parseToolCall(alloc, call_value); parsed_count += 1; } return calls;}
fn parseToolCall(alloc: Allocator, value: std.json.Value) !session.ToolCall { const object = try exactObject(value, &.{ "id", "name", "arguments_json", "provider_result", }); const id = try parseRequiredDurableBytes(alloc, object, "id"); errdefer mem_utils.free(alloc, id); const name = try parseRequiredDurableBytes(alloc, object, "name"); errdefer mem_utils.free(alloc, name); var arguments_json = try parseRequiredDurableBytes(alloc, object, "arguments_json"); errdefer mem_utils.free(alloc, arguments_json); const argument_integrity = try types.ToolArgumentIntegrity.classifyFunctionInput(alloc, arguments_json); if (argument_integrity == .malformed_json) { const safe_arguments = try alloc.dupe(u8, "{}"); mem_utils.free(alloc, arguments_json); arguments_json = safe_arguments; } const provider_result = try parseOptionalDurableBytes( alloc, object.get("provider_result") orelse return error.InvalidSessionFormat, ); return .{ .id = id, .name = name, .arguments_json = arguments_json, .argument_integrity = argument_integrity, .provider_result = provider_result, };}
fn parseOptionalToolCall(alloc: Allocator, value: std.json.Value) !?session.ToolCall { return switch (value) { .null => null, .object => blk: { var call = try parseToolCall(alloc, value); errdefer session.freeToolCall(alloc, call); try session.repairPersistedInterruptedToolArguments(alloc, &call, .schema_v3); break :blk call; }, else => error.InvalidSessionFormat, };}
fn parseToolResults( alloc: Allocator, value: std.json.Value, schema_version: u64,) ![]session.PersistedToolResult { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const results = try alloc.alloc(session.PersistedToolResult, value.array.items.len); errdefer mem_utils.free(alloc, results); var parsed_count: usize = 0; errdefer for (results[0..parsed_count]) |result| { mem_utils.free(alloc, result.tool_call_id); mem_utils.free(alloc, result.tool_name); mem_utils.free(alloc, result.output); types.freeToolImages(alloc, result.tool_images); if (result.tool_image_handle) |handle| mem_utils.free(alloc, handle); if (result.output_handle) |handle| mem_utils.free(alloc, handle); if (result.preview) |preview| mem_utils.free(alloc, preview); types.freePermissionFeedback(alloc, result.permission_feedback); if (result.committed_file_presentation) |presentation| { types.freeCommittedFilePresentation(alloc, presentation); } if (result.command_output_replay) |replay| { types.freeCommandOutputReplay(alloc, replay); } }; for (value.array.items, 0..) |result_value, i| { results[i] = try parseToolResult(alloc, result_value, schema_version); parsed_count += 1; } return results;}
fn parseToolResult( alloc: Allocator, value: std.json.Value, schema_version: u64,) !session.PersistedToolResult { const v1_keys = &.{ "tool_call_id", "tool_name", "status", "output", "output_handle", "preview", "output_bytes", "stored_output_bytes", "truncated", "provider_native", "created_at_ms", }; const v2_keys = &.{ "tool_call_id", "tool_name", "status", "output", "output_handle", "preview", "output_bytes", "stored_output_bytes", "truncated", "provider_native", "created_at_ms", "permission_feedback", }; const v2_extended_keys = &.{ "tool_call_id", "tool_name", "status", "output", "output_handle", "preview", "output_bytes", "stored_output_bytes", "truncated", "provider_native", "created_at_ms", "permission_feedback", "committed_file_presentation", }; const v3_keys = &.{ "tool_call_id", "tool_name", "status", "output", "output_handle", "preview", "output_bytes", "stored_output_bytes", "truncated", "provider_native", "created_at_ms", "permission_feedback", "committed_file_presentation", "command_output_replay", "command_process_presentation", }; const v4_keys = &[_][]const u8{ "tool_call_id", "tool_name", "status", "output", "output_handle", "preview", "output_bytes", "stored_output_bytes", "truncated", "provider_native", "created_at_ms", "permission_feedback", "committed_file_presentation", "command_output_replay", "command_process_presentation", "terminal_action_presentation", }; const result_shape: ExactVariantObject = switch (schema_version) { 1 => .{ .object = try exactObject(value, v1_keys), .extended = false }, 2 => try exactVariantObject(value, v2_keys, v2_extended_keys), 3 => .{ .object = try exactObject(value, v3_keys), .extended = true }, 4, 5, 6, 7 => .{ .object = try exactObject(value, v4_keys), .extended = true }, 8, 9 => .{ .object = if (value == .object and value.object.contains("tool_images")) try exactObject(value, &(v4_keys.* ++ [_][]const u8{"tool_images"})) else if (value == .object and value.object.contains("tool_image_handle")) try exactObject(value, &(v4_keys.* ++ [_][]const u8{"tool_image_handle"})) else try exactObject(value, v4_keys), .extended = true }, 10 => blk: { const keys = v4_keys.* ++ [_][]const u8{"review_feedback"}; break :blk .{ .object = if (value == .object and value.object.contains("tool_images")) try exactObject(value, &(keys ++ [_][]const u8{"tool_images"})) else if (value == .object and value.object.contains("tool_image_handle")) try exactObject(value, &(keys ++ [_][]const u8{"tool_image_handle"})) else try exactObject(value, &keys), .extended = true }; }, else => return error.InvalidSessionFormat, }; const object = result_shape.object; const status = std.meta.stringToEnum( session.PersistedToolStatus, try requireString(object, "status"), ) orelse return error.InvalidSessionFormat; const provider_native = try requireBool(object, "provider_native"); const review_feedback = if (schema_version >= 10) try requireBool(object, "review_feedback") else false; if (review_feedback and (status != .failure or provider_native)) return error.InvalidSessionFormat; const tool_call_id = try parseRequiredDurableBytes(alloc, object, "tool_call_id"); errdefer mem_utils.free(alloc, tool_call_id); const tool_name = try parseRequiredDurableBytes(alloc, object, "tool_name"); errdefer mem_utils.free(alloc, tool_name); var output = try parseRequiredDurableBytes(alloc, object, "output"); errdefer mem_utils.free(alloc, output); const output_handle = try parseOptionalDurableBytes( alloc, object.get("output_handle") orelse return error.InvalidSessionFormat, ); errdefer if (output_handle) |owned| mem_utils.free(alloc, owned); const preview = try parseOptionalDurableBytes( alloc, object.get("preview") orelse return error.InvalidSessionFormat, ); errdefer if (preview) |owned| mem_utils.free(alloc, owned); const permission_feedback: [][]u8 = if (schema_version == 1) &.{} else try parseDurableBytesArray( alloc, object.get("permission_feedback") orelse return error.InvalidSessionFormat, ); errdefer types.freePermissionFeedback(alloc, permission_feedback); const committed_file_presentation = if (result_shape.extended) try parseOptionalCommittedFilePresentation( alloc, object.get("committed_file_presentation") orelse return error.InvalidSessionFormat, ) else null; errdefer if (committed_file_presentation) |presentation| { types.freeCommittedFilePresentation(alloc, presentation); }; const command_output_replay = if (schema_version >= 3) try parseOptionalCommandOutputReplay( alloc, object.get("command_output_replay") orelse return error.InvalidSessionFormat, ) else null; errdefer if (command_output_replay) |replay| { types.freeCommandOutputReplay(alloc, replay); }; const command_process_presentation = if (schema_version >= 3) try parseOptionalCommandProcessPresentation( object.get("command_process_presentation") orelse return error.InvalidSessionFormat, ) else null; const terminal_action_presentation = if (schema_version >= 4) try parseOptionalTerminalActionPresentation( object.get("terminal_action_presentation") orelse return error.InvalidSessionFormat, ) else null; const tool_image_handle = if (object.get("tool_image_handle")) |value_handle| try parseOptionalDurableBytes(alloc, value_handle) else null; errdefer if (tool_image_handle) |handle| mem_utils.free(alloc, handle); const tool_images = if (object.get("tool_images")) |images| images: { if (images != .array) return error.InvalidSessionFormat; const parsed_images = image_data.parseToolImages(alloc, images.array.items) catch |err| { if (err == error.OutOfMemory) return error.OutOfMemory; const notice = try std.fmt.allocPrint(alloc, "{s}\n[Saved tool image unavailable: {s}]", .{ output, @errorName(err) }); mem_utils.free(alloc, output); output = notice; break :images try alloc.alloc(types.ToolImage, 0); }; if (parsed_images.len != images.array.items.len) { types.freeToolImages(alloc, parsed_images); const notice = try std.fmt.allocPrint(alloc, "{s}\n[Saved tool image unavailable: unsupported content]", .{output}); mem_utils.free(alloc, output); output = notice; break :images try alloc.alloc(types.ToolImage, 0); } break :images parsed_images; } else try alloc.alloc(types.ToolImage, 0); errdefer types.freeToolImages(alloc, tool_images); return .{ .tool_images = tool_images, .tool_image_handle = tool_image_handle, .tool_call_id = tool_call_id, .tool_name = tool_name, .status = status, .output = output, .output_handle = output_handle, .preview = preview, .output_bytes = try requireUsize(object, "output_bytes"), .stored_output_bytes = try requireUsize(object, "stored_output_bytes"), .truncated = try requireBool(object, "truncated"), .provider_native = provider_native, .review_feedback = review_feedback, .created_at_ms = try requireI64(object, "created_at_ms"), .permission_feedback = permission_feedback, .committed_file_presentation = committed_file_presentation, .command_output_replay = command_output_replay, .command_process_presentation = command_process_presentation, .terminal_action_presentation = terminal_action_presentation, };}
fn parseOptionalCommandOutputReplay( alloc: Allocator, value: std.json.Value,) !?types.CommandOutputReplay { if (value == .null) return null; const object = switch (value) { .object => |object| object, else => return error.InvalidSessionFormat, }; const kind = try requireString(object, "kind"); if (std.mem.eql(u8, kind, "unavailable")) { _ = try exactObject(value, &.{"kind"}); return .unavailable; } if (!std.mem.eql(u8, kind, "available")) return error.InvalidSessionFormat; const exact = try exactObject(value, &.{ "kind", "handle", "framed_bytes" }); const handle = try parseRequiredDurableBytes(alloc, exact, "handle"); errdefer alloc.free(handle); return .{ .available = .{ .handle = handle, .framed_bytes = try requireUsize(exact, "framed_bytes"), } };}
fn parseOptionalCommandProcessPresentation( value: std.json.Value,) !?types.CommandProcessPresentation { if (value == .null) return null; const object = try exactObject(value, &.{ "kind", "value" }); const kind = try requireString(object, "kind"); if (std.mem.eql(u8, kind, "exit_code")) { return .{ .exit_code = try requireI64(object, "value") }; } if (std.mem.eql(u8, kind, "signal")) { const signal = try requireU64(object, "value"); return .{ .signal = std.math.cast(u32, signal) orelse return error.InvalidSessionFormat }; } if (std.mem.eql(u8, kind, "timed_out")) { if (object.get("value").? != .null) return error.InvalidSessionFormat; return .timed_out; } if (std.mem.eql(u8, kind, "output_capture_failed")) { if (object.get("value").? != .null) return error.InvalidSessionFormat; return .output_capture_failed; } return error.InvalidSessionFormat;}
fn parseOptionalTerminalActionPresentation( value: std.json.Value,) !?types.TerminalActionPresentation { if (value == .null) return null; const object = switch (value) { .object => |object| object, else => return error.InvalidSessionFormat, }; const kind = try requireString(object, "kind"); if (std.mem.eql(u8, kind, "returned")) { _ = try exactObject(value, &.{ "kind", "outcome" }); return .{ .returned = try parseTerminalReturnPresentation( object.get("outcome") orelse return error.InvalidSessionFormat, ) }; } if (std.mem.eql(u8, kind, "failed")) { _ = try exactObject(value, &.{ "kind", "code" }); const code = std.meta.stringToEnum( types.TerminalFailurePresentation, try requireString(object, "code"), ) orelse return error.InvalidSessionFormat; return .{ .failed = code }; } return error.InvalidSessionFormat;}
fn parseTerminalReturnPresentation( value: std.json.Value,) !types.TerminalReturnPresentation { const object = try exactObject(value, &.{ "kind", "value" }); const kind = try requireString(object, "kind"); if (std.mem.eql(u8, kind, "started") or std.mem.eql(u8, kind, "condition_met") or std.mem.eql(u8, kind, "safety_ceiling") or std.mem.eql(u8, kind, "cancelled")) { if (object.get("value").? != .null) return error.InvalidSessionFormat; if (std.mem.eql(u8, kind, "started")) return .started; if (std.mem.eql(u8, kind, "condition_met")) return .condition_met; if (std.mem.eql(u8, kind, "safety_ceiling")) return .safety_ceiling; return .cancelled; } if (std.mem.eql(u8, kind, "exited")) { return .{ .exited = std.math.cast(i32, try requireI64(object, "value")) orelse return error.InvalidSessionFormat }; } if (std.mem.eql(u8, kind, "signal")) { return .{ .signal = std.math.cast(u32, try requireU64(object, "value")) orelse return error.InvalidSessionFormat }; } return error.InvalidSessionFormat;}
fn parseOptionalCommittedFilePresentation( alloc: Allocator, value: std.json.Value,) !?types.CommittedFilePresentation { return switch (value) { .null => null, .object => try parseCommittedFilePresentation(alloc, value), else => error.InvalidSessionFormat, };}
fn parseCommittedFilePresentation( alloc: Allocator, value: std.json.Value,) !types.CommittedFilePresentation { const object = try exactObject(value, &.{ "path", "kind", "lines", "additions", "deletions", "truncated", "previous_content", "after_content", "lifecycle_id", }); const path = try parseRequiredDurableBytes(alloc, object, "path"); errdefer mem_utils.free(alloc, path); const lines = try parseCommittedFilePresentationLines( alloc, object.get("lines") orelse return error.InvalidSessionFormat, ); errdefer freeCommittedFilePresentationLines(alloc, lines); const previous_content = try parseOptionalDurableBytes( alloc, object.get("previous_content") orelse return error.InvalidSessionFormat, ); errdefer if (previous_content) |content| mem_utils.free(alloc, content); const after_content = try parseOptionalDurableBytes( alloc, object.get("after_content") orelse return error.InvalidSessionFormat, ); errdefer if (after_content) |content| mem_utils.free(alloc, content); const lifecycle_id = try parseOptionalToolLifecycleId( alloc, object.get("lifecycle_id") orelse return error.InvalidSessionFormat, ); errdefer if (lifecycle_id) |id| mem_utils.free(alloc, id.call_id); return .{ .path = path, .kind = std.meta.stringToEnum( types.CommittedFilePresentationKind, try requireString(object, "kind"), ) orelse return error.InvalidSessionFormat, .lines = lines, .additions = try requireUsize(object, "additions"), .deletions = try requireUsize(object, "deletions"), .truncated = try requireBool(object, "truncated"), .previous_content = previous_content, .after_content = after_content, .lifecycle_id = lifecycle_id, };}
fn parseCommittedFilePresentationLines( alloc: Allocator, value: std.json.Value,) ![]types.CommittedFilePresentationLine { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const lines = try alloc.alloc(types.CommittedFilePresentationLine, value.array.items.len); var parsed_count: usize = 0; errdefer { for (lines[0..parsed_count]) |line| mem_utils.free(alloc, @constCast(line.text)); mem_utils.free(alloc, lines); } for (value.array.items, 0..) |line_value, index| { const object = try exactObject(line_value, &.{ "kind", "old_line", "new_line", "text" }); const text = try parseRequiredDurableBytes(alloc, object, "text"); errdefer mem_utils.free(alloc, text); lines[index] = .{ .kind = std.meta.stringToEnum( types.CommittedFilePresentationLineKind, try requireString(object, "kind"), ) orelse return error.InvalidSessionFormat, .old_line = try parseOptionalU32(object.get("old_line") orelse return error.InvalidSessionFormat), .new_line = try parseOptionalU32(object.get("new_line") orelse return error.InvalidSessionFormat), .text = text, }; parsed_count += 1; } return lines;}
fn freeCommittedFilePresentationLines( alloc: Allocator, lines: []const types.CommittedFilePresentationLine,) void { for (lines) |line| mem_utils.free(alloc, @constCast(line.text)); if (lines.len > 0) mem_utils.free(alloc, @constCast(lines));}
fn parseOptionalToolLifecycleId( alloc: Allocator, value: std.json.Value,) !?types.ToolLifecycleId { return switch (value) { .null => null, .object => { const object = try exactObject(value, &.{ "turn_id", "call_id" }); const call_id = try parseRequiredDurableBytes(alloc, object, "call_id"); errdefer mem_utils.free(alloc, call_id); return .{ .turn_id = try requireU64(object, "turn_id"), .call_id = call_id, }; }, else => error.InvalidSessionFormat, };}
fn parseFiles(alloc: Allocator, value: std.json.Value) ![]session.FileEvidence { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const files = try alloc.alloc(session.FileEvidence, value.array.items.len); errdefer mem_utils.free(alloc, files); var parsed_count: usize = 0; errdefer for (files[0..parsed_count]) |file| { mem_utils.free(alloc, file.path); if (file.new_path) |new_path| mem_utils.free(alloc, new_path); mem_utils.free(alloc, file.tool_call_id); mem_utils.free(alloc, file.tool_name); }; for (value.array.items, 0..) |file_value, i| { files[i] = try parseFile(alloc, file_value); parsed_count += 1; } return files;}
fn parseFile(alloc: Allocator, value: std.json.Value) !session.FileEvidence { const object = try exactObject(value, &.{ "path", "new_path", "tool_call_id", "tool_name", "action", "status", "model_view_covers_full_file", "stale", }); const path = try parseRequiredDurableBytes(alloc, object, "path"); errdefer mem_utils.free(alloc, path); const new_path = try parseOptionalDurableBytes( alloc, object.get("new_path") orelse return error.InvalidSessionFormat, ); errdefer if (new_path) |owned| mem_utils.free(alloc, owned); const tool_call_id = try parseRequiredDurableBytes(alloc, object, "tool_call_id"); errdefer mem_utils.free(alloc, tool_call_id); const tool_name = try parseRequiredDurableBytes(alloc, object, "tool_name"); errdefer mem_utils.free(alloc, tool_name); return .{ .path = path, .new_path = new_path, .tool_call_id = tool_call_id, .tool_name = tool_name, .action = std.meta.stringToEnum( session.FileEvidenceAction, try requireString(object, "action"), ) orelse return error.InvalidSessionFormat, .status = std.meta.stringToEnum( session.PersistedToolStatus, try requireString(object, "status"), ) orelse return error.InvalidSessionFormat, .model_view_covers_full_file = try requireBool(object, "model_view_covers_full_file"), .stale = try requireBool(object, "stale"), };}
fn parseDurableBytesArray(alloc: Allocator, value: std.json.Value) ![][]u8 { if (value != .array) return error.InvalidSessionFormat; if (value.array.items.len == 0) return &.{}; const items = try alloc.alloc([]u8, value.array.items.len); errdefer alloc.free(items); var parsed_count: usize = 0; errdefer for (items[0..parsed_count]) |item| alloc.free(item); for (value.array.items, 0..) |item, i| { items[i] = try parseDurableBytes(alloc, item); parsed_count += 1; } return items;}
noinline fn parseRequiredDurableBytes(alloc: Allocator, object: std.json.ObjectMap, key: []const u8) ![]u8 { return parseDurableBytes(alloc, object.get(key) orelse return error.InvalidSessionFormat);}
noinline fn parseOptionalDurableBytes(alloc: Allocator, value: std.json.Value) !?[]u8 { return switch (value) { .null => null, .string, .object => try parseDurableBytes(alloc, value), else => error.InvalidSessionFormat, };}
fn parseOptionalIdentifier(value: std.json.Value) !?[16]u8 { return switch (value) { .null => null, .string => |hex| try parseHexIdentifier(hex), else => error.InvalidSessionFormat, };}
fn parseHexIdentifier(hex: []const u8) ![16]u8 { if (hex.len != 32) return error.InvalidSessionFormat; var result: [16]u8 = undefined; _ = std.fmt.hexToBytes(&result, hex) catch return error.InvalidSessionFormat; const canonical = std.fmt.bytesToHex(result, .lower); if (!std.mem.eql(u8, &canonical, hex)) return error.InvalidSessionFormat; return result;}
noinline fn writeOptionalDurableBytes(writer: *std.Io.Writer, value: anytype) !void { if (value) |bytes| { try writeDurableBytes(writer, bytes); } else { try writer.writeAll("null"); }}
fn writeOptionalU32(writer: *std.Io.Writer, value: ?u32) !void { if (value) |number| { try writer.print("{d}", .{number}); } else { try writer.writeAll("null"); }}
noinline fn writeJsonString(writer: *std.Io.Writer, bytes: []const u8) !void { try std.json.Stringify.value(bytes, .{}, writer);}
noinline fn validateSessionId(value: []const u8) !void { if (value.len == 0 or value.len > 255 or std.mem.eql(u8, value, ".") or std.mem.eql(u8, value, "..")) { return error.InvalidDurableField; } for (value) |byte| { if (!std.ascii.isAlphanumeric(byte) and byte != '.' and byte != '_' and byte != '-') { return error.InvalidDurableField; } }}
fn validateWorkspaceRoot(value: []const u8) !void { if (value.len == 0 or value.len > std.Io.Dir.max_path_bytes or !std.unicode.utf8ValidateSlice(value) or !std.Io.Dir.path.isAbsolute(value)) { return error.InvalidDurableField; }}
fn validateModel(value: []const u8) !void { if (value.len == 0 or value.len > 1024 or !std.unicode.utf8ValidateSlice(value)) { return error.InvalidDurableField; } if (std.ascii.isWhitespace(value[0]) or std.ascii.isWhitespace(value[value.len - 1])) { return error.InvalidDurableField; } for (value) |byte| if (std.ascii.isControl(byte)) return error.InvalidDurableField;}
fn validateConversationLanguage(language: session.ConversationLanguage) !void { try validateConversationLanguageBytes(language.view());}
fn validateConversationLanguageBytes(value: []const u8) !void { if (value.len == 0 or value.len > session.ConversationLanguage.max_len or !std.unicode.utf8ValidateSlice(value) or !std.mem.eql(u8, value, std.mem.trim(u8, value, " \t\r\n"))) { return error.InvalidDurableField; } for (value) |byte| if (std.ascii.isControl(byte)) return error.InvalidDurableField;}
fn dupeHistory(alloc: Allocator, history: []const session.HistoryTurn) ![]session.HistoryTurn { if (history.len == 0) return &.{}; const copy = try alloc.alloc(session.HistoryTurn, history.len); errdefer alloc.free(copy); var copied: usize = 0; errdefer for (copy[0..copied]) |turn| session.freeHistoryTurn(alloc, turn); for (history, 0..) |turn, i| { copy[i] = try session.dupeHistoryTurn(alloc, turn); copied += 1; } return copy;}
fn expectKey(reader: *std.json.Reader, alloc: Allocator, expected: []const u8) !void { const token = try reader.nextAllocMax(alloc, .alloc_if_needed, 64); defer freeToken(alloc, token); const actual = switch (token) { .string => |value| value, .allocated_string => |value| value, else => return error.InvalidSessionFormat, }; if (!std.mem.eql(u8, actual, expected)) return error.InvalidSessionFormat;}
fn readStringOwned(reader: *std.json.Reader, alloc: Allocator, max_len: usize) ![]u8 { const token = try reader.nextAllocMax(alloc, .alloc_always, max_len); return switch (token) { .allocated_string => |value| value, .string => |value| try alloc.dupe(u8, value), else => { freeToken(alloc, token); return error.InvalidSessionFormat; }, };}
fn readI64(reader: *std.json.Reader, alloc: Allocator) !i64 { const token = try reader.nextAllocMax(alloc, .alloc_if_needed, 32); defer freeToken(alloc, token); const raw = switch (token) { .number => |value| value, .allocated_number => |value| value, else => return error.InvalidSessionFormat, }; return std.fmt.parseInt(i64, raw, 10) catch error.InvalidSessionFormat;}
fn readU64(reader: *std.json.Reader, alloc: Allocator) !u64 { const token = try reader.nextAllocMax(alloc, .alloc_if_needed, 32); defer freeToken(alloc, token); const raw = switch (token) { .number => |value| value, .allocated_number => |value| value, else => return error.InvalidSessionFormat, }; return std.fmt.parseUnsigned(u64, raw, 10) catch error.InvalidSessionFormat;}
fn readBool(reader: *std.json.Reader) !bool { return switch (try reader.next()) { .true => true, .false => false, else => error.InvalidSessionFormat, };}
fn freeToken(alloc: Allocator, token: std.json.Token) void { switch (token) { .allocated_number => |value| alloc.free(value), .allocated_string => |value| alloc.free(value), else => {}, }}
fn expectToken(actual: std.json.Token, comptime expected: std.meta.Tag(std.json.Token)) !void { if (std.meta.activeTag(actual) != expected) return error.InvalidSessionFormat;}
noinline fn requireObject(value: std.json.Value) !std.json.ObjectMap { if (value != .object) return error.InvalidSessionFormat; return value.object;}
pub fn exactObject(value: std.json.Value, keys: []const []const u8) !std.json.ObjectMap { const object = try requireObject(value); if (object.count() != keys.len) return error.InvalidSessionFormat; var iterator = object.iterator(); while (iterator.next()) |entry| { var known = false; for (keys) |key| { if (std.mem.eql(u8, entry.key_ptr.*, key)) { known = true; break; } } if (!known) return error.InvalidSessionFormat; } return object;}
fn exactInterruptedHistoryObject( value: std.json.Value, has_execution: bool, has_presentation: bool, has_terminal_reason: bool, has_cancellation_origin: bool,) !std.json.ObjectMap { var keys: [9][]const u8 = undefined; keys[0] = "kind"; keys[1] = "user"; keys[2] = "assistant"; keys[3] = "tool_call"; keys[4] = "completed_tool_names"; var len: usize = 5; if (has_execution) { keys[len] = "execution"; len += 1; } if (has_presentation) { keys[len] = "cancelled_command"; len += 1; } if (has_terminal_reason) { keys[len] = "terminal_reason"; len += 1; } if (has_cancellation_origin) { keys[len] = "cancellation_origin"; len += 1; } return exactObject(value, keys[0..len]);}
fn parseInterruptedTerminalReason( object: std.json.ObjectMap, key: []const u8,) !types.InterruptedTerminalReason { const raw = try requireString(object, key); if (std.mem.eql(u8, raw, "cancelled")) return .cancelled; if (std.mem.eql(u8, raw, "failed")) return .failed; return error.InvalidSessionFormat;}
const ExactVariantObject = struct { object: std.json.ObjectMap, extended: bool,};
fn exactVariantObject( value: std.json.Value, legacy_keys: []const []const u8, extended_keys: []const []const u8,) !ExactVariantObject { const object = try requireObject(value); if (object.count() == legacy_keys.len) { return .{ .object = try exactObject(value, legacy_keys), .extended = false, }; } if (object.count() == extended_keys.len) { return .{ .object = try exactObject(value, extended_keys), .extended = true, }; } return error.InvalidSessionFormat;}
pub fn requireString(object: std.json.ObjectMap, key: []const u8) ![]const u8 { const value = object.get(key) orelse return error.InvalidSessionFormat; if (value != .string) return error.InvalidSessionFormat; return value.string;}
fn requireBool(object: std.json.ObjectMap, key: []const u8) !bool { const value = object.get(key) orelse return error.InvalidSessionFormat; if (value != .bool) return error.InvalidSessionFormat; return value.bool;}
fn requireI64(object: std.json.ObjectMap, key: []const u8) !i64 { const value = object.get(key) orelse return error.InvalidSessionFormat; return switch (value) { .integer => |number| number, .number_string => |raw| std.fmt.parseInt(i64, raw, 10) catch error.InvalidSessionFormat, else => error.InvalidSessionFormat, };}
fn requireU64(object: std.json.ObjectMap, key: []const u8) !u64 { const value = object.get(key) orelse return error.InvalidSessionFormat; return switch (value) { .integer => |number| if (number >= 0) @intCast(number) else error.InvalidSessionFormat, .number_string => |raw| std.fmt.parseUnsigned(u64, raw, 10) catch error.InvalidSessionFormat, else => error.InvalidSessionFormat, };}
noinline fn requireUsize(object: std.json.ObjectMap, key: []const u8) !usize { const number = try requireU64(object, key); if (number > std.math.maxInt(usize)) return error.InvalidSessionFormat; return @intCast(number);}
fn parseOptionalU32(value: std.json.Value) !?u32 { const number: u64 = switch (value) { .null => return null, .integer => |integer| if (integer >= 0) @intCast(integer) else return error.InvalidSessionFormat, .number_string => |raw| std.fmt.parseUnsigned(u64, raw, 10) catch return error.InvalidSessionFormat, else => return error.InvalidSessionFormat, }; if (number > std.math.maxInt(u32)) return error.InvalidSessionFormat; return @intCast(number);}
test "durable byte fields use canonical string or padded base64 representation" { const alloc = std.testing.allocator;
var utf8_out: std.Io.Writer.Allocating = .init(alloc); defer utf8_out.deinit(); try writeDurableBytes(&utf8_out.writer, "hello\nworld"); try std.testing.expectEqualStrings("\"hello\\nworld\"", utf8_out.written());
const invalid = [_]u8{ 0xff, 0x00 }; var binary_out: std.Io.Writer.Allocating = .init(alloc); defer binary_out.deinit(); try writeDurableBytes(&binary_out.writer, &invalid); try std.testing.expectEqualStrings("{\"encoding\":\"base64\",\"data\":\"/wA=\"}", binary_out.written());
var parsed = try std.json.parseFromSlice(std.json.Value, alloc, binary_out.written(), .{}); defer parsed.deinit(); const decoded = try parseDurableBytes(alloc, parsed.value); defer alloc.free(decoded); try std.testing.expectEqualSlices(u8, &invalid, decoded);
const invalid_values = [_][]const u8{ "{\"encoding\":\"base64\",\"data\":\"/wA\"}", "{\"encoding\":\"base64\",\"data\":\"***=\"}", "{\"encoding\":\"base64\",\"data\":\"aGk=\"}", "{\"data\":\"/wA=\",\"encoding\":\"base64\"}", "{\"encoding\":\"base64\",\"data\":\"/wA=\",\"extra\":true}", }; for (invalid_values) |json_text| { var bad = try std.json.parseFromSlice(std.json.Value, alloc, json_text, .{}); defer bad.deinit(); try std.testing.expectError(error.InvalidDurableBytes, parseDurableBytes(alloc, bad.value)); }}
test "durable state round trips live history while discarding legacy authority" { const alloc = std.testing.allocator; const invalid_a = [_]u8{ 'a', 0xff }; const invalid_b = [_]u8{ 0xfe, 'b', 0x00 }; const invalid_c = [_]u8{ 'c', 0xf8 }; const exact_arguments = " \n{\"number\":1e+02}\t"; var images = [_]session.ImageAttachment{.{ .id = 7, .path = @constCast(invalid_a[0..]), .media_type = @constCast(invalid_b[0..]), .snapshot_path = @constCast(invalid_c[0..]), .snapshot_sha256 = @constCast(invalid_a[0..]), }}; var tool_calls = [_]session.ToolCall{.{ .id = invalid_a[0..], .name = invalid_b[0..], .arguments_json = @constCast(exact_arguments), .provider_result = invalid_a[0..], }}; var tool_results = [_]session.PersistedToolResult{.{ .tool_call_id = @constCast(invalid_a[0..]), .tool_name = @constCast(invalid_b[0..]), .status = .failure, .output = @constCast(invalid_c[0..]), .output_handle = @constCast(invalid_a[0..]), .preview = @constCast(invalid_b[0..]), .output_bytes = 99, .stored_output_bytes = 42, .truncated = true, .provider_native = true, .created_at_ms = 123, }}; var tool_steps = [_]session.ToolExecutionStep{.{ .assistant = @constCast(invalid_c[0..]), .tool_calls = tool_calls[0..], .tool_results = tool_results[0..], }}; var files = [_]session.FileEvidence{.{ .path = @constCast(invalid_a[0..]), .new_path = @constCast(invalid_b[0..]), .tool_call_id = @constCast(invalid_c[0..]), .tool_name = @constCast(invalid_a[0..]), .action = .rename, .status = .failure, .model_view_covers_full_file = true, .stale = true, }}; var completed_tool_names = [_][]u8{ @constCast(invalid_a[0..]), @constCast(invalid_b[0..]), }; const history = [_]session.HistoryTurn{ .{ .assistant = .{ .user = .{ .text = @constCast(invalid_a[0..]), .images = images[0..], .work_id = @constCast("work-assistant"), }, .assistant = @constCast(invalid_b[0..]), .execution = .{ .tool_steps = tool_steps[0..], .files = files[0..], }, } }, .{ .assistant = .{ .user = .{ .text = @constCast(invalid_b[0..]), .work_id = @constCast("work-background"), }, .assistant = @constCast(invalid_a[0..]), .execution = .{ .tool_steps = tool_steps[0..], .files = files[0..], }, } }, .{ .interrupted = .{ .user = .{ .text = @constCast(invalid_c[0..]), .work_id = @constCast("work-interrupted"), }, .assistant = @constCast(invalid_a[0..]), .tool_call = tool_calls[0], .completed_tool_names = completed_tool_names[0..], .execution = .{ .tool_steps = tool_steps[0..], .files = files[0..], }, } }, .{ .compacted_summary = .{ .summary = @constCast(invalid_b[0..]), .removed_turn_count = 3, .compaction_count = 2, .root_user_messages = completed_tool_names[0..], } }, }; var usage_runtime = session_usage.Usage.initFresh(); defer usage_runtime.deinit(alloc); const usage_sequence = try usage_runtime.reserveInvocation(); try usage_runtime.finishObservedInvocation( alloc, usage_sequence, 25, .observed_generation, "gen_01ARZ3NDEKTSV4RRFFQ69G5FAV", "https://ai-gateway.vercel.sh", null, ); var usage = try usage_runtime.snapshot(alloc); defer usage.deinit(alloc); const state = DurableSessionState{ .id = @constCast("1700000000000-42-session"), .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 100, .updated_at_ms = 90, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .model = @constCast("openai/gpt-test"), .effort = types.ReasoningEffort.literal("future-tier"), .fast_mode = true, }, .history = @constCast(history[0..]), .context_history_start = 3, .total_input_tokens = 1234, .total_output_tokens = 567, .last_subagent_work_id = @constCast("work-17"), .subagent_child = true, .usage = usage, };
var first: std.Io.Writer.Allocating = .init(alloc); defer first.deinit(); const first_summary = try encodeState(state, &first.writer); try std.testing.expectEqual(@as(u64, first.written().len), first_summary.encoded_bytes); try std.testing.expect(first.written().len > 0); try std.testing.expect(first.written()[first.written().len - 1] != '\n');
var second: std.Io.Writer.Allocating = .init(alloc); defer second.deinit(); _ = try encodeState(state, &second.writer); try std.testing.expectEqualStrings(first.written(), second.written());
var source = std.Io.Reader.fixed(first.written()); var decoded = try decodeState(alloc, &source, .{}); defer decoded.deinit(alloc); try expectStateEqual(state, decoded);}
test "durable state repairs duplicate-key execution and interrupted tool arguments" { const alloc = std.testing.allocator; const duplicate_arguments = "{\"depth\":1,\"depth\":2}";
var calls = [_]session.ToolCall{.{ .id = "call_bad", .name = "read_file", .arguments_json = duplicate_arguments, }}; var results = [_]session.PersistedToolResult{.{ .tool_call_id = @constCast("call_bad"), .tool_name = @constCast("read_file"), .status = .success, .output = @constCast("stale success"), .output_bytes = 13, .stored_output_bytes = 13, .truncated = false, .provider_native = false, .created_at_ms = 1, }}; var steps = [_]session.ToolExecutionStep{.{ .tool_calls = calls[0..], .tool_results = results[0..], }}; const history = [_]session.HistoryTurn{ .{ .assistant = .{ .user = .{ .text = @constCast("inspect") }, .assistant = @constCast("failed"), .execution = .{ .tool_steps = steps[0..] }, } }, .{ .interrupted = .{ .user = .{ .text = @constCast("continue") }, .tool_call = .{ .id = "call_interrupted", .name = "glob_files", .arguments_json = duplicate_arguments, }, } }, }; const state = DurableSessionState{ .id = @constCast("session-1"), .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .model = @constCast("openai/gpt-test"), .effort = .auto, .fast_mode = false, }, .history = @constCast(history[0..]), .total_input_tokens = 3, .total_output_tokens = 4, };
var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); _ = try encodeState(state, &encoded.writer);
var source = std.Io.Reader.fixed(encoded.written()); var decoded = try decodeState(alloc, &source, .{}); defer decoded.deinit(alloc);
const decoded_step = decoded.history[0].assistant.execution.tool_steps[0]; try std.testing.expectEqualStrings("{}", decoded_step.tool_calls[0].arguments_json); try std.testing.expectEqual(types.ToolArgumentIntegrity.valid, decoded_step.tool_calls[0].argument_integrity); try std.testing.expectEqual(session.PersistedToolStatus.failure, decoded_step.tool_results[0].status); try std.testing.expect(std.mem.find(u8, decoded_step.tool_results[0].output, "tool_execution_failed") != null); try std.testing.expect(std.mem.find(u8, decoded_step.tool_results[0].output, duplicate_arguments) == null);
const interrupted = decoded.history[1].interrupted.tool_call.?; try std.testing.expectEqualStrings("{}", interrupted.arguments_json); try std.testing.expectEqual(types.ToolArgumentIntegrity.valid, interrupted.argument_integrity);}
test "non-object saved function inputs are repaired without changing recorded outcomes" { const alloc = std.testing.allocator; for ([_][]const u8{ "[]", "42", "null", "true", "\"text\"" }) |arguments| { for ([_]session.PersistedToolStatus{ .success, .failure }) |status| { for (0..3) |native_kind| { var calls = [_]session.ToolCall{.{ .id = "call", .name = "read_file", .arguments_json = arguments }}; calls[0].provider_result = if (native_kind == 1) "provider result" else null; var results = [_]session.PersistedToolResult{persistedResultForTest("call", "read_file")}; results[0].status = status; results[0].provider_native = native_kind == 2; results[0].output_handle = @constCast("retained-handle"); results[0].preview = @constCast("retained-preview"); var steps = [_]session.ToolExecutionStep{.{ .tool_calls = &calls, .tool_results = &results }}; const turn: session.HistoryTurn = .{ .assistant = .{ .user = .{ .text = @constCast("read") }, .assistant = @constCast("done"), .execution = .{ .tool_steps = &steps }, } }; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); const step = decoded.assistant.execution.tool_steps[0]; try std.testing.expectEqualStrings(if (native_kind == 0) "{}" else arguments, step.tool_calls[0].arguments_json); try std.testing.expectEqual(if (native_kind == 0) types.ToolExecutionProvenance.fx_local else .provider_executed, step.tool_calls[0].provenance); try std.testing.expectEqual(types.ToolArgumentIntegrity.valid, step.tool_calls[0].argument_integrity); try std.testing.expectEqual(status, step.tool_results[0].status); try std.testing.expectEqualStrings("stale", step.tool_results[0].output); try std.testing.expectEqualStrings("retained-handle", step.tool_results[0].output_handle.?); try std.testing.expectEqualStrings("retained-preview", step.tool_results[0].preview.?); try std.testing.expectEqualStrings(arguments, calls[0].arguments_json); try session.repairPersistedToolArguments(alloc, step.tool_calls, step.tool_results, .schema_v3); try std.testing.expectEqualStrings(if (native_kind == 0) "{}" else arguments, step.tool_calls[0].arguments_json); if (native_kind == 0 and status == .failure and std.mem.eql(u8, arguments, "[]")) { try std.testing.checkAllAllocationFailures(alloc, checkNonObjectHistoryAllocationFailures, .{parsed.value}); } } } }}
fn checkNonObjectHistoryAllocationFailures(alloc: Allocator, value: std.json.Value) !void { const decoded = try parseHistoryTurn(alloc, value); defer session.freeHistoryTurn(alloc, decoded); try std.testing.expectEqualStrings("{}", decoded.assistant.execution.tool_steps[0].tool_calls[0].arguments_json); try std.testing.expectEqual(session.PersistedToolStatus.failure, decoded.assistant.execution.tool_steps[0].tool_results[0].status);}
test "non-object interrupted inputs repair locally and preserve provider-owned records" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |native| { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try writeToolCall(&out.writer, .{ .id = "interrupted", .name = "read_file", .arguments_json = "[]", .provider_result = if (native) "provider result" else null, }); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, out.written(), .{}); defer parsed.deinit(); const repaired = (try parseOptionalToolCall(alloc, parsed.value)).?; defer session.freeToolCall(alloc, repaired); try std.testing.expectEqualStrings(if (native) "[]" else "{}", repaired.arguments_json); if (!native) try std.testing.checkAllAllocationFailures(alloc, checkNonObjectInterruptedAllocationFailures, .{parsed.value}); }}
fn checkNonObjectInterruptedAllocationFailures(alloc: Allocator, value: std.json.Value) !void { const call = (try parseOptionalToolCall(alloc, value)).?; defer session.freeToolCall(alloc, call); try std.testing.expectEqualStrings("{}", call.arguments_json);}
test "current history decode rejects ambiguous malformed tool result pairings" { var duplicate_calls = [_]session.ToolCall{ .{ .id = "call_bad", .name = "read_file", .arguments_json = "{]" }, .{ .id = "call_bad", .name = "read_file", .arguments_json = "{]" }, }; var one_result = [_]session.PersistedToolResult{persistedResultForTest("call_bad", "read_file")}; try expectMalformedPairRejected(duplicate_calls[0..], one_result[0..]);
var missing_call = [_]session.ToolCall{ .{ .id = "call_bad", .name = "read_file", .arguments_json = "{]" }, }; var no_results = [_]session.PersistedToolResult{}; try expectMalformedPairRejected(missing_call[0..], no_results[0..]);
var multiple_call = [_]session.ToolCall{ .{ .id = "call_bad", .name = "read_file", .arguments_json = "{]" }, }; var multiple_results = [_]session.PersistedToolResult{ persistedResultForTest("call_bad", "read_file"), persistedResultForTest("call_bad", "read_file"), }; try expectMalformedPairRejected(multiple_call[0..], multiple_results[0..]);
var mismatched_call = [_]session.ToolCall{ .{ .id = "call_bad", .name = "read_file", .arguments_json = "{]" }, }; var mismatched_result = [_]session.PersistedToolResult{persistedResultForTest("call_bad", "glob_files")}; try expectMalformedPairRejected(mismatched_call[0..], mismatched_result[0..]);}
test "current history duplicate-key repair preserves allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkCurrentToolArgumentRepairAllocationFailures, .{}, );}
test "legacy durable state defaults context history start to zero" { const alloc = std.testing.allocator; const legacy = "{\"id\":\"legacy\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\"," ++ "\"created_at_ms\":1,\"updated_at_ms\":2,\"conversation_language\":\"en\"," ++ "\"preferences\":{\"model\":\"model\",\"effort\":\"auto\",\"fast_mode\":false}," ++ "\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}"; var source = std.Io.Reader.fixed(legacy); var decoded = try decodeState(alloc, &source, .{}); defer decoded.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), decoded.context_history_start);
const invalid = "{\"id\":\"invalid\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\"," ++ "\"created_at_ms\":1,\"updated_at_ms\":2,\"conversation_language\":\"en\"," ++ "\"preferences\":{\"model\":\"model\",\"effort\":\"auto\",\"fast_mode\":false}," ++ "\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0,\"context_history_start\":1}"; var invalid_source = std.Io.Reader.fixed(invalid); try std.testing.expectError( error.InvalidDurableField, decodeState(alloc, &invalid_source, .{}), );}
test "per-turn work provenance is strict and ordinary state omits it" { const alloc = std.testing.allocator; const ordinary = "{\"id\":\"session\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"test/model\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[{\"kind\":\"assistant\",\"user\":{\"text\":\"hello\",\"images\":[]},\"assistant\":\"world\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}],\"total_input_tokens\":0,\"total_output_tokens\":0}"; var ordinary_source = std.Io.Reader.fixed(ordinary); var state = try decodeState(alloc, &ordinary_source, .{}); defer state.deinit(alloc); try std.testing.expect(state.history[0].assistant.user.work_id == null);
var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); _ = try encodeState(state, &encoded.writer); try std.testing.expect(std.mem.find(u8, encoded.written(), "work_id") == null);
const invalid = [_][]const u8{ "{\"kind\":\"assistant\",\"user\":{\"text\":\"hello\",\"images\":[],\"work_id\":\"\"},\"assistant\":\"world\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}", "{\"kind\":\"assistant\",\"user\":{\"text\":\"hello\",\"images\":[],\"work_id\":null},\"assistant\":\"world\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}", "{\"kind\":\"assistant\",\"user\":{\"text\":\"hello\",\"images\":[],\"work_id\":\"bad\\u0000id\"},\"assistant\":\"world\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}", }; for (invalid) |json| { var parsed = try std.json.parseFromSlice(std.json.Value, alloc, json, .{}); defer parsed.deinit(); try std.testing.expectError( error.InvalidSessionFormat, parseHistoryTurn(alloc, parsed.value), ); }
var too_long: [session.max_work_id_bytes + 1]u8 = undefined; @memset(&too_long, 'x'); state.history[0].assistant.user.work_id = &too_long; try std.testing.expectError(error.InvalidDurableField, validateState(state)); state.history[0].assistant.user.work_id = null;}
test "durable session IDs accept safe opaque basenames" { const valid_ids = [_][]const u8{ "session.v3", ".hidden-session", "session..branch", }; for (valid_ids) |id| try validateSessionId(id);
var max_id: [255]u8 = undefined; @memset(&max_id, 'a'); try validateSessionId(&max_id);
const state = DurableSessionState{ .id = max_id[0..], .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .model = @constCast("model"), .effort = .auto, .fast_mode = false, }, .history = &.{}, .total_input_tokens = 0, .total_output_tokens = 0, }; var encoded: std.Io.Writer.Allocating = .init(std.testing.allocator); defer encoded.deinit(); _ = try encodeState(state, &encoded.writer);
var source = std.Io.Reader.fixed(encoded.written()); var decoded = try decodeState(std.testing.allocator, &source, .{}); defer decoded.deinit(std.testing.allocator); try std.testing.expectEqualSlices(u8, &max_id, decoded.id);}
test "durable session IDs reject unsafe basenames" { const invalid_ids = [_][]const u8{ "", ".", "..", "path/session", "path\\session", "session:name", "session name", }; for (invalid_ids) |id| { try std.testing.expectError(error.InvalidDurableField, validateSessionId(id)); }
const unsupported = [_]u8{ 0xff, 'a' }; try std.testing.expectError(error.InvalidDurableField, validateSessionId(&unsupported));
var too_long: [256]u8 = undefined; @memset(&too_long, 'a'); try std.testing.expectError(error.InvalidDurableField, validateSessionId(&too_long));}
test "durable state rejects invalid metadata fields before returning" { const alloc = std.testing.allocator; const invalid_documents = [_][]const u8{ "{\"id\":\"../bad\",\"origin_workspace_root\":\"/tmp/a\",\"workspace_root\":\"/tmp/a\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"m\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", "{\"id\":\"good\",\"origin_workspace_root\":\"relative\",\"workspace_root\":\"/tmp/a\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"m\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", "{\"id\":\"good\",\"origin_workspace_root\":\"/tmp/a\",\"workspace_root\":\"/tmp/a\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\" en \",\"preferences\":{\"model\":\"m\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", "{\"id\":\"good\",\"origin_workspace_root\":\"/tmp/a\",\"workspace_root\":\"/tmp/a\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"m\",\"effort\":\"not valid\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", "{\"id\":\"good\",\"origin_workspace_root\":\"/tmp/a\",\"workspace_root\":\"/tmp/a\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\" m\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", };
for (invalid_documents) |document| { var source = std.Io.Reader.fixed(document); try std.testing.expectError(error.InvalidDurableField, decodeState(alloc, &source, .{})); }}
test "review feedback survives durable execution memory round trip" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |review_feedback| { var calls = [_]session.ToolCall{.{ .id = "call-review", .name = "shell", .arguments_json = "{}", }}; var results = [_]session.PersistedToolResult{.{ .tool_call_id = @constCast("call-review"), .tool_name = @constCast("shell"), .status = .failure, .output = @constCast("Security review held this action."), .output_bytes = "Security review held this action.".len, .stored_output_bytes = "Security review held this action.".len, .review_feedback = review_feedback, }}; var steps = [_]session.ToolExecutionStep{.{ .tool_calls = &calls, .tool_results = &results }}; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeExecutionMemory(&encoded.writer, .{ .tool_steps = &steps }); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const decoded = try parseExecutionMemory(alloc, parsed.value); defer session.freeExecutionMemory(alloc, decoded); const result = decoded.tool_steps[0].tool_results[0]; try std.testing.expectEqual(review_feedback, result.review_feedback); try std.testing.expectEqualStrings("Security review held this action.", result.output); try std.testing.expectEqual(session.PersistedToolStatus.failure, result.status); }}
test "review feedback durable codec rejects invalid provenance and unknown fields" { const alloc = std.testing.allocator; var result = persistedResultForTest("call-review", "shell"); result.status = .failure; result.review_feedback = true; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writePersistedToolResult(&encoded.writer, result); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const cases = [_]struct { status: []const u8, provider_native: bool, marker: std.json.Value }{ .{ .status = "success", .provider_native = false, .marker = .{ .bool = true } }, .{ .status = "failure", .provider_native = true, .marker = .{ .bool = true } }, .{ .status = "failure", .provider_native = false, .marker = .null }, .{ .status = "failure", .provider_native = false, .marker = .{ .integer = 1 } }, .{ .status = "failure", .provider_native = false, .marker = .{ .string = "true" } }, }; for (cases) |case| { parsed.value.object.getPtr("status").?.* = .{ .string = case.status }; parsed.value.object.getPtr("provider_native").?.* = .{ .bool = case.provider_native }; parsed.value.object.getPtr("review_feedback").?.* = case.marker; try std.testing.expectError(error.InvalidSessionFormat, parseToolResult(alloc, parsed.value, 10)); } parsed.value.object.getPtr("status").?.* = .{ .string = "failure" }; parsed.value.object.getPtr("review_feedback").?.* = .{ .bool = true }; try parsed.value.object.put(parsed.arena.allocator(), "unknown_feedback", .{ .bool = true }); try std.testing.expectError(error.InvalidSessionFormat, parseToolResult(alloc, parsed.value, 10)); for ([_]bool{ false, true }) |native| { var invalid = result; invalid.status = if (native) .failure else .success; invalid.provider_native = native; try std.testing.expectError(error.InvalidSessionFormat, writePersistedToolResult(&encoded.writer, invalid)); }}
test "review feedback defaults old durable results without inspecting output" { const alloc = std.testing.allocator; var result = persistedResultForTest("call-review", "shell"); result.status = .failure; result.output = @constCast("Security review held this action."); var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writePersistedToolResult(&encoded.writer, result); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); try std.testing.expect(parsed.value.object.swapRemove("review_feedback")); for ([_]u64{ 8, 9 }) |version| { const decoded = try parseToolResult(alloc, parsed.value, version); defer { alloc.free(decoded.tool_call_id); alloc.free(decoded.tool_name); alloc.free(decoded.output); types.freeToolImages(alloc, decoded.tool_images); } try std.testing.expect(!decoded.review_feedback); try std.testing.expectEqualStrings("Security review held this action.", decoded.output); } try parsed.value.object.put(parsed.arena.allocator(), "review_feedback", .{ .bool = true }); try std.testing.expectError(error.InvalidSessionFormat, parseToolResult(alloc, parsed.value, 9));}
test "execution memory codec preserves feedback and reads v1 results without it" { const alloc = std.testing.allocator; const provider_state = types.ProviderReplay{ .source = .{ .provider = .gateway, .model = "test" }, .parts_json = "[{\"type\":\"reasoning\",\"text\":\"kept\"}]" }; var feedback = [_][]u8{@constCast("read it after writing")}; const presentation_lines = [_]types.CommittedFilePresentationLine{ .{ .kind = .addition, .new_line = 1, .text = "persisted line" }, .{ .kind = .notice, .text = "… 118 lines omitted" }, }; var calls = [_]session.ToolCall{.{ .id = @constCast("call_feedback"), .name = @constCast("write_file"), .arguments_json = @constCast("{\"path\":\"note.txt\"}"), }}; var results = [_]session.PersistedToolResult{.{ .tool_call_id = @constCast("call_feedback"), .tool_name = @constCast("write_file"), .status = .success, .output = @constCast("wrote note.txt"), .tool_image_handle = @constCast("saved-tool-image"), .output_bytes = 15, .stored_output_bytes = 15, .permission_feedback = feedback[0..], .committed_file_presentation = .{ .path = "note.txt", .kind = .added, .lines = presentation_lines[0..], .additions = 120, .deletions = 0, .truncated = true, .after_content = "persisted line\n", .lifecycle_id = .{ .turn_id = 9, .call_id = "call_feedback", }, }, .command_output_replay = .{ .available = .{ .handle = "fx-command-replay-private.bin", .framed_bytes = 321, } }, .command_process_presentation = .{ .exit_code = 7 }, }}; var steps = [_]session.ToolExecutionStep{.{ .provider_replay = provider_state, .tool_calls = calls[0..], .tool_results = results[0..], }}; var steering = [_]types.PersistedSteering{.{ .text = @constCast("focus on persistence"), .assistant_prefix = @constCast("partial assistant"), .after_tool_step_count = 1, }}; const turn: session.HistoryTurn = .{ .assistant = .{ .provider_replay = provider_state, .user = .{ .text = @constCast("write a note") }, .assistant = @constCast("done"), .execution = .{ .tool_steps = steps[0..], .steering = steering[0..], .turn_summary = .{ .started_at_ms = 100, .completed_at_ms = 250, .thinking_duration_ms = 40, .turn_duration_ms = 150, .token_progress = .{ .input_tokens = 12, .output_tokens = 34 }, }, }, } };
var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"schema_version\":10") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"text\":\"focus on persistence\"") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"assistant_prefix\":\"partial assistant\"") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"after_tool_step_count\":1") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"permission_feedback\"") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"committed_file_presentation\"") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "\"command_output_replay\"") != null); try std.testing.expect(std.mem.find(u8, encoded.written(), "fx-command-replay-private.bin") != null);
var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); const decoded_result = decoded.assistant.execution.tool_steps[0].tool_results[0]; try std.testing.expectEqualStrings(provider_state.parts_json, decoded.assistant.provider_replay.?.parts_json); try std.testing.expectEqualStrings(provider_state.parts_json, decoded.assistant.execution.tool_steps[0].provider_replay.?.parts_json); try std.testing.expectEqualStrings("saved-tool-image", decoded_result.tool_image_handle.?); try std.testing.expectEqual( turn.assistant.execution.turn_summary, decoded.assistant.execution.turn_summary, ); try std.testing.expectEqual(@as(usize, 1), decoded.assistant.execution.steering.len); try std.testing.expectEqualStrings( "focus on persistence", decoded.assistant.execution.steering[0].text, ); try std.testing.expectEqual( @as(usize, 1), decoded.assistant.execution.steering[0].after_tool_step_count, ); try std.testing.expectEqualStrings( "partial assistant", decoded.assistant.execution.steering[0].assistant_prefix.?, ); try std.testing.expectEqual(@as(usize, 1), decoded_result.permission_feedback.len); try std.testing.expectEqualStrings("read it after writing", decoded_result.permission_feedback[0]); const presentation = decoded_result.committed_file_presentation orelse return error.TestExpectedPresentation; try std.testing.expectEqualStrings("note.txt", presentation.path); try std.testing.expectEqual(types.CommittedFilePresentationKind.added, presentation.kind); try std.testing.expectEqual(@as(usize, 2), presentation.lines.len); try std.testing.expectEqual(types.CommittedFilePresentationLineKind.notice, presentation.lines[1].kind); try std.testing.expectEqualStrings("… 118 lines omitted", presentation.lines[1].text); try std.testing.expectEqual(@as(usize, 120), presentation.additions); try std.testing.expectEqualStrings("persisted line\n", presentation.after_content.?); try std.testing.expectEqual(@as(u64, 9), presentation.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings("call_feedback", presentation.lifecycle_id.?.call_id); const replay = decoded_result.command_output_replay orelse return error.TestExpectedReplay; const descriptor = switch (replay) { .available => |value| value, .unavailable => return error.TestExpectedReplay, }; try std.testing.expectEqualStrings("fx-command-replay-private.bin", descriptor.handle); try std.testing.expectEqual(@as(usize, 321), descriptor.framed_bytes); try std.testing.expectEqual( types.CommandProcessPresentation{ .exit_code = 7 }, decoded_result.command_process_presentation.?, );
try std.testing.expect(parsed.value.object.swapRemove("provider_replay")); const legacy_execution = parsed.value.object.getPtr("execution").?; legacy_execution.object.getPtr("schema_version").?.* = .{ .integer = 8 }; try std.testing.expect(legacy_execution.object.getPtr("tool_steps").?.array.items[0].object.swapRemove("provider_replay")); try std.testing.expect(legacy_execution.object.getPtr("tool_steps").?.array.items[0].object.getPtr("tool_results").?.array.items[0].object.swapRemove("review_feedback")); const v8_decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, v8_decoded); try std.testing.expect(v8_decoded.assistant.provider_replay == null); try std.testing.expect(v8_decoded.assistant.execution.tool_steps[0].provider_replay == null); try std.testing.expectEqualStrings("saved-tool-image", v8_decoded.assistant.execution.tool_steps[0].tool_results[0].tool_image_handle.?);
const v1 = "{\"kind\":\"assistant\",\"user\":{\"text\":\"write a note\",\"images\":[]},\"assistant\":\"done\",\"execution\":{\"schema_version\":1,\"tool_steps\":[{\"assistant\":null,\"tool_calls\":[{\"id\":\"call_feedback\",\"name\":\"write_file\",\"arguments_json\":\"{\\\"path\\\":\\\"note.txt\\\"}\",\"provider_result\":null}],\"tool_results\":[{\"tool_call_id\":\"call_feedback\",\"tool_name\":\"write_file\",\"status\":\"success\",\"output\":\"wrote note.txt\",\"output_handle\":null,\"preview\":null,\"output_bytes\":15,\"stored_output_bytes\":15,\"truncated\":false,\"provider_native\":false,\"created_at_ms\":1}]}],\"files\":[]}}"; var v1_parsed = try std.json.parseFromSlice(std.json.Value, alloc, v1, .{}); defer v1_parsed.deinit(); const v1_decoded = try parseHistoryTurn(alloc, v1_parsed.value); defer session.freeHistoryTurn(alloc, v1_decoded); try std.testing.expectEqual( @as(usize, 0), v1_decoded.assistant.execution.tool_steps[0].tool_results[0].permission_feedback.len, ); try std.testing.expect(v1_decoded.assistant.execution.tool_steps[0].tool_results[0].command_output_replay == null); try std.testing.expect(v1_decoded.assistant.execution.tool_steps[0].tool_results[0].command_process_presentation == null);
const v2 = "{\"kind\":\"assistant\",\"user\":{\"text\":\"write a note\",\"images\":[]},\"assistant\":\"done\",\"execution\":{\"schema_version\":2,\"tool_steps\":[{\"assistant\":null,\"tool_calls\":[{\"id\":\"call_feedback\",\"name\":\"write_file\",\"arguments_json\":\"{\\\"path\\\":\\\"note.txt\\\"}\",\"provider_result\":null}],\"tool_results\":[{\"tool_call_id\":\"call_feedback\",\"tool_name\":\"write_file\",\"status\":\"success\",\"output\":\"wrote note.txt\",\"output_handle\":null,\"preview\":null,\"output_bytes\":15,\"stored_output_bytes\":15,\"truncated\":false,\"provider_native\":false,\"created_at_ms\":1,\"permission_feedback\":[]}]}],\"files\":[]}}"; var v2_parsed = try std.json.parseFromSlice(std.json.Value, alloc, v2, .{}); defer v2_parsed.deinit(); const v2_decoded = try parseHistoryTurn(alloc, v2_parsed.value); defer session.freeHistoryTurn(alloc, v2_decoded); try std.testing.expect(v2_decoded.assistant.execution.tool_steps[0].tool_results[0].committed_file_presentation == null); try std.testing.expect(v2_decoded.assistant.execution.tool_steps[0].tool_results[0].command_output_replay == null); try std.testing.expect(v2_decoded.assistant.execution.tool_steps[0].tool_results[0].command_process_presentation == null);
const v5 = "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[]},\"assistant\":\"done\",\"execution\":{\"schema_version\":5,\"tool_steps\":[],\"files\":[],\"turn_summary\":null}}"; var v5_parsed = try std.json.parseFromSlice(std.json.Value, alloc, v5, .{}); defer v5_parsed.deinit(); const v5_decoded = try parseHistoryTurn(alloc, v5_parsed.value); defer session.freeHistoryTurn(alloc, v5_decoded); try std.testing.expectEqual(@as(usize, 0), v5_decoded.assistant.execution.steering.len);
const v6 = "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[]},\"assistant\":\"done\",\"execution\":{\"schema_version\":6,\"tool_steps\":[],\"files\":[],\"steering\":[\"legacy steer\"],\"turn_summary\":null}}"; var v6_parsed = try std.json.parseFromSlice(std.json.Value, alloc, v6, .{}); defer v6_parsed.deinit(); const v6_decoded = try parseHistoryTurn(alloc, v6_parsed.value); defer session.freeHistoryTurn(alloc, v6_decoded); try std.testing.expectEqualStrings("legacy steer", v6_decoded.assistant.execution.steering[0].text); try std.testing.expectEqual(@as(usize, 0), v6_decoded.assistant.execution.steering[0].after_tool_step_count);
const invalid_v7 = "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[]},\"assistant\":\"done\",\"execution\":{\"schema_version\":7,\"tool_steps\":[],\"files\":[],\"steering\":[{\"text\":\"invalid\",\"assistant_prefix\":null,\"after_tool_step_count\":1}],\"turn_summary\":null}}"; var invalid_v7_parsed = try std.json.parseFromSlice(std.json.Value, alloc, invalid_v7, .{}); defer invalid_v7_parsed.deinit(); try std.testing.expectError( error.InvalidSessionFormat, parseHistoryTurn(alloc, invalid_v7_parsed.value), );}
fn checkV7SteeringAllocationFailures(alloc: Allocator) !void { const json = "[{\"text\":\"first\",\"assistant_prefix\":\"prefix one\",\"after_tool_step_count\":0}," ++ "{\"text\":\"second\",\"assistant_prefix\":\"prefix two\",\"after_tool_step_count\":1}]"; var parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); defer parsed.deinit(); const steering = try parsePersistedSteering(alloc, parsed.value, 7, 1); defer types.freePersistedSteering(alloc, steering); try std.testing.expectEqual(@as(usize, 2), steering.len);}
test "v7 steering cleans up every allocation failure" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkV7SteeringAllocationFailures, .{}, );}
test "private codec preserves summary-only interrupted turns" { const alloc = std.testing.allocator; const summary = types.TurnSummary{ .started_at_ms = 100, .completed_at_ms = 250, .thinking_duration_ms = 40, .turn_duration_ms = 150, .token_progress = .{ .input_tokens = 12, .output_tokens = 34 }, };
const turns = [_]session.HistoryTurn{.{ .interrupted = .{ .user = .{ .text = @constCast("inspect repository") }, .execution = .{ .turn_summary = summary }, } }};
for (turns) |turn| { var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn);
var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); try std.testing.expectEqual(summary, types.historyTurnSummary(decoded).?); }}
test "command process presentation codec preserves every terminal cause" { const alloc = std.testing.allocator; const cases = [_]types.CommandProcessPresentation{ .{ .exit_code = 7 }, .{ .signal = 9 }, .timed_out, .output_capture_failed, }; for (cases) |case| { var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeOptionalCommandProcessPresentation(&encoded.writer, case);
var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); try std.testing.expectEqual( case, (try parseOptionalCommandProcessPresentation(parsed.value)).?, ); }}
test "terminal action presentation codec preserves return and failure causes" { const alloc = std.testing.allocator; const cases = [_]types.TerminalActionPresentation{ .{ .returned = .started }, .{ .returned = .condition_met }, .{ .returned = .safety_ceiling }, .{ .returned = .cancelled }, .{ .returned = .{ .exited = 7 } }, .{ .returned = .{ .signal = 9 } }, .{ .failed = .session_not_found }, .{ .failed = .capacity_exceeded }, }; for (cases) |case| { var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeOptionalTerminalActionPresentation(&encoded.writer, case);
var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); try std.testing.expectEqual( case, (try parseOptionalTerminalActionPresentation(parsed.value)).?, ); }}
test "durable cancellation provenance defaults old records and preserves ordinary bytes" { const alloc = std.testing.allocator; const old = "{\"kind\":\"interrupted\",\"user\":{\"text\":\"request\",\"images\":[]},\"assistant\":\"partial\",\"tool_call\":null,\"completed_tool_names\":[]}"; var parsed = try std.json.parseFromSlice(std.json.Value, alloc, old, .{}); defer parsed.deinit(); const turn = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, turn); try std.testing.expectEqual(types.CancellationOrigin.turn, turn.interrupted.cancellation_origin); try std.testing.expectEqual(types.InterruptedTerminalReason.cancelled, turn.interrupted.terminal_reason); var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try writeHistoryTurn(&out.writer, turn); try std.testing.expectEqualStrings(old[0 .. old.len - 1] ++ ",\"terminal_reason\":\"cancelled\"}", out.written());}
test "durable cancellation provenance roundtrips and cleans allocation failures" { const Case = struct { fn run(alloc: Allocator, origin: types.CancellationOrigin, reason: types.InterruptedTerminalReason) !void { const turn: session.HistoryTurn = .{ .interrupted = .{ .user = .{ .text = @constCast("request") }, .assistant = @constCast("partial"), .terminal_reason = reason, .cancellation_origin = origin, } }; const copy = try session.dupeHistoryTurn(alloc, turn); defer session.freeHistoryTurn(alloc, copy); var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); // This in-memory writer reports injected allocation failure as WriteFailed. writeHistoryTurn(&out.writer, copy) catch |err| return if (err == error.WriteFailed) error.OutOfMemory else err; try std.testing.expectEqual(origin == .compaction, std.mem.find(u8, out.written(), "\"cancellation_origin\"") != null); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, out.written(), .{}); defer parsed.deinit(); const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); try std.testing.expectEqual(origin, decoded.interrupted.cancellation_origin); try std.testing.expectEqual(reason, decoded.interrupted.terminal_reason); try std.testing.expectEqualStrings("request", decoded.interrupted.user.text); try std.testing.expectEqualStrings("partial", decoded.interrupted.assistant.?); var again: std.Io.Writer.Allocating = .init(alloc); defer again.deinit(); writeHistoryTurn(&again.writer, decoded) catch |err| return if (err == error.WriteFailed) error.OutOfMemory else err; try std.testing.expectEqualStrings(out.written(), again.written()); } }; for (std.enums.values(types.CancellationOrigin)) |origin| { for (std.enums.values(types.InterruptedTerminalReason)) |reason| { try std.testing.checkAllAllocationFailures(std.testing.allocator, Case.run, .{ origin, reason }); } }}
test "durable cancellation provenance accepts explicit turn and rejects invalid or unknown fields" { const alloc = std.testing.allocator; const prefix = "{\"kind\":\"interrupted\",\"user\":{\"text\":\"request\",\"images\":[]},\"assistant\":\"partial\",\"tool_call\":null,\"completed_tool_names\":[\"read_file\"],\"cancellation_origin\":"; for ([_][]const u8{ "\"turn\"", "\"compaction\"", "\"unknown\"", "null", "0", "true", "{}", "[]", "\"compaction\",\"unknown_field\":true" }, 0..) |value, i| { const bytes = try std.fmt.allocPrint(alloc, "{s}{s}}}", .{ prefix, value }); defer alloc.free(bytes); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, bytes, .{}); defer parsed.deinit(); if (i < 2) { const turn = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, turn); try std.testing.expectEqual(if (i == 0) types.CancellationOrigin.turn else .compaction, turn.interrupted.cancellation_origin); } else { try std.testing.expectError(error.InvalidSessionFormat, parseHistoryTurn(alloc, parsed.value)); } }}
test "durable history rejects unknown fields instead of silently dropping bytes" { const alloc = std.testing.allocator; var parsed = try std.json.parseFromSlice( std.json.Value, alloc, "{\"kind\":\"compacted_summary\",\"summary\":\"kept\",\"removed_turn_count\":1,\"compaction_count\":1,\"future_bytes\":\"lost\"}", .{}, ); defer parsed.deinit();
try std.testing.expectError( error.InvalidSessionFormat, parseHistoryTurn(alloc, parsed.value), );}
test "legacy compacted authority absence remains incomplete across durable reload" { const alloc = std.testing.allocator; var legacy_parsed = try std.json.parseFromSlice( std.json.Value, alloc, "{\"kind\":\"compacted_summary\",\"summary\":\"legacy\",\"removed_turn_count\":2,\"compaction_count\":1}", .{}, ); defer legacy_parsed.deinit(); const legacy = try parseHistoryTurn(alloc, legacy_parsed.value); defer session.freeHistoryTurn(alloc, legacy); try std.testing.expect(!legacy.compacted_summary.root_user_messages_complete); try std.testing.expect(!legacy.compacted_summary.permission_feedback_complete);
var known_messages = [_][]u8{@constCast("newer exact request")}; const incomplete: session.HistoryTurn = .{ .compacted_summary = .{ .summary = @constCast("legacy plus newer context"), .removed_turn_count = 3, .compaction_count = 2, .root_user_messages = &known_messages, .root_user_messages_complete = false, .permission_feedback = &known_messages, .permission_feedback_complete = false, } }; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, incomplete); var reloaded_parsed = try std.json.parseFromSlice( std.json.Value, alloc, encoded.written(), .{}, ); defer reloaded_parsed.deinit(); const reloaded = try parseHistoryTurn(alloc, reloaded_parsed.value); defer session.freeHistoryTurn(alloc, reloaded); try std.testing.expect(!reloaded.compacted_summary.root_user_messages_complete); try std.testing.expect(!reloaded.compacted_summary.permission_feedback_complete); try std.testing.expectEqual(@as(usize, 0), reloaded.compacted_summary.root_user_messages.len);
var first_generation_parsed = try std.json.parseFromSlice( std.json.Value, alloc, "{\"kind\":\"compacted_summary\",\"summary\":\"first generation\",\"removed_turn_count\":2,\"compaction_count\":1,\"root_user_messages\":[\"original request\"]}", .{}, ); defer first_generation_parsed.deinit(); const first_generation = try parseHistoryTurn(alloc, first_generation_parsed.value); defer session.freeHistoryTurn(alloc, first_generation); try std.testing.expect(first_generation.compacted_summary.root_user_messages_complete); try std.testing.expect(!first_generation.compacted_summary.permission_feedback_complete); try std.testing.expectEqualStrings( "original request", first_generation.compacted_summary.root_user_messages[0], );}
fn persistedResultForTest(call_id: []const u8, tool_name: []const u8) session.PersistedToolResult { return .{ .tool_call_id = @constCast(call_id), .tool_name = @constCast(tool_name), .status = .success, .output = @constCast("stale"), .output_bytes = 5, .stored_output_bytes = 5, .truncated = false, .provider_native = false, .created_at_ms = 1, };}
fn expectMalformedPairRejected( calls: []session.ToolCall, results: []session.PersistedToolResult,) !void { var steps = [_]session.ToolExecutionStep{.{ .tool_calls = calls, .tool_results = results, }}; const turn: session.HistoryTurn = .{ .assistant = .{ .user = .{ .text = @constCast("inspect") }, .assistant = @constCast("failed"), .execution = .{ .tool_steps = steps[0..] }, } };
var encoded: std.Io.Writer.Allocating = .init(std.testing.allocator); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn); var parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, encoded.written(), .{}); defer parsed.deinit(); try std.testing.expectError( error.InvalidSessionFormat, parseHistoryTurn(std.testing.allocator, parsed.value), );}
fn checkCurrentToolArgumentRepairAllocationFailures(alloc: Allocator) !void { var feedback = [_][]u8{@constCast("read the repaired file")}; var calls = [_]session.ToolCall{.{ .id = @constCast("call_bad"), .name = @constCast("read_file"), .arguments_json = @constCast("{\"depth\":1,\"depth\":2}"), }}; var results = [_]session.PersistedToolResult{persistedResultForTest("call_bad", "read_file")}; results[0].permission_feedback = feedback[0..]; var steps = [_]session.ToolExecutionStep{.{ .tool_calls = calls[0..], .tool_results = results[0..], }}; const turn: session.HistoryTurn = .{ .assistant = .{ .user = .{ .text = @constCast("inspect") }, .assistant = @constCast("failed"), .execution = .{ .tool_steps = steps[0..] }, } };
var encoded: std.Io.Writer.Allocating = .init(std.testing.allocator); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn); var parsed = try std.json.parseFromSlice( std.json.Value, std.testing.allocator, encoded.written(), .{}, ); defer parsed.deinit();
const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); const step = decoded.assistant.execution.tool_steps[0]; try std.testing.expectEqualStrings("{}", step.tool_calls[0].arguments_json); try std.testing.expectEqual(session.PersistedToolStatus.failure, step.tool_results[0].status); try std.testing.expectEqual(@as(usize, 1), step.tool_results[0].permission_feedback.len);}
test "durable specialized history accepts only legacy or complete extended shapes" { const alloc = std.testing.allocator; const empty_execution = "{\"schema_version\":1,\"tool_steps\":[],\"files\":[]}"; const cases = [_]struct { json: []const u8, expected: enum { background_legacy, background_extended, interrupted_legacy, interrupted_extended }, }{ .{ .json = "{\"kind\":\"background_command\",\"user\":{\"text\":\"run\",\"images\":[]},\"log_path\":\"/tmp/log\",\"expect_url\":false,\"url\":null,\"background_record_id\":null}", .expected = .background_legacy, }, .{ .json = "{\"kind\":\"background_command\",\"user\":{\"text\":\"run\",\"images\":[]},\"log_path\":\"/tmp/log\",\"expect_url\":false,\"url\":null,\"background_record_id\":null,\"assistant\":\"candidate\",\"execution\":" ++ empty_execution ++ "}", .expected = .background_extended, }, .{ .json = "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":null,\"completed_tool_names\":[]}", .expected = .interrupted_legacy, }, .{ .json = "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":\"candidate\",\"tool_call\":null,\"completed_tool_names\":[],\"execution\":" ++ empty_execution ++ "}", .expected = .interrupted_extended, }, };
for (cases) |case| { var parsed = try std.json.parseFromSlice(std.json.Value, alloc, case.json, .{}); defer parsed.deinit(); const turn = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, turn); switch (case.expected) { .background_legacy => { try std.testing.expect(turn == .assistant); try std.testing.expect(std.mem.find(u8, turn.assistant.assistant, "no longer owns") != null); try std.testing.expect(turn.assistant.execution.isEmpty()); }, .background_extended => { try std.testing.expect(turn == .assistant); try std.testing.expect(std.mem.find(u8, turn.assistant.assistant, "candidate") != null); try std.testing.expect(std.mem.find(u8, turn.assistant.assistant, "no longer owns") != null); try std.testing.expect(turn.assistant.execution.isEmpty()); }, .interrupted_legacy => try std.testing.expect(turn.interrupted.execution.isEmpty()), .interrupted_extended => { try std.testing.expectEqualStrings("candidate", turn.interrupted.assistant.?); try std.testing.expect(turn.interrupted.execution.isEmpty()); }, } }
const invalid = [_][]const u8{ "{\"kind\":\"background_command\",\"user\":{\"text\":\"run\",\"images\":[]},\"log_path\":\"/tmp/log\",\"expect_url\":false,\"url\":null,\"background_record_id\":null,\"assistant\":\"partial\"}", "{\"kind\":\"background_command\",\"user\":{\"text\":\"run\",\"images\":[]},\"log_path\":\"/tmp/log\",\"expect_url\":false,\"url\":null,\"background_record_id\":null,\"assistant\":\"candidate\",\"execution\":" ++ empty_execution ++ ",\"future\":true}", "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":null,\"completed_tool_names\":[],\"future\":true}", }; for (invalid) |json| { var parsed = try std.json.parseFromSlice(std.json.Value, alloc, json, .{}); defer parsed.deinit(); try std.testing.expectError(error.InvalidSessionFormat, parseHistoryTurn(alloc, parsed.value)); }
const migrated = session.HistoryTurn{ .assistant = .{ .user = .{ .text = @constCast("run") }, .assistant = @constCast("historical command"), } }; var legacy_encoded: std.Io.Writer.Allocating = .init(alloc); defer legacy_encoded.deinit(); try writeHistoryTurn(&legacy_encoded.writer, migrated); try std.testing.expect(std.mem.find(u8, legacy_encoded.written(), "\"kind\":\"assistant\"") != null); try std.testing.expect(std.mem.find(u8, legacy_encoded.written(), "background_command") == null);}
test "interrupted command presentation is strict and round trips" { const alloc = std.testing.allocator; const turn: session.HistoryTurn = .{ .interrupted = .{ .user = .{ .text = @constCast("stop") }, .tool_call = .{ .id = "call-command", .name = "run_command", .arguments_json = "{\"command\":\"slow\"}", }, .cancelled_command = .{ .output_replay = .{ .available = .{ .handle = "fx-command-replay.bin", .framed_bytes = 42, } }, .command_artifact_handle = "fx-command.log", }, } }; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writeHistoryTurn(&encoded.writer, turn); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, encoded.written(), .{}); defer parsed.deinit(); const decoded = try parseHistoryTurn(alloc, parsed.value); defer session.freeHistoryTurn(alloc, decoded); const presentation = decoded.interrupted.cancelled_command.?; const descriptor = switch (presentation.output_replay.?) { .available => |value| value, .unavailable => return error.TestExpectedReplay, }; try std.testing.expectEqualStrings("fx-command-replay.bin", descriptor.handle); try std.testing.expectEqual(@as(usize, 42), descriptor.framed_bytes); try std.testing.expectEqualStrings("fx-command.log", presentation.command_artifact_handle.?); try std.testing.expectEqual(types.InterruptedTerminalReason.cancelled, decoded.interrupted.terminal_reason);
const failed_turn: session.HistoryTurn = .{ .interrupted = .{ .user = .{ .text = @constCast("stream") }, .assistant = @constCast("partial"), .terminal_reason = .failed, } }; var failed_encoded: std.Io.Writer.Allocating = .init(alloc); defer failed_encoded.deinit(); try writeHistoryTurn(&failed_encoded.writer, failed_turn); try std.testing.expect(std.mem.find(u8, failed_encoded.written(), "\"terminal_reason\":\"failed\"") != null); var failed_parsed = try std.json.parseFromSlice(std.json.Value, alloc, failed_encoded.written(), .{}); defer failed_parsed.deinit(); const failed_decoded = try parseHistoryTurn(alloc, failed_parsed.value); defer session.freeHistoryTurn(alloc, failed_decoded); try std.testing.expectEqual(types.InterruptedTerminalReason.failed, failed_decoded.interrupted.terminal_reason);
const zero_output = "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]}," ++ "\"assistant\":null,\"tool_call\":{\"id\":\"call\",\"name\":\"run_command\"," ++ "\"arguments_json\":\"{}\",\"provider_result\":null},\"completed_tool_names\":[]," ++ "\"cancelled_command\":{\"output_replay\":null,\"command_artifact_handle\":null}}"; var zero_parsed = try std.json.parseFromSlice(std.json.Value, alloc, zero_output, .{}); defer zero_parsed.deinit(); const zero_turn = try parseHistoryTurn(alloc, zero_parsed.value); defer session.freeHistoryTurn(alloc, zero_turn); try std.testing.expect(zero_turn.interrupted.cancelled_command != null); try std.testing.expectEqual(types.InterruptedTerminalReason.cancelled, zero_turn.interrupted.terminal_reason);
const invalid = [_][]const u8{ "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":null,\"completed_tool_names\":[],\"cancelled_command\":{\"output_replay\":null,\"command_artifact_handle\":null}}", "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":{\"id\":\"call\",\"name\":\"read_file\",\"arguments_json\":\"{}\",\"provider_result\":null},\"completed_tool_names\":[],\"cancelled_command\":{\"output_replay\":null,\"command_artifact_handle\":null}}", "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":{\"id\":\"call\",\"name\":\"run_command\",\"arguments_json\":\"{}\",\"provider_result\":null},\"completed_tool_names\":[],\"cancelled_command\":{\"output_replay\":null,\"command_artifact_handle\":null,\"future\":true}}", "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":{\"id\":\"call\",\"name\":\"run_command\",\"arguments_json\":\"{}\",\"provider_result\":null},\"completed_tool_names\":[],\"terminal_reason\":\"failed\",\"cancelled_command\":{\"output_replay\":null,\"command_artifact_handle\":null}}", "{\"kind\":\"interrupted\",\"user\":{\"text\":\"stop\",\"images\":[]},\"assistant\":null,\"tool_call\":null,\"completed_tool_names\":[],\"terminal_reason\":\"future\"}", }; for (invalid) |json| { var invalid_parsed = try std.json.parseFromSlice(std.json.Value, alloc, json, .{}); defer invalid_parsed.deinit(); try std.testing.expectError( error.InvalidSessionFormat, parseHistoryTurn(alloc, invalid_parsed.value), ); }}
fn expectStateEqual(expected: DurableSessionState, actual: DurableSessionState) !void { try std.testing.expectEqualStrings(expected.id, actual.id); try std.testing.expectEqualStrings(expected.origin_workspace_root, actual.origin_workspace_root); try std.testing.expectEqualStrings(expected.workspace_root, actual.workspace_root); try std.testing.expectEqual(expected.created_at_ms, actual.created_at_ms); try std.testing.expectEqual(expected.updated_at_ms, actual.updated_at_ms); try std.testing.expectEqualStrings(expected.conversation_language.view(), actual.conversation_language.view()); try std.testing.expectEqualStrings(expected.preferences.model, actual.preferences.model); try std.testing.expectEqual(expected.preferences.provider, actual.preferences.provider); try std.testing.expectEqual(expected.preferences.effort, actual.preferences.effort); try std.testing.expectEqual(expected.preferences.fast_mode, actual.preferences.fast_mode); try std.testing.expectEqual(expected.context_history_start, actual.context_history_start); try std.testing.expectEqual(expected.total_input_tokens, actual.total_input_tokens); try std.testing.expectEqual(expected.total_output_tokens, actual.total_output_tokens); try std.testing.expectEqual(expected.subagent_child, actual.subagent_child); try expectPermissionStateEqual(expected.permission_state, actual.permission_state); try std.testing.expectEqual(expected.last_subagent_work_id != null, actual.last_subagent_work_id != null); if (expected.last_subagent_work_id) |work_id| { try std.testing.expectEqualStrings(work_id, actual.last_subagent_work_id.?); } try std.testing.expectEqual(expected.usage != null, actual.usage != null); if (expected.usage) |expected_usage| { var expected_json: std.Io.Writer.Allocating = .init(std.testing.allocator); defer expected_json.deinit(); try session_usage.writeSnapshot(&expected_json.writer, expected_usage); var actual_json: std.Io.Writer.Allocating = .init(std.testing.allocator); defer actual_json.deinit(); try session_usage.writeSnapshot(&actual_json.writer, actual.usage.?); try std.testing.expectEqualStrings(expected_json.written(), actual_json.written()); } try std.testing.expectEqual(expected.history.len, actual.history.len); for (expected.history, actual.history) |expected_turn, actual_turn| { try expectHistoryTurnEqual(expected_turn, actual_turn); }}
fn expectPermissionStateEqual( expected: session_permission_state.State, actual: session_permission_state.State,) !void { try std.testing.expectEqual(expected.version, actual.version); try std.testing.expectEqual(expected.next_generation, actual.next_generation); try std.testing.expectEqual(expected.rules.items.len, actual.rules.items.len); for (expected.rules.items, actual.rules.items) |expected_rule, actual_rule| { try std.testing.expectEqual(expected_rule.id, actual_rule.id); try std.testing.expectEqual(expected_rule.key.kind, actual_rule.key.kind); try std.testing.expectEqualSlices(u8, &expected_rule.key.digest, &actual_rule.key.digest); try std.testing.expectEqualSlices(u8, expected_rule.key.canonical, actual_rule.key.canonical); try std.testing.expectEqualStrings(expected_rule.display_identity, actual_rule.display_identity); try std.testing.expectEqual(expected_rule.decision, actual_rule.decision); try std.testing.expectEqual(expected_rule.generation, actual_rule.generation); }}
fn expectHistoryTurnEqual(expected: session.HistoryTurn, actual: session.HistoryTurn) !void { try std.testing.expectEqual(std.meta.activeTag(expected), std.meta.activeTag(actual)); switch (expected) { .compacted_summary => |entry| { try std.testing.expectEqualSlices(u8, entry.summary, actual.compacted_summary.summary); try std.testing.expectEqual(entry.removed_turn_count, actual.compacted_summary.removed_turn_count); try std.testing.expectEqual(entry.compaction_count, actual.compacted_summary.compaction_count); try std.testing.expect(!actual.compacted_summary.root_user_messages_complete); try std.testing.expectEqual( @as(usize, 0), actual.compacted_summary.root_user_messages.len, ); try std.testing.expect(!actual.compacted_summary.permission_feedback_complete); try std.testing.expectEqual( @as(usize, 0), actual.compacted_summary.permission_feedback.len, ); }, .assistant => |entry| { const got = actual.assistant; try expectUserTurnEqual(entry.user, got.user); try std.testing.expectEqualSlices(u8, entry.assistant, got.assistant); try expectExecutionMemoryEqual(entry.execution, got.execution); }, .interrupted => |entry| { const got = actual.interrupted; try expectUserTurnEqual(entry.user, got.user); try expectOptionalBytesEqual(entry.assistant, got.assistant); try std.testing.expectEqual(entry.tool_call != null, got.tool_call != null); if (entry.tool_call) |call| try expectToolCallEqual(call, got.tool_call.?); try std.testing.expectEqual(entry.completed_tool_names.len, got.completed_tool_names.len); for (entry.completed_tool_names, got.completed_tool_names) |name, got_name| { try std.testing.expectEqualSlices(u8, name, got_name); } try expectExecutionMemoryEqual(entry.execution, got.execution); try std.testing.expectEqualDeep(entry.cancelled_command, got.cancelled_command); try std.testing.expectEqual(entry.terminal_reason, got.terminal_reason); }, }}
fn expectExecutionMemoryEqual(expected: session.ExecutionMemory, actual: session.ExecutionMemory) !void { try std.testing.expectEqual(expected.turn_summary, actual.turn_summary); try std.testing.expectEqual(expected.steering.len, actual.steering.len); for (expected.steering, actual.steering) |steering, got_steering| { try std.testing.expectEqualSlices(u8, steering.text, got_steering.text); try expectOptionalBytesEqual(steering.assistant_prefix, got_steering.assistant_prefix); try std.testing.expectEqual(steering.after_tool_step_count, got_steering.after_tool_step_count); } try std.testing.expectEqual(expected.tool_steps.len, actual.tool_steps.len); for (expected.tool_steps, actual.tool_steps) |step, got_step| { try expectOptionalBytesEqual(step.assistant, got_step.assistant); try std.testing.expectEqual(step.tool_calls.len, got_step.tool_calls.len); for (step.tool_calls, got_step.tool_calls) |call, got_call| try expectToolCallEqual(call, got_call); try std.testing.expectEqual(step.tool_results.len, got_step.tool_results.len); for (step.tool_results, got_step.tool_results) |result, got_result| { try std.testing.expectEqualSlices(u8, result.tool_call_id, got_result.tool_call_id); try std.testing.expectEqualSlices(u8, result.tool_name, got_result.tool_name); try std.testing.expectEqual(result.status, got_result.status); try std.testing.expectEqualSlices(u8, result.output, got_result.output); try expectOptionalBytesEqual(result.output_handle, got_result.output_handle); try expectOptionalBytesEqual(result.preview, got_result.preview); try std.testing.expectEqual(result.output_bytes, got_result.output_bytes); try std.testing.expectEqual(result.stored_output_bytes, got_result.stored_output_bytes); try std.testing.expectEqual(result.truncated, got_result.truncated); try std.testing.expectEqual(result.provider_native, got_result.provider_native); try std.testing.expectEqual(result.review_feedback, got_result.review_feedback); try std.testing.expectEqual(result.created_at_ms, got_result.created_at_ms); } } try std.testing.expectEqual(expected.files.len, actual.files.len); for (expected.files, actual.files) |file, got_file| { try std.testing.expectEqualSlices(u8, file.path, got_file.path); try expectOptionalBytesEqual(file.new_path, got_file.new_path); try std.testing.expectEqualSlices(u8, file.tool_call_id, got_file.tool_call_id); try std.testing.expectEqualSlices(u8, file.tool_name, got_file.tool_name); try std.testing.expectEqual(file.action, got_file.action); try std.testing.expectEqual(file.status, got_file.status); try std.testing.expectEqual(file.model_view_covers_full_file, got_file.model_view_covers_full_file); try std.testing.expectEqual(file.stale, got_file.stale); }}
fn expectUserTurnEqual(expected: session.UserTurn, actual: session.UserTurn) !void { try std.testing.expectEqualSlices(u8, expected.text, actual.text); try expectOptionalBytesEqual(expected.work_id, actual.work_id); try std.testing.expectEqual(expected.images.len, actual.images.len); for (expected.images, actual.images) |image, got| { try std.testing.expectEqual(image.id, got.id); try std.testing.expectEqualSlices(u8, image.path, got.path); try std.testing.expectEqualSlices(u8, image.media_type, got.media_type); try expectOptionalBytesEqual(image.snapshot_path, got.snapshot_path); try expectOptionalBytesEqual(image.snapshot_sha256, got.snapshot_sha256); }}
fn expectToolCallEqual(expected: session.ToolCall, actual: session.ToolCall) !void { try std.testing.expectEqualSlices(u8, expected.id, actual.id); try std.testing.expectEqualSlices(u8, expected.name, actual.name); try std.testing.expectEqualSlices(u8, expected.arguments_json, actual.arguments_json); try expectOptionalBytesEqual(expected.provider_result, actual.provider_result);}
fn expectOptionalBytesEqual(expected: anytype, actual: @TypeOf(expected)) !void { try std.testing.expectEqual(expected != null, actual != null); if (expected) |bytes| try std.testing.expectEqualSlices(u8, bytes, actual.?);}
test "durable image snapshots serialize as session-relative locators" { const alloc = std.testing.allocator; var images = [_]session.ImageAttachment{.{ .id = 1, .path = @constCast("/Users/private/source.png"), .media_type = @constCast("image/png"), .snapshot_path = @constCast("/tmp/fx/sessions/session-id/images/image-1-deadbeef.bin"), .snapshot_sha256 = @constCast("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), }}; const turn: session.HistoryTurn = .{ .assistant = .{ .user = .{ .text = @constCast("[Image #1]"), .images = &images }, .assistant = @constCast("done"), } }; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit();
try writeHistoryTurn(&encoded.writer, turn);
try std.testing.expect(std.mem.find( u8, encoded.written(), "/tmp/fx/sessions/session-id/images", ) == null); try std.testing.expect(std.mem.find( u8, encoded.written(), "\"snapshot_path\":\"images/image-1-deadbeef.bin\"", ) != null);}
test "codec structural helpers enforce exact objects and required strings" { const alloc = std.testing.allocator;
var unknown_key = try std.json.parseFromSlice( std.json.Value, alloc, "{\"known\":\"value\",\"unexpected\":true}", .{}, ); defer unknown_key.deinit(); try std.testing.expectError( error.InvalidSessionFormat, exactObject(unknown_key.value, &.{"known"}), );
var non_string = try std.json.parseFromSlice( std.json.Value, alloc, "{\"known\":1}", .{}, ); defer non_string.deinit(); const non_string_object = try exactObject(non_string.value, &.{"known"}); try std.testing.expectError( error.InvalidSessionFormat, requireString(non_string_object, "known"), );
var valid = try std.json.parseFromSlice( std.json.Value, alloc, "{\"known\":\"value\"}", .{}, ); defer valid.deinit(); const valid_object = try exactObject(valid.value, &.{"known"}); try std.testing.expectEqualStrings("value", try requireString(valid_object, "known"));}
test "recovery checkpoint round trips while legacy state stays absent" { const alloc = std.testing.allocator; const checkpoint = RecoveryCheckpoint{ .turn_id = 42, .user = .{ .text = @constCast("keep working") }, .assistant_source = @constCast("partial answer"), .cause = .response_interrupted, .action = .continuing_response, .tool_state = .confirmed, .authority = .{ .provider = .codex, .model = @constCast("gpt-5.4-mini"), .credential_source = .chatgpt_subscription, .credential_identity = credential_authority.derive( .chatgpt_subscription, "acct_1", ), }, .requested_fast_mode = true, .fast_mode = true, .max_provider_attempts = 10, .consumed_provider_attempts = 4, .outstanding_reservation = true, }; const state = DurableSessionState{ .id = @constCast("session-recovery"), .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .provider = .codex, .model = @constCast("gpt-5.4-mini"), .effort = .auto, .fast_mode = false, }, .history = &.{}, .total_input_tokens = 0, .total_output_tokens = 0, .recovery_checkpoint = checkpoint, };
var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); _ = try encodeState(state, &encoded.writer); var source = std.Io.Reader.fixed(encoded.written()); var decoded = try decodeState(alloc, &source, .{}); defer decoded.deinit(alloc);
const restored = decoded.recovery_checkpoint.?; try std.testing.expectEqual(@as(u64, 42), restored.turn_id); try std.testing.expectEqualStrings("keep working", restored.user.text); try std.testing.expectEqualStrings("partial answer", restored.assistant_source); try std.testing.expectEqual(types.ModelRecoveryCause.response_interrupted, restored.cause); try std.testing.expectEqual(types.ModelRecoveryAction.continuing_response, restored.action); try std.testing.expectEqual(RecoveryToolState.confirmed, restored.tool_state); try std.testing.expectEqual(model_provider.ProviderId.codex, restored.authority.provider); try std.testing.expectEqualStrings("gpt-5.4-mini", restored.authority.model); try std.testing.expectEqual(types.CredentialSource.chatgpt_subscription, restored.authority.credential_source.?); try std.testing.expect(restored.authority.credential_identity != null); try std.testing.expectEqual(model_provider.ProviderId.codex, decoded.preferences.provider); try std.testing.expect(restored.requested_fast_mode); try std.testing.expect(restored.fast_mode); try std.testing.expectEqual(@as(usize, 4), restored.consumed_provider_attempts); try std.testing.expect(restored.outstanding_reservation);
const legacy = "{\"id\":\"legacy\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":2,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"openai/gpt-test\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}"; var legacy_source = std.Io.Reader.fixed(legacy); var legacy_state = try decodeState(alloc, &legacy_source, .{}); defer legacy_state.deinit(alloc); try std.testing.expectEqual(model_provider.ProviderId.gateway, legacy_state.preferences.provider); try std.testing.expectEqual(@as(?RecoveryCheckpoint, null), legacy_state.recovery_checkpoint);}
test "recovery checkpoint accepts the exact composer byte limit" { const alloc = std.testing.allocator; const prompt = try alloc.alloc(u8, 8 * 1024 * 1024); defer alloc.free(prompt); @memset(prompt, 'x'); const checkpoint = RecoveryCheckpoint{ .turn_id = 1, .user = .{ .text = prompt }, .assistant_source = @constCast(""), .cause = .network_interrupted, .action = .retrying_request, .authority = .{ .provider = .gateway, .model = @constCast("test/model") }, .requested_fast_mode = false, .fast_mode = false, .max_provider_attempts = 3, .consumed_provider_attempts = 0, }; const encoded = try encodeRecoveryCheckpoint(alloc, checkpoint); defer alloc.free(encoded); try std.testing.expect(encoded.len > prompt.len); try std.testing.expect(encoded.len <= max_recovery_checkpoint_bytes);}
test "session permission state round trips while legacy state stays empty" { const alloc = std.testing.allocator; const key = try session_permission_state.RuleKey.init( .command, "command\x00/workspace\x00git status", ); var empty_permission_state: session_permission_state.State = .{}; defer empty_permission_state.deinit(alloc); var applied = try session_permission_state.apply( alloc, empty_permission_state, .{ .set = .{ .key = key, .display_identity = "git status in /workspace", .decision = .deny, .expected_generation = null, } }, ); var permission_state = applied.takeApplied() orelse return error.TestExpectedAppliedState; defer permission_state.deinit(alloc);
const state = DurableSessionState{ .id = @constCast("session-permission-state"), .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .model = @constCast("openai/gpt-test"), .effort = .auto, .fast_mode = false, }, .history = &.{}, .total_input_tokens = 0, .total_output_tokens = 0, .permission_state = permission_state, }; var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); _ = try encodeState(state, &encoded.writer); var source = std.Io.Reader.fixed(encoded.written()); var decoded = try decodeState(alloc, &source, .{}); defer decoded.deinit(alloc); try std.testing.expectEqual( session_permission_state.StateDecision.deny, session_permission_state.decide(decoded.permission_state, key), );
const legacy = "{\"id\":\"legacy-permission-state\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":2,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"openai/gpt-test\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}"; var legacy_source = std.Io.Reader.fixed(legacy); var legacy_state = try decodeState(alloc, &legacy_source, .{}); defer legacy_state.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), legacy_state.permission_state.rules.items.len);}
test "recovery checkpoint rejects an outstanding attempt beyond its budget" { const invalid = DurableSessionState{ .id = @constCast("session-recovery"), .origin_workspace_root = @constCast("/tmp/origin"), .workspace_root = @constCast("/tmp/current"), .created_at_ms = 1, .updated_at_ms = 2, .conversation_language = session.ConversationLanguage.literal("en"), .preferences = .{ .model = @constCast("openai/gpt-test"), .effort = .auto, .fast_mode = false, }, .history = &.{}, .total_input_tokens = 0, .total_output_tokens = 0, .recovery_checkpoint = .{ .turn_id = 1, .user = .{ .text = @constCast("prompt") }, .assistant_source = @constCast(""), .cause = .network_interrupted, .action = .retrying_request, .authority = .{ .provider = .gateway, .model = @constCast("openai/gpt-test") }, .requested_fast_mode = false, .fast_mode = false, .max_provider_attempts = 1, .consumed_provider_attempts = 1, .outstanding_reservation = true, }, }; try std.testing.expectError(error.InvalidDurableField, validateState(invalid));}
test "permission state schema two round trips before activation" { const alloc = std.testing.allocator; var state: session_permission_state.State = .{ .version = session_permission_state.schema_version, .next_generation = 2, }; defer state.deinit(alloc); const key = try session_permission_state.commandKeyV2( alloc, "git status", "/workspace", "foreground", "macos", ); try state.rules.append(alloc, .{ .id = .{ .value = 1 }, .key = key, .display_identity = try alloc.dupe(u8, "git status"), .decision = .deny, .generation = 1, });
var encoded: std.Io.Writer.Allocating = .init(alloc); defer encoded.deinit(); try writePermissionState(&encoded.writer, state); var json = try std.json.parseFromSlice( std.json.Value, alloc, encoded.written(), .{}, ); defer json.deinit(); var decoded = try parsePermissionState(alloc, json.value); defer decoded.deinit(alloc);
try std.testing.expectEqual(session_permission_state.schema_version, decoded.version); try std.testing.expectEqual(@as(usize, 1), decoded.rules.items.len); try std.testing.expectEqual(session_permission_state.StateDecision.deny, session_permission_state.decide(decoded, key));}
test "durable state usage remains strict and releases partial allocations" { const alloc = std.testing.allocator; const state_json = \\{"id":"usage-state","origin_workspace_root":"/workspace","workspace_root":"/workspace","created_at_ms":1,"updated_at_ms":2, \\"conversation_language":"en","preferences":{"model":"test/model","effort":"auto","fast_mode":false},"history":[],"total_input_tokens":0,"total_output_tokens":0, \\"usage":{"billing":"complete","api_duration_complete":true,"wall_duration_complete":true,"code_complete":true,"next_sequence":2,"settled_through_sequence":1, \\"api_duration_ms":10,"wall_duration_ms":20,"total_cost":1,"input_tokens":10,"output_tokens":3,"cache_read_tokens":2,"cache_write_tokens":0,"billable_web_search_calls":0,"lines_added":0,"lines_removed":0, \\"models":[{"model":"test/model","first_sequence":1,"total_cost":1,"input_tokens":10,"output_tokens":3,"cache_read_tokens":2,"cache_write_tokens":0,"billable_web_search_calls":0}],"pending":[]}, \\"last_subagent_work_id":"legacy-work"} ; try std.testing.checkAllAllocationFailures(alloc, struct { fn check(a: Allocator, bytes: []const u8) !void { var source = std.Io.Reader.fixed(bytes); var decoded = try decodeState(a, &source, .{}); defer decoded.deinit(a); try std.testing.expectEqual(@as(u64, 10), decoded.usage.?.input_tokens); try std.testing.expectEqualStrings("legacy-work", decoded.last_subagent_work_id.?); } }.check, .{state_json}); const incompatible = try std.mem.replaceOwned(u8, alloc, state_json, "\"cache_read_tokens\":2", "\"cache_read_tokens\":11"); defer alloc.free(incompatible); var strict_source = std.Io.Reader.fixed(incompatible); try std.testing.expectError(error.InvalidSessionFormat, decodeState(alloc, &strict_source, .{}));}
test "durable session optional fields handle fuzzed ownership paths" { try std.testing.fuzz({}, fuzzDurableSessionOptionalFields, .{ .corpus = &.{ "{\"id\":\"session\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"test/model\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0}", "{\"id\":\"session\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"test/model\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0,\"context_history_start\":0,\"last_subagent_work_id\":\"work\"}", "{\"id\":\"session\",\"origin_workspace_root\":\"/tmp/origin\",\"workspace_root\":\"/tmp/current\",\"created_at_ms\":1,\"updated_at_ms\":1,\"conversation_language\":\"en\",\"preferences\":{\"model\":\"test/model\",\"effort\":\"auto\",\"fast_mode\":false},\"history\":[],\"total_input_tokens\":0,\"total_output_tokens\":0,\"last_subagent_work_id\":\"work\",\"context_history_start\":0}", "", "{}", "{\"last_subagent_work_id\":null}", "{\"usage\":null,\"last_subagent_work_id\":\"work\"}", "{\"last_subagent_work_id\":\"work\",\"usage\":null}", } });}
test "per-turn work provenance parser handles fuzzed ownership paths" { try std.testing.fuzz({}, fuzzWorkProvenanceHistoryTurn, .{ .corpus = &.{ "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[],\"work_id\":\"work-1\"},\"assistant\":\"reply\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}", "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[]},\"assistant\":\"reply\",\"execution\":{\"schema_version\":3,\"tool_steps\":[],\"files\":[]}}", "{\"kind\":\"assistant\",\"user\":{\"text\":\"prompt\",\"images\":[],\"work_id\":\"\"}}", "", "{}", } });}
fn fuzzWorkProvenanceHistoryTurn(_: void, smith: *std.testing.Smith) !void { var buffer: [4096]u8 = undefined; const len: usize = @intCast(smith.slice(&buffer)); var parsed = std.json.parseFromSlice( std.json.Value, std.testing.allocator, buffer[0..len], .{ .allocate = .alloc_always }, ) catch return; defer parsed.deinit(); const turn = parseHistoryTurn(std.testing.allocator, parsed.value) catch return; session.freeHistoryTurn(std.testing.allocator, turn);}
fn fuzzDurableSessionOptionalFields(_: void, smith: *std.testing.Smith) !void { var buffer: [8192]u8 = undefined; const len: usize = @intCast(smith.slice(&buffer)); var source = std.Io.Reader.fixed(buffer[0..len]); var state = decodeState(std.testing.allocator, &source, .{ .max_history_turns = 8, .max_value_bytes = buffer.len, }) catch return; state.deinit(std.testing.allocator);}
test "session metadata round trips without conversation or control state" { const alloc = std.testing.allocator; const encoded = try encodeSessionMetadata(alloc, .{ .id = "session-1", .origin_workspace_root = "/tmp/origin", .workspace_root = "/tmp/current", .created_at_ms = 10, .updated_at_ms = 20, .conversation_language = "en", .provider = .gateway, .model = "openai/gpt-5.6", .effort = "high", .fast_mode = false, .title = "Compaction work", .subagent_child = false, }); defer alloc.free(encoded); try std.testing.expect(std.mem.find(u8, encoded, "history") == null); try std.testing.expect(std.mem.find(u8, encoded, "usage") == null); try std.testing.expect(std.mem.find(u8, encoded, "permission") == null); try std.testing.expect(std.mem.find(u8, encoded, "checkpoint") == null);
var decoded = try decodeSessionMetadata(alloc, encoded); defer decoded.deinit(); try std.testing.expectEqualStrings("session-1", decoded.value.id); try std.testing.expectEqualStrings("/tmp/current", decoded.value.workspace_root); try std.testing.expectEqualStrings("openai/gpt-5.6", decoded.value.model); try std.testing.expectEqualStrings("Compaction work", decoded.value.title.?);}