char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
e5ec380
main
1// What berry and an environment's worker say to each other: one json value per line, with bytes and the 2// environment's errors spelled out so that they arrive as what they were. 3 4import { Buffer } from "node:buffer" ; 5import { ExecutionError , FileError } from "@earendil-works/pi-durable/env" ; 6 7/** Bumped whenever either side's messages change shape. */ 8export const PROTOCOL = 3 ; 9 10/** What the worker says first: the machine it found itself on. */ 11export type Hello = { 12readonly protocol :number ; 13readonly platform :string ; 14readonly home :string ; 15readonly variables :Record < string , string >; 16}; 17 18export type FetchOptions = { 19method :string ; 20headers :[ string , string ][]; 21body ?:Uint8Array ; 22redirect :RequestRedirect ; 23credentials :RequestCredentials ; 24cache :RequestCache ; 25referrer :string ; 26referrerPolicy :ReferrerPolicy ; 27integrity :string ; 28}; 29 30export type FetchResponse = { 31status :number ; 32statusText :string ; 33headers :[ string , string ][]; 34url :string ; 35redirected :boolean ; 36handle ?:number ; 37}; 38 39export type Request = 40/** Call `m` with `a`, resolving paths against `cwd`. */ 41| { 42readonly i :number ; 43readonly m :string ; 44readonly a :readonly unknown []; 45readonly cwd ?:string ; 46} 47/** Abort the call `c`. */ 48| { readonly c :number }; 49 50export type Response = 51| { readonly hello :Hello } 52/** The call `i` settled with `r`. */ 53| { readonly i :number ; readonly r :unknown } 54/** The call `i` threw, which the environment's methods never mean to. */ 55| { readonly i :number ; readonly e :string } 56/** Something call `i` streams, while it runs or for as long as what it opened stays open. */ 57| { readonly i :number ; readonly o :unknown }; 58 59export function encode ( message :Request | Response ) :string { 60return JSON . stringify ( message , function ( this :Record < string , unknown >, key , converted ) { 61// what toJSON() made of it, for buffers an array of numbers 62const value = this [ key ]; 63if ( value instanceof Uint8Array ) { 64return { 65$bytes :Buffer . from ( value . buffer , value . byteOffset , value . byteLength ). toString ( "base64" ), 66}; 67} 68if ( value instanceof FileError ) { 69return { $file :{ code :value . code , message :value . message , path :value . path } }; 70} 71if ( value instanceof ExecutionError ) { 72return { $exec :{ code :value . code , message :value . message , spillPath :value . spillPath } }; 73} 74return converted ; 75}); 76} 77 78export function decode ( line :string ) :Request | Response { 79return JSON . parse ( line , ( _ , value ) => { 80if ( typeof value !== "object" || value === null ) return value ; 81if ( typeof value . $bytes === "string" ) { 82return new Uint8Array ( Buffer . from ( value . $bytes , "base64" )); 83} 84if ( value . $file ) return new FileError ( value . $file . code , value . $file . message , value . $file . path ); 85if ( value . $exec ) { 86const error = new ExecutionError ( value . $exec . code , value . $exec . message ); 87if ( value . $exec . spillPath !== undefined ) error . spillPath = value . $exec . spillPath ; 88return error ; 89} 90return value ; 91}); 92} 93 94/** Lines of a byte stream, as they complete. */ 95export async function * lines ( 96reader :ReadableStreamDefaultReader < Uint8Array >, 97buffered :Uint8Array = new Uint8Array (), 98) :AsyncGenerator < string > { 99const decoder = new TextDecoder (); 100// chunks of the line in progress, which can be a whole file's worth 101let parts :string [] = []; 102let text = decoder . decode ( buffered , { stream :true }); 103while ( true ) { 104let start = 0 ; 105for ( let end ; ( end = text . indexOf ( "\n" , start )) >= 0 ; start = end + 1 ) { 106parts . push ( text . slice ( start , end )); 107yield parts . join ( "" ); 108parts = []; 109} 110parts . push ( text . slice ( start )); 111const { value, done} = await reader . read (); 112if ( done ) return ; 113text = decoder . decode ( value , { stream :true }); 114} 115}