Files
orca/src/shared/terminal-stream-protocol.ts
T
NeilandOrca 879aad7dd6 oom(foundation): bound shared readers/limits + add BoundedMap primitive (#10299)
* oom(01): A1-shared-readers — reintroduce #10179 subset

Files: 18 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(02): A2-shared-image-media — reintroduce #10179 subset

Files: 7 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(03): A3-shared-fs-listing — reintroduce #10179 subset

Files: 21 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(04): A4-shared-remote-relay — reintroduce #10179 subset

Files: 8 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(05): A5-shared-misc — reintroduce #10179 subset

Files: 28 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(06): B-shared-wiring — reintroduce #10179 subset

Files: 81 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-24 21:36:57 -07:00

122 lines
3.8 KiB
TypeScript

import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit'
const TERMINAL_STREAM_KIND = 0x74
const TERMINAL_STREAM_VERSION = 1
const HEADER_BYTES = 16
export const TERMINAL_STREAM_JSON_MAX_BYTES = 8 * 1024 * 1024
export const TERMINAL_STREAM_JSON_STRUCTURE_LIMITS = {
structuralTokens: 256 * 1024,
nestingDepth: 32
} as const
export enum TerminalStreamOpcode {
Output = 1,
SnapshotStart = 2,
SnapshotChunk = 3,
SnapshotEnd = 4,
Resized = 5,
Error = 6,
Input = 7,
Resize = 8,
Subscribe = 9,
Unsubscribe = 10,
SnapshotRequest = 11,
Metadata = 12,
// Why 13: Metadata=12 shipped to mobile clients in v1.4.120; Ack (branch-only
// remote-multiplex flow control) renumbers to stay wire-compatible.
Ack = 13,
// Why 14: Ack already occupies 13 on current clients; older runtimes ignore
// this opcode and still receive the compatibility Resize frame behind it.
ClaimViewport = 14,
OutputSpan = 15
}
export type TerminalStreamFrame = {
opcode: TerminalStreamOpcode
streamId: number
seq: number
payload: Uint8Array
}
export function encodeTerminalStreamFrame(frame: TerminalStreamFrame): Uint8Array {
const out = new Uint8Array(HEADER_BYTES + frame.payload.length)
const view = new DataView(out.buffer, out.byteOffset, out.byteLength)
view.setUint8(0, TERMINAL_STREAM_KIND)
view.setUint8(1, TERMINAL_STREAM_VERSION)
view.setUint8(2, frame.opcode)
view.setUint8(3, 0)
view.setUint32(4, frame.streamId, true)
const seq = Math.max(0, Math.floor(frame.seq))
view.setUint32(8, Math.floor(seq / 0x100000000), true)
view.setUint32(12, seq >>> 0, true)
out.set(frame.payload, HEADER_BYTES)
return out
}
export function decodeTerminalStreamFrame(bytes: Uint8Array): TerminalStreamFrame | null {
if (bytes.length < HEADER_BYTES) {
return null
}
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
if (view.getUint8(0) !== TERMINAL_STREAM_KIND || view.getUint8(1) !== TERMINAL_STREAM_VERSION) {
return null
}
const opcode = view.getUint8(2)
if (!isTerminalStreamOpcode(opcode)) {
return null
}
const high = view.getUint32(8, true)
const low = view.getUint32(12, true)
return {
opcode,
streamId: view.getUint32(4, true),
seq: high * 0x100000000 + low,
payload: bytes.slice(HEADER_BYTES)
}
}
export function encodeTerminalStreamJson(value: unknown): Uint8Array {
return new TextEncoder().encode(JSON.stringify(value))
}
export function decodeTerminalStreamJson<T>(payload: Uint8Array): T | null {
if (payload.byteLength > TERMINAL_STREAM_JSON_MAX_BYTES) {
return null
}
try {
const content = new TextDecoder().decode(payload)
assertJsonTextStructureWithinLimits(content, TERMINAL_STREAM_JSON_STRUCTURE_LIMITS)
return JSON.parse(content) as T
} catch {
return null
}
}
export function encodeTerminalStreamText(value: string): Uint8Array {
return new TextEncoder().encode(value)
}
export function decodeTerminalStreamText(payload: Uint8Array): string {
return new TextDecoder().decode(payload)
}
function isTerminalStreamOpcode(value: number): value is TerminalStreamOpcode {
return (
value === TerminalStreamOpcode.Output ||
value === TerminalStreamOpcode.SnapshotStart ||
value === TerminalStreamOpcode.SnapshotChunk ||
value === TerminalStreamOpcode.SnapshotEnd ||
value === TerminalStreamOpcode.Resized ||
value === TerminalStreamOpcode.Error ||
value === TerminalStreamOpcode.Input ||
value === TerminalStreamOpcode.Resize ||
value === TerminalStreamOpcode.Subscribe ||
value === TerminalStreamOpcode.Unsubscribe ||
value === TerminalStreamOpcode.SnapshotRequest ||
value === TerminalStreamOpcode.Metadata ||
value === TerminalStreamOpcode.Ack ||
value === TerminalStreamOpcode.ClaimViewport ||
value === TerminalStreamOpcode.OutputSpan
)
}