@@ -0,0 +1,1146 @@
1+package soy.iko.opencode.core
2+
3+import com.novi.bincode.BincodeDeserializer
4+import com.novi.bincode.BincodeSerializer
5+import com.novi.serde.Bytes
6+import com.novi.serde.DeserializationError
7+import com.novi.serde.Deserializer
8+import com.novi.serde.Serializer
9+
10+fun <T> List<T>.serialize(
11+ serializer: Serializer,
12+ serializeElement: Serializer.(T) -> Unit,
13+) {
14+ serializer.serialize_len(size.toLong())
15+ forEach { element ->
16+ serializer.serializeElement(element)
17+ }
18+}
19+
20+fun <T> Deserializer.deserializeListOf(deserializeElement: (Deserializer) -> T): List<T> {
21+ val length = deserialize_len()
22+ val list = mutableListOf<T>()
23+ repeat(length.toInt()) {
24+ list.add(deserializeElement(this))
25+ }
26+ return list
27+}
28+
29+fun <T> T?.serializeOptionOf(
30+ serializer: Serializer,
31+ serializeElement: Serializer.(T) -> Unit,
32+) {
33+ if (this != null) {
34+ serializer.serialize_option_tag(true)
35+ serializer.serializeElement(this)
36+ } else {
37+ serializer.serialize_option_tag(false)
38+ }
39+}
40+
41+fun <T> Deserializer.deserializeOptionOf(deserializeElement: (Deserializer) -> T): T? {
42+ val tag = deserialize_option_tag()
43+ return if (tag) {
44+ deserializeElement(this)
45+ } else {
46+ null
47+ }
48+}
49+
50+sealed interface Effect {
51+ fun serialize(serializer: Serializer)
52+
53+ fun bincodeSerialize(): ByteArray {
54+ val serializer = BincodeSerializer()
55+ serialize(serializer)
56+ return serializer.get_bytes()
57+ }
58+
59+ data class Render(
60+ val value: soy.iko.opencode.core.RenderOperation,
61+ ) : Effect {
62+ override fun serialize(serializer: Serializer) {
63+ serializer.increase_container_depth()
64+ serializer.serialize_variant_index(0)
65+ value.serialize(serializer)
66+ serializer.decrease_container_depth()
67+ }
68+
69+ companion object {
70+ fun deserialize(deserializer: Deserializer): Render {
71+ deserializer.increase_container_depth()
72+ val value = soy.iko.opencode.core.RenderOperation.deserialize(deserializer)
73+ deserializer.decrease_container_depth()
74+ return Render(value)
75+ }
76+ }
77+ }
78+
79+ data class Http(
80+ val value: soy.iko.opencode.core.HttpRequest,
81+ ) : Effect {
82+ override fun serialize(serializer: Serializer) {
83+ serializer.increase_container_depth()
84+ serializer.serialize_variant_index(1)
85+ value.serialize(serializer)
86+ serializer.decrease_container_depth()
87+ }
88+
89+ companion object {
90+ fun deserialize(deserializer: Deserializer): Http {
91+ deserializer.increase_container_depth()
92+ val value = soy.iko.opencode.core.HttpRequest.deserialize(deserializer)
93+ deserializer.decrease_container_depth()
94+ return Http(value)
95+ }
96+ }
97+ }
98+
99+ companion object {
100+ @Throws(DeserializationError::class)
101+ fun deserialize(deserializer: Deserializer): Effect {
102+ val index = deserializer.deserialize_variant_index()
103+ return when (index) {
104+ 0 -> Render.deserialize(deserializer)
105+ 1 -> Http.deserialize(deserializer)
106+ else -> throw DeserializationError("Unknown variant index for Effect: $index")
107+ }
108+ }
109+
110+ @Throws(DeserializationError::class)
111+ fun bincodeDeserialize(input: ByteArray?): Effect {
112+ if (input == null) {
113+ throw DeserializationError("Cannot deserialize null array")
114+ }
115+ val deserializer = BincodeDeserializer(input)
116+ val value = deserialize(deserializer)
117+ if (deserializer.get_buffer_offset() < input.size) {
118+ throw DeserializationError("Some input bytes were not read")
119+ }
120+ return value
121+ }
122+ }
123+}
124+
125+sealed interface Event {
126+ fun serialize(serializer: Serializer)
127+
128+ fun bincodeSerialize(): ByteArray {
129+ val serializer = BincodeSerializer()
130+ serialize(serializer)
131+ return serializer.get_bytes()
132+ }
133+
134+ data object Start: Event {
135+ override fun serialize(serializer: Serializer) {
136+ serializer.increase_container_depth()
137+ serializer.serialize_variant_index(0)
138+ serializer.decrease_container_depth()
139+ }
140+
141+ fun deserialize(deserializer: Deserializer): Start {
142+ return Start
143+ }
144+ }
145+
146+ data class ServerUrlChanged(
147+ val value: String,
148+ ) : Event {
149+ override fun serialize(serializer: Serializer) {
150+ serializer.increase_container_depth()
151+ serializer.serialize_variant_index(1)
152+ serializer.serialize_str(value)
153+ serializer.decrease_container_depth()
154+ }
155+
156+ companion object {
157+ fun deserialize(deserializer: Deserializer): ServerUrlChanged {
158+ deserializer.increase_container_depth()
159+ val value = deserializer.deserialize_str()
160+ deserializer.decrease_container_depth()
161+ return ServerUrlChanged(value)
162+ }
163+ }
164+ }
165+
166+ data class UsernameChanged(
167+ val value: String,
168+ ) : Event {
169+ override fun serialize(serializer: Serializer) {
170+ serializer.increase_container_depth()
171+ serializer.serialize_variant_index(2)
172+ serializer.serialize_str(value)
173+ serializer.decrease_container_depth()
174+ }
175+
176+ companion object {
177+ fun deserialize(deserializer: Deserializer): UsernameChanged {
178+ deserializer.increase_container_depth()
179+ val value = deserializer.deserialize_str()
180+ deserializer.decrease_container_depth()
181+ return UsernameChanged(value)
182+ }
183+ }
184+ }
185+
186+ data class PasswordChanged(
187+ val value: String,
188+ ) : Event {
189+ override fun serialize(serializer: Serializer) {
190+ serializer.increase_container_depth()
191+ serializer.serialize_variant_index(3)
192+ serializer.serialize_str(value)
193+ serializer.decrease_container_depth()
194+ }
195+
196+ companion object {
197+ fun deserialize(deserializer: Deserializer): PasswordChanged {
198+ deserializer.increase_container_depth()
199+ val value = deserializer.deserialize_str()
200+ deserializer.decrease_container_depth()
201+ return PasswordChanged(value)
202+ }
203+ }
204+ }
205+
206+ data object Connect: Event {
207+ override fun serialize(serializer: Serializer) {
208+ serializer.increase_container_depth()
209+ serializer.serialize_variant_index(4)
210+ serializer.decrease_container_depth()
211+ }
212+
213+ fun deserialize(deserializer: Deserializer): Connect {
214+ return Connect
215+ }
216+ }
217+
218+ data object CancelAuth: Event {
219+ override fun serialize(serializer: Serializer) {
220+ serializer.increase_container_depth()
221+ serializer.serialize_variant_index(5)
222+ serializer.decrease_container_depth()
223+ }
224+
225+ fun deserialize(deserializer: Deserializer): CancelAuth {
226+ return CancelAuth
227+ }
228+ }
229+
230+ data object LoadSessions: Event {
231+ override fun serialize(serializer: Serializer) {
232+ serializer.increase_container_depth()
233+ serializer.serialize_variant_index(6)
234+ serializer.decrease_container_depth()
235+ }
236+
237+ fun deserialize(deserializer: Deserializer): LoadSessions {
238+ return LoadSessions
239+ }
240+ }
241+
242+ data class SelectSession(
243+ val value: String,
244+ ) : Event {
245+ override fun serialize(serializer: Serializer) {
246+ serializer.increase_container_depth()
247+ serializer.serialize_variant_index(7)
248+ serializer.serialize_str(value)
249+ serializer.decrease_container_depth()
250+ }
251+
252+ companion object {
253+ fun deserialize(deserializer: Deserializer): SelectSession {
254+ deserializer.increase_container_depth()
255+ val value = deserializer.deserialize_str()
256+ deserializer.decrease_container_depth()
257+ return SelectSession(value)
258+ }
259+ }
260+ }
261+
262+ data object CreateSession: Event {
263+ override fun serialize(serializer: Serializer) {
264+ serializer.increase_container_depth()
265+ serializer.serialize_variant_index(8)
266+ serializer.decrease_container_depth()
267+ }
268+
269+ fun deserialize(deserializer: Deserializer): CreateSession {
270+ return CreateSession
271+ }
272+ }
273+
274+ data class LoadMessages(
275+ val value: String,
276+ ) : Event {
277+ override fun serialize(serializer: Serializer) {
278+ serializer.increase_container_depth()
279+ serializer.serialize_variant_index(9)
280+ serializer.serialize_str(value)
281+ serializer.decrease_container_depth()
282+ }
283+
284+ companion object {
285+ fun deserialize(deserializer: Deserializer): LoadMessages {
286+ deserializer.increase_container_depth()
287+ val value = deserializer.deserialize_str()
288+ deserializer.decrease_container_depth()
289+ return LoadMessages(value)
290+ }
291+ }
292+ }
293+
294+ data class SendMessage(
295+ val value: String,
296+ ) : Event {
297+ override fun serialize(serializer: Serializer) {
298+ serializer.increase_container_depth()
299+ serializer.serialize_variant_index(10)
300+ serializer.serialize_str(value)
301+ serializer.decrease_container_depth()
302+ }
303+
304+ companion object {
305+ fun deserialize(deserializer: Deserializer): SendMessage {
306+ deserializer.increase_container_depth()
307+ val value = deserializer.deserialize_str()
308+ deserializer.decrease_container_depth()
309+ return SendMessage(value)
310+ }
311+ }
312+ }
313+
314+ data class EventReceived(
315+ val value: String,
316+ ) : Event {
317+ override fun serialize(serializer: Serializer) {
318+ serializer.increase_container_depth()
319+ serializer.serialize_variant_index(11)
320+ serializer.serialize_str(value)
321+ serializer.decrease_container_depth()
322+ }
323+
324+ companion object {
325+ fun deserialize(deserializer: Deserializer): EventReceived {
326+ deserializer.increase_container_depth()
327+ val value = deserializer.deserialize_str()
328+ deserializer.decrease_container_depth()
329+ return EventReceived(value)
330+ }
331+ }
332+ }
333+
334+ data class NavigateToChat(
335+ val value: String,
336+ ) : Event {
337+ override fun serialize(serializer: Serializer) {
338+ serializer.increase_container_depth()
339+ serializer.serialize_variant_index(12)
340+ serializer.serialize_str(value)
341+ serializer.decrease_container_depth()
342+ }
343+
344+ companion object {
345+ fun deserialize(deserializer: Deserializer): NavigateToChat {
346+ deserializer.increase_container_depth()
347+ val value = deserializer.deserialize_str()
348+ deserializer.decrease_container_depth()
349+ return NavigateToChat(value)
350+ }
351+ }
352+ }
353+
354+ data object NavigateToSessions: Event {
355+ override fun serialize(serializer: Serializer) {
356+ serializer.increase_container_depth()
357+ serializer.serialize_variant_index(13)
358+ serializer.decrease_container_depth()
359+ }
360+
361+ fun deserialize(deserializer: Deserializer): NavigateToSessions {
362+ return NavigateToSessions
363+ }
364+ }
365+
366+ data object DismissError: Event {
367+ override fun serialize(serializer: Serializer) {
368+ serializer.increase_container_depth()
369+ serializer.serialize_variant_index(14)
370+ serializer.decrease_container_depth()
371+ }
372+
373+ fun deserialize(deserializer: Deserializer): DismissError {
374+ return DismissError
375+ }
376+ }
377+
378+ companion object {
379+ @Throws(DeserializationError::class)
380+ fun deserialize(deserializer: Deserializer): Event {
381+ val index = deserializer.deserialize_variant_index()
382+ return when (index) {
383+ 0 -> Start.deserialize(deserializer)
384+ 1 -> ServerUrlChanged.deserialize(deserializer)
385+ 2 -> UsernameChanged.deserialize(deserializer)
386+ 3 -> PasswordChanged.deserialize(deserializer)
387+ 4 -> Connect.deserialize(deserializer)
388+ 5 -> CancelAuth.deserialize(deserializer)
389+ 6 -> LoadSessions.deserialize(deserializer)
390+ 7 -> SelectSession.deserialize(deserializer)
391+ 8 -> CreateSession.deserialize(deserializer)
392+ 9 -> LoadMessages.deserialize(deserializer)
393+ 10 -> SendMessage.deserialize(deserializer)
394+ 11 -> EventReceived.deserialize(deserializer)
395+ 12 -> NavigateToChat.deserialize(deserializer)
396+ 13 -> NavigateToSessions.deserialize(deserializer)
397+ 14 -> DismissError.deserialize(deserializer)
398+ else -> throw DeserializationError("Unknown variant index for Event: $index")
399+ }
400+ }
401+
402+ @Throws(DeserializationError::class)
403+ fun bincodeDeserialize(input: ByteArray?): Event {
404+ if (input == null) {
405+ throw DeserializationError("Cannot deserialize null array")
406+ }
407+ val deserializer = BincodeDeserializer(input)
408+ val value = deserialize(deserializer)
409+ if (deserializer.get_buffer_offset() < input.size) {
410+ throw DeserializationError("Some input bytes were not read")
411+ }
412+ return value
413+ }
414+ }
415+}
416+
417+/// An error produced when an HTTP request fails.
418+///
419+/// Variants fall into two groups:
420+///
421+/// **Transport errors** — generated by the shell when it cannot complete the HTTP
422+/// exchange. These cross the FFI boundary and are serialized in the protocol:
423+/// [`Url`](HttpError::Url), [`Io`](HttpError::Io), [`Timeout`](HttpError::Timeout).
424+///
425+/// **Processing errors** — generated on the Rust side after a response arrives.
426+/// These are never serialized or visible to shells:
427+///
428+/// - [`Http`](HttpError::Http) — produced by `Response::new()` when the server returns
429+/// a 4xx or 5xx status. At the *protocol* level these arrive as
430+/// [`HttpResult::Ok`](crate::protocol::HttpResult::Ok); `Response::new()` converts
431+/// them here, so app code using `crux_http::Result<Response<T>>` will see them as
432+/// `Err(HttpError::Http { code, .. })`.
433+/// - [`Json`](HttpError::Json) — produced when response body deserialisation fails.
434+sealed interface HttpError {
435+ fun serialize(serializer: Serializer)
436+
437+ fun bincodeSerialize(): ByteArray {
438+ val serializer = BincodeSerializer()
439+ serialize(serializer)
440+ return serializer.get_bytes()
441+ }
442+
443+ /// The request URL could not be parsed.
444+ data class Url(
445+ val value: String,
446+ ) : HttpError {
447+ override fun serialize(serializer: Serializer) {
448+ serializer.increase_container_depth()
449+ serializer.serialize_variant_index(0)
450+ serializer.serialize_str(value)
451+ serializer.decrease_container_depth()
452+ }
453+
454+ companion object {
455+ fun deserialize(deserializer: Deserializer): Url {
456+ deserializer.increase_container_depth()
457+ val value = deserializer.deserialize_str()
458+ deserializer.decrease_container_depth()
459+ return Url(value)
460+ }
461+ }
462+ }
463+
464+ /// An IO error prevented the request from completing.
465+ data class Io(
466+ val value: String,
467+ ) : HttpError {
468+ override fun serialize(serializer: Serializer) {
469+ serializer.increase_container_depth()
470+ serializer.serialize_variant_index(1)
471+ serializer.serialize_str(value)
472+ serializer.decrease_container_depth()
473+ }
474+
475+ companion object {
476+ fun deserialize(deserializer: Deserializer): Io {
477+ deserializer.increase_container_depth()
478+ val value = deserializer.deserialize_str()
479+ deserializer.decrease_container_depth()
480+ return Io(value)
481+ }
482+ }
483+ }
484+
485+ /// The request timed out before a response was received.
486+ data object Timeout: HttpError {
487+ override fun serialize(serializer: Serializer) {
488+ serializer.increase_container_depth()
489+ serializer.serialize_variant_index(2)
490+ serializer.decrease_container_depth()
491+ }
492+
493+ fun deserialize(deserializer: Deserializer): Timeout {
494+ return Timeout
495+ }
496+ }
497+
498+ companion object {
499+ @Throws(DeserializationError::class)
500+ fun deserialize(deserializer: Deserializer): HttpError {
501+ val index = deserializer.deserialize_variant_index()
502+ return when (index) {
503+ 0 -> Url.deserialize(deserializer)
504+ 1 -> Io.deserialize(deserializer)
505+ 2 -> Timeout.deserialize(deserializer)
506+ else -> throw DeserializationError("Unknown variant index for HttpError: $index")
507+ }
508+ }
509+
510+ @Throws(DeserializationError::class)
511+ fun bincodeDeserialize(input: ByteArray?): HttpError {
512+ if (input == null) {
513+ throw DeserializationError("Cannot deserialize null array")
514+ }
515+ val deserializer = BincodeDeserializer(input)
516+ val value = deserialize(deserializer)
517+ if (deserializer.get_buffer_offset() < input.size) {
518+ throw DeserializationError("Some input bytes were not read")
519+ }
520+ return value
521+ }
522+ }
523+}
524+
525+data class HttpHeader(
526+ val name: String,
527+ val value: String,
528+) {
529+ fun serialize(serializer: Serializer) {
530+ serializer.increase_container_depth()
531+ serializer.serialize_str(name)
532+ serializer.serialize_str(value)
533+ serializer.decrease_container_depth()
534+ }
535+
536+ fun bincodeSerialize(): ByteArray {
537+ val serializer = BincodeSerializer()
538+ serialize(serializer)
539+ return serializer.get_bytes()
540+ }
541+
542+ companion object {
543+ fun deserialize(deserializer: Deserializer): HttpHeader {
544+ deserializer.increase_container_depth()
545+ val name = deserializer.deserialize_str()
546+ val value = deserializer.deserialize_str()
547+ deserializer.decrease_container_depth()
548+ return HttpHeader(name, value)
549+ }
550+
551+ @Throws(DeserializationError::class)
552+ fun bincodeDeserialize(input: ByteArray?): HttpHeader {
553+ if (input == null) {
554+ throw DeserializationError("Cannot deserialize null array")
555+ }
556+ val deserializer = BincodeDeserializer(input)
557+ val value = deserialize(deserializer)
558+ if (deserializer.get_buffer_offset() < input.size) {
559+ throw DeserializationError("Some input bytes were not read")
560+ }
561+ return value
562+ }
563+ }
564+}
565+
566+/// A raw HTTP request, as sent to the shell over the protocol boundary.
567+///
568+/// # No header validation
569+///
570+/// All fields are plain strings. Header names and values are carried as-is with no
571+/// validation against the HTTP specification. This is intentional: `HttpRequest` is a
572+/// cross-language data carrier deserialised by Swift, Kotlin, and TypeScript shells;
573+/// Rust's `http`-crate validation rules cannot be enforced on the other side of that
574+/// boundary.
575+///
576+/// **Shell authors must not assume that header names or values are well-formed.**
577+/// Pass them to your underlying HTTP client as-is — it will apply its own rules.
578+///
579+/// For the ergonomic Rust-side builder that *does* validate header values (and panics
580+/// on invalid input), see [`crate::command::RequestBuilder`].
581+data class HttpRequest(
582+ val method: String,
583+ val url: String,
584+ val headers: List<soy.iko.opencode.core.HttpHeader>,
585+ val body: Bytes,
586+) {
587+ fun serialize(serializer: Serializer) {
588+ serializer.increase_container_depth()
589+ serializer.serialize_str(method)
590+ serializer.serialize_str(url)
591+ headers.serialize(serializer) {
592+ it.serialize(serializer)
593+ }
594+ serializer.serialize_bytes(body)
595+ serializer.decrease_container_depth()
596+ }
597+
598+ fun bincodeSerialize(): ByteArray {
599+ val serializer = BincodeSerializer()
600+ serialize(serializer)
601+ return serializer.get_bytes()
602+ }
603+
604+ companion object {
605+ fun deserialize(deserializer: Deserializer): HttpRequest {
606+ deserializer.increase_container_depth()
607+ val method = deserializer.deserialize_str()
608+ val url = deserializer.deserialize_str()
609+ val headers =
610+ deserializer.deserializeListOf {
611+ soy.iko.opencode.core.HttpHeader.deserialize(deserializer)
612+ }
613+ val body = deserializer.deserialize_bytes()
614+ deserializer.decrease_container_depth()
615+ return HttpRequest(method, url, headers, body)
616+ }
617+
618+ @Throws(DeserializationError::class)
619+ fun bincodeDeserialize(input: ByteArray?): HttpRequest {
620+ if (input == null) {
621+ throw DeserializationError("Cannot deserialize null array")
622+ }
623+ val deserializer = BincodeDeserializer(input)
624+ val value = deserialize(deserializer)
625+ if (deserializer.get_buffer_offset() < input.size) {
626+ throw DeserializationError("Some input bytes were not read")
627+ }
628+ return value
629+ }
630+ }
631+}
632+
633+data class HttpResponse(
634+ val status: UShort,
635+ val headers: List<soy.iko.opencode.core.HttpHeader>,
636+ val body: Bytes,
637+) {
638+ fun serialize(serializer: Serializer) {
639+ serializer.increase_container_depth()
640+ serializer.serialize_u16(status)
641+ headers.serialize(serializer) {
642+ it.serialize(serializer)
643+ }
644+ serializer.serialize_bytes(body)
645+ serializer.decrease_container_depth()
646+ }
647+
648+ fun bincodeSerialize(): ByteArray {
649+ val serializer = BincodeSerializer()
650+ serialize(serializer)
651+ return serializer.get_bytes()
652+ }
653+
654+ companion object {
655+ fun deserialize(deserializer: Deserializer): HttpResponse {
656+ deserializer.increase_container_depth()
657+ val status = deserializer.deserialize_u16()
658+ val headers =
659+ deserializer.deserializeListOf {
660+ soy.iko.opencode.core.HttpHeader.deserialize(deserializer)
661+ }
662+ val body = deserializer.deserialize_bytes()
663+ deserializer.decrease_container_depth()
664+ return HttpResponse(status, headers, body)
665+ }
666+
667+ @Throws(DeserializationError::class)
668+ fun bincodeDeserialize(input: ByteArray?): HttpResponse {
669+ if (input == null) {
670+ throw DeserializationError("Cannot deserialize null array")
671+ }
672+ val deserializer = BincodeDeserializer(input)
673+ val value = deserialize(deserializer)
674+ if (deserializer.get_buffer_offset() < input.size) {
675+ throw DeserializationError("Some input bytes were not read")
676+ }
677+ return value
678+ }
679+ }
680+}
681+
682+/// The result of an HTTP request, as returned by the shell over the protocol boundary.
683+///
684+/// # Status codes are not errors
685+///
686+/// Any completed HTTP exchange — including responses with 4xx or 5xx status codes — is
687+/// returned as [`HttpResult::Ok`]. Only *transport-level* failures (the shell could not
688+/// reach the server at all) produce [`HttpResult::Err`].
689+///
690+/// To act on an error status, inspect [`HttpResponse::status`]:
691+///
692+/// ```
693+/// # use crux_http::protocol::{HttpResult, HttpResponse};
694+/// # use crux_http::HttpError;
695+/// # fn handle(result: HttpResult) {
696+/// match result {
697+/// HttpResult::Ok(response) if response.status == 200 => { /* success */ }
698+/// HttpResult::Ok(response) if response.status == 404 => { /* not found */ }
699+/// HttpResult::Ok(response) if response.status >= 500 => { /* server error */ }
700+/// HttpResult::Ok(_) => { /* other status */ }
701+/// HttpResult::Err(e) => { /* transport failure: bad URL, IO error, or timeout */ }
702+/// }
703+/// # }
704+/// ```
705+sealed interface HttpResult {
706+ fun serialize(serializer: Serializer)
707+
708+ fun bincodeSerialize(): ByteArray {
709+ val serializer = BincodeSerializer()
710+ serialize(serializer)
711+ return serializer.get_bytes()
712+ }
713+
714+ /// The shell completed the HTTP exchange. The response may carry any status code,
715+ /// including 4xx and 5xx — inspect [`HttpResponse::status`] to distinguish them.
716+ data class Ok(
717+ val value: soy.iko.opencode.core.HttpResponse,
718+ ) : HttpResult {
719+ override fun serialize(serializer: Serializer) {
720+ serializer.increase_container_depth()
721+ serializer.serialize_variant_index(0)
722+ value.serialize(serializer)
723+ serializer.decrease_container_depth()
724+ }
725+
726+ companion object {
727+ fun deserialize(deserializer: Deserializer): Ok {
728+ deserializer.increase_container_depth()
729+ val value = soy.iko.opencode.core.HttpResponse.deserialize(deserializer)
730+ deserializer.decrease_container_depth()
731+ return Ok(value)
732+ }
733+ }
734+ }
735+
736+ /// The shell could not complete the HTTP exchange due to a transport-level failure.
737+ /// See [`HttpError`] for the possible causes.
738+ data class Err(
739+ val value: soy.iko.opencode.core.HttpError,
740+ ) : HttpResult {
741+ override fun serialize(serializer: Serializer) {
742+ serializer.increase_container_depth()
743+ serializer.serialize_variant_index(1)
744+ value.serialize(serializer)
745+ serializer.decrease_container_depth()
746+ }
747+
748+ companion object {
749+ fun deserialize(deserializer: Deserializer): Err {
750+ deserializer.increase_container_depth()
751+ val value = soy.iko.opencode.core.HttpError.deserialize(deserializer)
752+ deserializer.decrease_container_depth()
753+ return Err(value)
754+ }
755+ }
756+ }
757+
758+ companion object {
759+ @Throws(DeserializationError::class)
760+ fun deserialize(deserializer: Deserializer): HttpResult {
761+ val index = deserializer.deserialize_variant_index()
762+ return when (index) {
763+ 0 -> Ok.deserialize(deserializer)
764+ 1 -> Err.deserialize(deserializer)
765+ else -> throw DeserializationError("Unknown variant index for HttpResult: $index")
766+ }
767+ }
768+
769+ @Throws(DeserializationError::class)
770+ fun bincodeDeserialize(input: ByteArray?): HttpResult {
771+ if (input == null) {
772+ throw DeserializationError("Cannot deserialize null array")
773+ }
774+ val deserializer = BincodeDeserializer(input)
775+ val value = deserialize(deserializer)
776+ if (deserializer.get_buffer_offset() < input.size) {
777+ throw DeserializationError("Some input bytes were not read")
778+ }
779+ return value
780+ }
781+ }
782+}
783+
784+data class MessageView(
785+ val id: String,
786+ val role: String,
787+ val text: String,
788+ val time: ULong,
789+) {
790+ fun serialize(serializer: Serializer) {
791+ serializer.increase_container_depth()
792+ serializer.serialize_str(id)
793+ serializer.serialize_str(role)
794+ serializer.serialize_str(text)
795+ serializer.serialize_u64(time)
796+ serializer.decrease_container_depth()
797+ }
798+
799+ fun bincodeSerialize(): ByteArray {
800+ val serializer = BincodeSerializer()
801+ serialize(serializer)
802+ return serializer.get_bytes()
803+ }
804+
805+ companion object {
806+ fun deserialize(deserializer: Deserializer): MessageView {
807+ deserializer.increase_container_depth()
808+ val id = deserializer.deserialize_str()
809+ val role = deserializer.deserialize_str()
810+ val text = deserializer.deserialize_str()
811+ val time = deserializer.deserialize_u64()
812+ deserializer.decrease_container_depth()
813+ return MessageView(id, role, text, time)
814+ }
815+
816+ @Throws(DeserializationError::class)
817+ fun bincodeDeserialize(input: ByteArray?): MessageView {
818+ if (input == null) {
819+ throw DeserializationError("Cannot deserialize null array")
820+ }
821+ val deserializer = BincodeDeserializer(input)
822+ val value = deserialize(deserializer)
823+ if (deserializer.get_buffer_offset() < input.size) {
824+ throw DeserializationError("Some input bytes were not read")
825+ }
826+ return value
827+ }
828+ }
829+}
830+
831+/// The single operation `Render` implements.
832+data object RenderOperation {
833+ fun serialize(serializer: Serializer) {}
834+
835+ fun bincodeSerialize(): ByteArray {
836+ val serializer = BincodeSerializer()
837+ serialize(serializer)
838+ return serializer.get_bytes()
839+ }
840+
841+ fun deserialize(deserializer: Deserializer): RenderOperation {
842+ return RenderOperation
843+ }
844+
845+ @Throws(DeserializationError::class)
846+ fun bincodeDeserialize(input: ByteArray?): RenderOperation {
847+ if (input == null) {
848+ throw DeserializationError("Cannot deserialize null array")
849+ }
850+ val deserializer = BincodeDeserializer(input)
851+ val value = deserialize(deserializer)
852+ if (deserializer.get_buffer_offset() < input.size) {
853+ throw DeserializationError("Some input bytes were not read")
854+ }
855+ return value
856+ }
857+}
858+
859+/// Request for a side-effect passed from the Core to the Shell.
860+///
861+/// The `EffectId` links the `Request` with the corresponding call to [`Core::resolve`] to pass the data back
862+/// to the [`App::update`] function (wrapped in the event provided to the capability originating the effect).
863+data class Request(
864+ val id: UInt,
865+ val effect: soy.iko.opencode.core.Effect,
866+) {
867+ fun serialize(serializer: Serializer) {
868+ serializer.increase_container_depth()
869+ serializer.serialize_u32(id)
870+ effect.serialize(serializer)
871+ serializer.decrease_container_depth()
872+ }
873+
874+ fun bincodeSerialize(): ByteArray {
875+ val serializer = BincodeSerializer()
876+ serialize(serializer)
877+ return serializer.get_bytes()
878+ }
879+
880+ companion object {
881+ fun deserialize(deserializer: Deserializer): Request {
882+ deserializer.increase_container_depth()
883+ val id = deserializer.deserialize_u32()
884+ val effect = soy.iko.opencode.core.Effect.deserialize(deserializer)
885+ deserializer.decrease_container_depth()
886+ return Request(id, effect)
887+ }
888+
889+ @Throws(DeserializationError::class)
890+ fun bincodeDeserialize(input: ByteArray?): Request {
891+ if (input == null) {
892+ throw DeserializationError("Cannot deserialize null array")
893+ }
894+ val deserializer = BincodeDeserializer(input)
895+ val value = deserialize(deserializer)
896+ if (deserializer.get_buffer_offset() < input.size) {
897+ throw DeserializationError("Some input bytes were not read")
898+ }
899+ return value
900+ }
901+ }
902+}
903+
904+/// A batch of effect requests from the Core to the Shell, as serialised by
905+/// [`Bridge::update`] and [`Bridge::resolve`].
906+///
907+/// The wire format is identical to `Vec<Request<Eff>>` (the newtype is
908+/// `serde(transparent)`), so existing shell code that already deserialises
909+/// a `Vec<Request>` remains binary-compatible.
910+///
911+/// Registering this type with the type-generation system causes the code
912+/// generators to emit a `Requests` type (with a `value` field containing the
913+/// list) together with a top-level `bincodeDeserialize` / `BincodeDeserialize`
914+/// helper, replacing the hand-written extension files that were previously
915+/// appended by `add_extensions()`.
916+data class Requests(
917+ val value: List<soy.iko.opencode.core.Request>,
918+) {
919+ fun serialize(serializer: Serializer) {
920+ serializer.increase_container_depth()
921+ value.serialize(serializer) {
922+ it.serialize(serializer)
923+ }
924+ serializer.decrease_container_depth()
925+ }
926+
927+ fun bincodeSerialize(): ByteArray {
928+ val serializer = BincodeSerializer()
929+ serialize(serializer)
930+ return serializer.get_bytes()
931+ }
932+
933+ companion object {
934+ fun deserialize(deserializer: Deserializer): Requests {
935+ deserializer.increase_container_depth()
936+ val value =
937+ deserializer.deserializeListOf {
938+ soy.iko.opencode.core.Request.deserialize(deserializer)
939+ }
940+ deserializer.decrease_container_depth()
941+ return Requests(value)
942+ }
943+
944+ @Throws(DeserializationError::class)
945+ fun bincodeDeserialize(input: ByteArray?): Requests {
946+ if (input == null) {
947+ throw DeserializationError("Cannot deserialize null array")
948+ }
949+ val deserializer = BincodeDeserializer(input)
950+ val value = deserialize(deserializer)
951+ if (deserializer.get_buffer_offset() < input.size) {
952+ throw DeserializationError("Some input bytes were not read")
953+ }
954+ return value
955+ }
956+ }
957+}
958+
959+enum class Screen {
960+ CONNECT,
961+ SESSIONS,
962+ CHAT;
963+
964+ fun serialize(serializer: Serializer) {
965+ serializer.increase_container_depth()
966+ serializer.serialize_variant_index(ordinal)
967+ serializer.decrease_container_depth()
968+ }
969+
970+ fun bincodeSerialize(): ByteArray {
971+ val serializer = BincodeSerializer()
972+ serialize(serializer)
973+ return serializer.get_bytes()
974+ }
975+
976+ companion object {
977+ @Throws(DeserializationError::class)
978+ fun deserialize(deserializer: Deserializer): Screen {
979+ deserializer.increase_container_depth()
980+ val index = deserializer.deserialize_variant_index()
981+ deserializer.decrease_container_depth()
982+ return when (index) {
983+ 0 -> CONNECT
984+ 1 -> SESSIONS
985+ 2 -> CHAT
986+ else -> throw DeserializationError("Unknown variant index for Screen: $index")
987+ }
988+ }
989+
990+ @Throws(DeserializationError::class)
991+ fun bincodeDeserialize(input: ByteArray?): Screen {
992+ if (input == null) {
993+ throw DeserializationError("Cannot deserialize null array")
994+ }
995+ val deserializer = BincodeDeserializer(input)
996+ val value = deserialize(deserializer)
997+ if (deserializer.get_buffer_offset() < input.size) {
998+ throw DeserializationError("Some input bytes were not read")
999+ }
1000+ return value
1001+ }
1002+ }
1003+}
1004+
1005+data class SessionView(
1006+ val id: String,
1007+ val title: String,
1008+) {
1009+ fun serialize(serializer: Serializer) {
1010+ serializer.increase_container_depth()
1011+ serializer.serialize_str(id)
1012+ serializer.serialize_str(title)
1013+ serializer.decrease_container_depth()
1014+ }
1015+
1016+ fun bincodeSerialize(): ByteArray {
1017+ val serializer = BincodeSerializer()
1018+ serialize(serializer)
1019+ return serializer.get_bytes()
1020+ }
1021+
1022+ companion object {
1023+ fun deserialize(deserializer: Deserializer): SessionView {
1024+ deserializer.increase_container_depth()
1025+ val id = deserializer.deserialize_str()
1026+ val title = deserializer.deserialize_str()
1027+ deserializer.decrease_container_depth()
1028+ return SessionView(id, title)
1029+ }
1030+
1031+ @Throws(DeserializationError::class)
1032+ fun bincodeDeserialize(input: ByteArray?): SessionView {
1033+ if (input == null) {
1034+ throw DeserializationError("Cannot deserialize null array")
1035+ }
1036+ val deserializer = BincodeDeserializer(input)
1037+ val value = deserialize(deserializer)
1038+ if (deserializer.get_buffer_offset() < input.size) {
1039+ throw DeserializationError("Some input bytes were not read")
1040+ }
1041+ return value
1042+ }
1043+ }
1044+}
1045+
1046+data class ViewModel(
1047+ val screen: soy.iko.opencode.core.Screen,
1048+ val serverUrl: String,
1049+ val username: String,
1050+ val password: String,
1051+ val authRequired: Boolean,
1052+ val connected: Boolean,
1053+ val loading: Boolean,
1054+ val error: String? = null,
1055+ val sessions: List<soy.iko.opencode.core.SessionView>,
1056+ val currentSessionId: String? = null,
1057+ val messages: List<soy.iko.opencode.core.MessageView>,
1058+ val draftMessage: String,
1059+ val crashLogCount: UInt,
1060+ val latestCrashLog: String? = null,
1061+) {
1062+ fun serialize(serializer: Serializer) {
1063+ serializer.increase_container_depth()
1064+ screen.serialize(serializer)
1065+ serializer.serialize_str(serverUrl)
1066+ serializer.serialize_str(username)
1067+ serializer.serialize_str(password)
1068+ serializer.serialize_bool(authRequired)
1069+ serializer.serialize_bool(connected)
1070+ serializer.serialize_bool(loading)
1071+ error.serializeOptionOf(serializer) {
1072+ serializer.serialize_str(it)
1073+ }
1074+ sessions.serialize(serializer) {
1075+ it.serialize(serializer)
1076+ }
1077+ currentSessionId.serializeOptionOf(serializer) {
1078+ serializer.serialize_str(it)
1079+ }
1080+ messages.serialize(serializer) {
1081+ it.serialize(serializer)
1082+ }
1083+ serializer.serialize_str(draftMessage)
1084+ serializer.serialize_u32(crashLogCount)
1085+ latestCrashLog.serializeOptionOf(serializer) {
1086+ serializer.serialize_str(it)
1087+ }
1088+ serializer.decrease_container_depth()
1089+ }
1090+
1091+ fun bincodeSerialize(): ByteArray {
1092+ val serializer = BincodeSerializer()
1093+ serialize(serializer)
1094+ return serializer.get_bytes()
1095+ }
1096+
1097+ companion object {
1098+ fun deserialize(deserializer: Deserializer): ViewModel {
1099+ deserializer.increase_container_depth()
1100+ val screen = soy.iko.opencode.core.Screen.deserialize(deserializer)
1101+ val serverUrl = deserializer.deserialize_str()
1102+ val username = deserializer.deserialize_str()
1103+ val password = deserializer.deserialize_str()
1104+ val authRequired = deserializer.deserialize_bool()
1105+ val connected = deserializer.deserialize_bool()
1106+ val loading = deserializer.deserialize_bool()
1107+ val error =
1108+ deserializer.deserializeOptionOf {
1109+ deserializer.deserialize_str()
1110+ }
1111+ val sessions =
1112+ deserializer.deserializeListOf {
1113+ soy.iko.opencode.core.SessionView.deserialize(deserializer)
1114+ }
1115+ val currentSessionId =
1116+ deserializer.deserializeOptionOf {
1117+ deserializer.deserialize_str()
1118+ }
1119+ val messages =
1120+ deserializer.deserializeListOf {
1121+ soy.iko.opencode.core.MessageView.deserialize(deserializer)
1122+ }
1123+ val draftMessage = deserializer.deserialize_str()
1124+ val crashLogCount = deserializer.deserialize_u32()
1125+ val latestCrashLog =
1126+ deserializer.deserializeOptionOf {
1127+ deserializer.deserialize_str()
1128+ }
1129+ deserializer.decrease_container_depth()
1130+ return ViewModel(screen, serverUrl, username, password, authRequired, connected, loading, error, sessions, currentSessionId, messages, draftMessage, crashLogCount, latestCrashLog)
1131+ }
1132+
1133+ @Throws(DeserializationError::class)
1134+ fun bincodeDeserialize(input: ByteArray?): ViewModel {
1135+ if (input == null) {
1136+ throw DeserializationError("Cannot deserialize null array")
1137+ }
1138+ val deserializer = BincodeDeserializer(input)
1139+ val value = deserialize(deserializer)
1140+ if (deserializer.get_buffer_offset() < input.size) {
1141+ throw DeserializationError("Some input bytes were not read")
1142+ }
1143+ return value
1144+ }
1145+ }
1146+}