Come gli handler ricevono i dati di input ed emettono i dati di output tramite stream tipizzati.
Le scritture sono asincrone e soggette a controllo di flusso
Ogni scrittura di dati è soggetta a controllo di flusso (un credito per CHUNK, vedi Controllo di flusso), quindi l’API degli handler è asincrona:
OutputStream::write,emit_list_itemedemit_cborsonoasync fn; restano in attesa quando la finestra del ricevente è esaurita. Chiamali con.await.blocking_write/blocking_emit_list_itemservono i contesti non asincroni (thread FFI, closurespawn_blocking).StreamSender(l’emettitore distaccato) mantiene la sua firma sincrona e acquisisce il credito in modo bloccante.log()/progress()restano sincroni e non sono mai soggetti a controllo di flusso; l’avanzamento deve fluire anche quando la finestra dei dati è esaurita (L14).start_unbounded(is_sequence, meta)apre uno stream senza alcuna promessa di lunghezza (L16); il suo STREAM_END omette chunk_count.InputStream::is_unbounded()riporta il flag sul lato che consuma, e gli helpercollect_*rifiutano gli stream illimitati con un errore bloccante, quindi consumali conrecv().finish(progress, message)dichiara lo stato terminale della richiesta, trasportato nei metadati terminali del frame END (L5). NON emettere un LOG finale di avanzamento al 100%, perché i metadati di END sono l’evento di avanzamento finale.
InputStream
Un InputStream rappresenta un singolo stream di input con nome che arriva dall’host. Ogni stream porta un media_urn che identifica il tipo dei dati (ad esempio "media:document;ext=pdf", "media:enc=utf-8") e produce valori CBOR decodificati estratti dai frame CHUNK.
Gli handler non vedono mai frame grezzi, numeri di sequenza, checksum o ID di stream. Il livello di demux del runtime decodifica i payload dei CHUNK, verifica i checksum e consegna valori CBOR puliti attraverso lo stream.
pub struct InputStream {
media_urn: String,
rx: mpsc::UnboundedReceiver<Result<ciborium::Value, StreamError>>,
}
Sorgente: capdag-rs/src/bifaci/cartridge_runtime.rs.
Ricevere i dati
Tre modalità di consumo:
Streaming: elabora i valori uno alla volta:
let mut stream = /* from InputPackage */;
while let Some(item) = stream.recv().await {
let value = item?;
// elabora ogni valore CBOR
}
Accumulo di byte: raccoglie tutto in un buffer di byte:
let bytes: Vec<u8> = stream.collect_bytes().await?;
collect_bytes() estrae i byte interni da ogni chunk Value::Bytes o Value::Text e li concatena. I tipi CBOR non di byte vengono codificati in CBOR prima di essere accodati. Usalo solo quando sai che lo stream è finito; gli stream infiniti si bloccano per sempre.
Valore singolo: si attende esattamente un chunk:
let value: ciborium::Value = stream.collect_value().await?;
Restituisce il primo valore e ignora il resto. Restituisce StreamError::Closed se lo stream è vuoto.
InputPackage
Un InputPackage raggruppa tutti gli stream di input di una singola richiesta. Gli handler lo ottengono chiamando req.take_input() (che può essere chiamato una sola volta; una seconda chiamata restituisce un errore per impedire il doppio consumo).
pub struct InputPackage {
rx: mpsc::UnboundedReceiver<Result<InputStream, StreamError>>,
_demux_handle: Option<JoinHandle<()>>,
}
Il package produce oggetti InputStream man mano che i frame STREAM_START arrivano dal canale. Restituisce None dopo il frame END, segnalando che tutti gli argomenti sono stati consegnati.
Tre modi di consumare un InputPackage:
Raccogliere ogni stream separatamente (il caso più comune):
let input = req.take_input()?;
let streams: Vec<(String, Vec<u8>)> = input.collect_streams().await?;
// streams = [("media:document;ext=pdf", <bytes>), ("media:enc=utf-8", <bytes>)]
I byte di ogni stream vengono accumulati in modo indipendente. Usa gli helper di ricerca degli stream (sotto) per trovare argomenti specifici in base al media URN.
Concatenare tutti gli stream:
let all_bytes: Vec<u8> = input.collect_all_bytes().await?;
Utile quando una cap ha un solo stream di input e vuoi soltanto i byte.
Iterare gli stream uno alla volta:
while let Some(stream_result) = input.recv().await {
let stream = stream_result?;
println!("Got stream: {}", stream.media_urn());
let bytes = stream.collect_bytes().await?;
}
Sorgente: cartridge_runtime.rs (InputPackage).
Helper di ricerca degli stream
Dopo aver raccolto gli stream con collect_streams(), usa queste funzioni libere per trovare argomenti specifici:
| Funzione | Restituisce | Se assente |
|---|---|---|
find_stream(streams, media_urn) |
Option<&[u8]> |
None |
find_stream_str(streams, media_urn) |
Option<String> |
None |
require_stream(streams, media_urn) |
Result<&[u8]> |
StreamError |
require_stream_str(streams, media_urn) |
Result<String> |
StreamError |
La corrispondenza usa MediaUrn::is_equivalent(): entrambi gli URN devono avere esattamente lo stesso insieme di tag (indipendentemente dall’ordine). È una corrispondenza esatta, non una sussunzione né un pattern matching. Entrambe le parti conoscono i media URN degli argomenti dalla definizione della cap, quindi l’equivalenza è il test corretto.
let streams = input.collect_streams().await?;
let pdf_data = require_stream(&streams, "media:document;ext=pdf")?;
let prompt = find_stream_str(&streams, "media:enc=utf-8");
Il parametro media_urn deve essere il media URN completo tratto dalla definizione dell’argomento della cap.
Sorgente: cartridge_runtime.rs (find_stream, require_stream).
Ruoli degli argomenti: l’input principale
Ogni argomento di una cap ha un ruolo, e il contratto che lo assegna è il modello
canonico dei ruoli degli argomenti che ogni front-end (la CLI capdag, i payload dei
requisiti di passo del planner, le UI) presenta:
- Input principale. Un argomento è l’input principale della cap se e solo se dichiara una
sorgente
stdin(CapArg::is_main_input); le regole di validazione RULE3 e RULE15 garantiscono che una cap il cuiin=non èmedia:voidne abbia esattamente uno, e che il suo URN di stdin sia equivalente come tagged URN ain=. L’input principale è il valore passato in pipe su stdin, come lo stdin di un comando Unix. Il suo URN dichiarato può differire dall’URN di stdin (ad esempio un argomentofile-pathil cui contenuto in pipe è unpdf-stream); è l’URN di stdin, non quello dell’argomento, a esserein=. - Identità. Un argomento è identificato dal suo
media_urn, unico all’interno della cap. I binding, i valori a runtime e la diagnostica nominano tutti un argomento con esso. - Nome. Ogni argomento ha un
name, unico all’interno della cap (RULE17): l’etichetta con cui una macchina lo seleziona, e la grafia che la CLI accetta in--arg <name>=. Non identifica nulla, e nulla dell’URN può essere letto da esso. - Tipo accettato. Ciò a cui deve essere conforme un valore legato all’argomento
(
CapArg::accepted_urn()): l’URN della sorgentestdinper l’input principale;acceptsquando la definizione lo dà; altrimenti ilmedia_urnstesso dell’argomento. - Identità dello stream. Il runtime esegue il demux dello stream di input di ogni argomento in base a
CapArg::stream_urn(): l’URN della sorgentestdindell’argomento quando ne dichiara una, altrimenti il suomedia_urn. L’engine rietichetta uno stream inoltrato con quell’URN dopo aver verificato che cosa porta, così un’op cerca i propri argomenti con gli URN dichiarati dalla sua stessa definizione, qualunque cosa abbia prodotto il valore. - Opzioni obbligatorie. Gli argomenti non principali con
required: truee senzadefault_value; un’invocazione deve fornire ciascuno di essi. - Opzioni. I restanti argomenti non principali, con il loro
default_value.
Ogni argomento, principale o no, può essere fornito dall’output di un’altra cap, da una costante, o da un valore a runtime (Notazione delle macchine). “Opzione” descrive come un argomento viene offerto su una riga di comando, non da dove il suo valore possa provenire.
Chi usa il modello: il planner contrassegna l’input principale su ogni argomento di passo
(ArgumentInfo.is_main_input), e la CLI struttura l’aiuto per singola cap e gli errori
di argomento mancante attorno a questa suddivisione in tre parti (vedi il
riferimento della CLI di CapDAG §”Presentazione dell’interfaccia della cap”).
Media di riferimento e risoluzione del trasporto
Alcuni input arrivano per riferimento: il valore sul canale non è il dato, è un piccolo puntatore che il runtime risolve nel dato. Il contratto è:
- L’argomento è dichiarato con un media URN di riferimento (l’URN di slot); la sua
sorgente
stdinporta il media URN del contenuto; l’in=del cap URN è uguale all’URN di stdin.CapArg::is_main_inputcorrisponde già sulla sorgente stdin, quindi un input principale alimentato per riferimento è strutturalmente identico a uno alimentato direttamente. RULE3/RULE15 si applicano invariate. - È il livello di risoluzione del trasporto del runtime, non l’op, a riconoscere
l’URN di riferimento su uno stream in arrivo, consumare il valore di riferimento,
risolverlo e consegnare il contenuto risolto all’op come un normale
InputStreametichettato con l’URN del contenuto. L’op è cieca rispetto al trasporto: non può distinguere un riferimento risolto da un contenuto passato direttamente in pipe, e non deve mai tentare di farlo. - Sulle piattaforme in cui la cartuccia non può raggiungere la risorsa referenziata (sandboxing, permessi del dispositivo), è l’host a risolvere al suo posto e a trasmettere il contenuto sul canale come un normale stream di input. Stessa cap, stesso piano, risolutore diverso: un dettaglio di deployment, mai un dettaglio di protocollo.
Esistono due famiglie di riferimenti:
Percorsi di file (riferimenti limitati)
Famiglia di URN di riferimento: media:file-path (qualsiasi URN che esso accetta). Il
valore di riferimento è una stringa di percorso oppure, per gli argomenti is_sequence,
un elenco di percorsi/glob separati da newline. La risoluzione legge il file o i file e
consegna i byte come stream limitato (scalare) o un elemento per file (sequenza). Un file
mancante, un glob fallito o una risoluzione multi-file in un argomento scalare è un errore
bloccante. Vedi FilePathContext in cartridge_runtime.rs.
Feed live (riferimenti illimitati)
Famiglia di URN di riferimento: qualsiasi URN che porti il tag marcatore live:
media:audio;live;microphone, media:image;live;webcam,
media:live;synthetic (il feed di test deterministico integrato). Il
valore di riferimento è un record selettore (JSON):
| Campo | Significato |
|---|---|
device |
Selettore del dispositivo definito dal provider (id o nome noto). Facoltativo; un provider con esattamente un dispositivo può usare un valore predefinito. |
params |
Parametri di acquisizione definiti dal provider (frequenza di campionamento, risoluzione, …). Facoltativo. |
stop |
Condizioni di arresto: { "duration_ms": N } e/o { "max_items": N }. Facoltativo; l’assenza significa “finché non viene fermato”. |
on_overrun |
"drop-oldest" (predefinito) o "fail". Vedi Controllo di flusso §Overrun. |
La risoluzione apre il dispositivo tramite il dispatch di acquisizione integrato
(capdag::capture::open, un insieme CHIUSO, fissato in fase di compilazione, di backend
selezionati in base alla famiglia di URN di riferimento in codice semplice, esattamente
come i percorsi di file vengono letti da codice semplice; l’acquisizione è trasporto, non
una capacità, e non c’è alcun registro né nulla da registrare) e consegna uno stream di
sequenza illimitato di elementi etichettati con l’URN del contenuto stdin dell’argomento.
Ogni elemento porta metadati per elemento (pts_us, capture_ts_us, seq e marcatori
di lacuna; vedi Streaming §Feed dal vivo). Un URN di riferimento non
registrato, un selettore non analizzabile o un dispositivo che il provider non riesce ad
aprire è un errore bloccante, mai uno stream vuoto silenzioso. L’analisi del selettore è
rigorosa a ogni livello di annidamento: un campo sconosciuto (nel selettore o
dentro stop) viene rifiutato (una condizione di arresto scritta male e ignorata in
silenzio farebbe girare un feed illimitato che il chiamante intendeva limitare). Un valore
vuoto è il selettore con tutti i valori predefiniti; la sua grafia canonica è {} (i
client serializzano i selettori omettendo i valori predefiniti e con le chiavi ordinate,
quindi selezioni uguali sono uguali byte per byte). L’appartenenza alla famiglia è il
predicato incapsulato is_live_feed sui media URN, presente in tutte e cinque le
implementazioni.
I backend: media:live;synthetic, una sorgente di clock deterministica
(parametri del selettore: items, interval_ms, item_bytes, ring, più
l’iniezione di overrun) usata dalla batteria di test condivisa e disponibile come feed di
fixture in ogni build; e i backend hardware (microfono tramite alsa/avfoundation, webcam
tramite v4l2/avfoundation) dietro la feature cargo capture di CapDAG, abilitata dalle
build capaci di acquisizione (la CLI, l’engine, audiocartridge, videocartridge). Un
riferimento al di fuori delle famiglie note, o una famiglia hardware in una build senza la
feature, è un errore bloccante e con nome.
Quale runtime risolve è un fatto di deployment, non di protocollo. Il runtime della cartuccia risolve quando il consumatore è un’op (il caso della macchina lineare: il processo della prima cap che consuma apre il dispositivo). L’HOST risolve quando la cartuccia non può raggiungere il dispositivo (sandboxing) e, per la stessa regola vista dall’altro lato, quando l’host stesso è il consumatore: una sorgente live che alimenta direttamente una regione ForEach viene iterata dall’ENGINE, quindi l’engine apre il feed tramite lo stesso dispatch ed esegue il dispatch di un corpo per ogni elemento consegnato mentre l’acquisizione è in corso. L’engine registra il tap in modo che l’arresto di un’esecuzione lo chiuda (chiusura del tap → il feed termina → i corpi in corso si svuotano → un’esecuzione fermata è un’esecuzione valida, 15.2 §Le esecuzioni si arrestano).
Contropressione e consegna. Il canale di consegna di un feed live verso l’op è limitato. La catena è: il credito sul canale blocca l’output dell’op → l’op smette di consumare input → il canale limitato del feed si riempie → l’alimentatore del provider smette di svuotare l’anello di acquisizione → l’anello si riempie → il bordo di acquisizione applica la politica di overrun. Una perdita può verificarsi SOLO al bordo di acquisizione, solo secondo la politica dichiarata, e sempre conteggiata (§Overrun). Questo è l’analogo live dell’assenza di deadlock di L10: ogni stadio ha un comportamento definito quando è pieno, e nessuno di essi è “bufferizzare per sempre”.
Terminazione. Un feed termina quando scatta una condizione di arresto, quando la
richiesta viene fermata (un frame CloseStream: il runtime chiude i feed live della
richiesta e lascia che la pipeline si svuoti; vedi Esecuzione
§Le esecuzioni si arrestano), o quando la richiesta viene annullata. La fine del feed è una normale
fine di stream di input: l’op la osserva esattamente come osserva la fine di uno stream
di file. Una cap che legge un feed lo dichiara: streaming = true sul suo
input principale (15.2 §Contratti di streaming), ed è questo che permette all’executor di
collegarla in modo live a un produttore illimitato.
Etichettatura dei media della risposta: il contratto di effetto
Il media URN che porta lo STREAM_START di una risposta è derivato dal runtime,
non scelto dall’op. derive_response_media(cap_urn) (Rust; la stessa
funzione esiste in ogni port del runtime) lo calcola a partire dall’effetto dichiarato
della cap sul suo input principale dichiarato; questa è l’etichetta che porta ogni stream
di input alimentato dall’engine:
| Effetto | Etichetta della risposta |
|---|---|
declared (predefinito) |
l’out= dichiarato |
none |
l’in= dichiarato (il tipo passa invariato) |
patch |
l’in= dichiarato con il delta dichiarato applicato |
Si tratta di CapUrn::apply_to_runtime_input_media sull’input dichiarato,
la stessa inferenza che il planner ha usato per raffinare i tipi a valle in fase di
pianificazione, quindi una risposta etichettata dal runtime soddisfa la verifica degli
effetti dell’engine per costruzione. Una cap la cui etichetta non può essere derivata
(URN non analizzabile, effetto non applicabile) fa fallire la richiesta al dispatch; non
esiste un’etichetta di ripiego.
L’engine verifica ogni emissione alla ricezione (ResponseAudit, basato sull’unico
predicato CapUrn::is_conformant_runtime_output): prima che lo STREAM_START di una
risposta venga inoltrato al passo successivo, reso persistente o
raccolto, il suo media URN deve soddisfare il contratto di effetto dichiarato dalla cap
che lo produce. La condizione dipende dall’effetto:
effect=none/effect=patchcalcolano un tipo di output esatto: l’emissione deve essere equivalente per tag all’inferenza. Un’emissione più specifica è comunque una violazione: l’effetto promette che il tipo è interamente determinato dall’input.effect=declaredpromette soltanto l’out=dichiarato: l’emissione deve essere conforme ad esso (conform_to). Più specifica è lecita; più generica rompe ogni raffinamento a valle e fallisce.
Una violazione è un fallimento bloccante attribuito come internal
(ExecutionError::EffectContractViolation), che nomina il cap URN, l’effetto
dichiarato, l’input a runtime e il valore atteso rispetto a quello effettivo, sollevato
al confine del frame, prima che l’inoltro in pipeline rietichetti lo stream
con l’URN dell’argomento dichiarato a valle, così la rietichettatura è un’asserzione
verificata e non una maschera su una cap che mente.
Output
Una cap dichiara uno o più output (nessuno quando out= è media:void). Ciascuno è
identificato dal suo media_urn, ha un name (RULE19), ed è optional oppure
obbligatorio. Uno è l’output principale: quello il cui URN è l’out= del cap URN.
let fit = req.output(); // l'output principale
fit.start(false, None)?;
fit.write(b"the main output").await?;
let residuals = req.output_for("media:fmt=json;list;record;residual")?;
residuals.start(true, None)?;
residuals.emit_list_item(&row, None).await?;
req.output() è lo stream dell’output principale. req.output_for(urn) è lo stream
dell’output che quell’URN identifica (equivalenza dei tag, come nella ricerca degli input); un
URN che la cap non dichiara è RuntimeError::UnknownOutput. Ciascuno è un
OutputStream (sotto) con il proprio STREAM_START, il proprio credito e il proprio STREAM_END.
Lo stream dell’output principale non nomina alcun output: è la risposta della cap. Lo stream
di ogni altro output nomina l’output che è (chiave del frame 21, il media_urn di quell’output)
accanto al media URN di ciò che porta. Che cosa porta un output,
e come l’engine lo verifica:
| Output | Porta | Verifica |
|---|---|---|
| principale | secondo l’effetto del cap URN (sezione successiva) | come descritto lì |
| altro, predefinito | il proprio media_urn |
l’emissione deve essere conforme ad esso (conform_to) |
altro, carries = "input" |
il tipo a runtime dell’input principale | equivalente per tag ad esso |
Quando l’handler restituisce Ok(()):
- un output obbligatorio su cui l’handler non ha mai scritto viene aperto e chiuso dal
runtime come valore vuoto (una sequenza vuota quando è
is_sequence); - un output facoltativo su cui l’handler non ha mai scritto non apre alcuno stream: non è scattato, e i suoi consumatori lo vedono come assente (Notazione delle macchine).
L’engine rifiuta, come ExecutionError::OutputContractViolation, uno stream dell’output principale
da una cap che non dichiara alcun output, uno stream che nomina un output che la cap
non dichiara o che nomina il suo output principale, un secondo stream per uno stesso output, un
output diverso da quello principale che porta un tipo diverso da quello che dichiara, e
una richiesta che termina senza uno stream per un output obbligatorio.
OutputStream
OutputStream è l’interfaccia dell’handler per emettere dati di risposta, avanzamento e messaggi di log. Gestisce automaticamente l’incorniciamento STREAM_START/CHUNK/STREAM_END: gli handler scrivono i dati e lo stream si occupa del resto.
Uno stream contiene il proprio stream_id, il media URN che porta il suo STREAM_START, l’output che
è quando è uno stream di risposta (uno stream di argomento di una chiamata a un peer non ne nomina alcuno), i suoi
contatori di chunk, la sua finestra di controllo di flusso e il suo buffer di accorpamento delle scritture.
Sorgente: cartridge_runtime.rs (OutputStream).
Aprire uno stream
Un handler apre uno stream prima di scriverci, e dice di che tipo è:
req.output().start(false, None)?; // un valore, scritto a pezzi
req.output().start(true, None)?; // una sequenza, un elemento alla volta
start(is_sequence, meta) invia STREAM_START. is_sequence è la modalità di cardinalità dello stream
e deve essere quella dichiarata dall’output; l’engine la verifica. meta sono i metadati dello stream,
inoltrati ai consumatori. start_unbounded(is_sequence, meta) apre uno stream che
non fa alcuna promessa di lunghezza (un feed). Scrivere su uno stream non avviato, avviarne uno
due volte, e scrivere in una modalità diversa da quella con cui è stato avviato sono ciascuno un errore.
Emettere dati
Byte grezzi: suddivisi automaticamente in chunk ai confini di max_chunk:
req.output().write(b"result data here").await?;
Le scritture piccole vengono ACCORPATE: si accumulano in un buffer per stream e partono come un unico CHUNK quando il lotto raggiunge 4096 byte o quando, alla scrittura successiva, il byte bufferizzato più vecchio ha 20 ms (COALESCE_MAX_BYTES / COALESCE_MAX_AGE). I confini dei chunk su uno stream scalare non hanno valore semantico, quindi questo è invisibile ai riceventi, e un emettitore per token produce lotti invece di un frame per token. Un lotto in scadenza (e qualsiasi scrittura più grande della soglia) viene diviso ai confini di max_chunk (predefinito 256 KB); ogni pezzo viene incapsulato come Value::Bytes e inviato come frame CHUNK con checksum, un credito per frame. flush() (e blocking_flush) forzano l’uscita della coda bufferizzata come barriere di latenza esplicite a metà stream; close() la svuota sempre.
Singolo valore CBOR:
req.output().emit_cbor(&ciborium::Value::Text("hello".to_string())).await?;
Gestisce la suddivisione in chunk specifica per tipo: i valori Bytes vengono accorpati esattamente come con write (pura continuazione di uno stream di byte su uno stream scalare); Text viene diviso ai confini di max_chunk (rispettando i confini dei caratteri UTF-8). Gli elementi di Array vengono inviati uno per chunk. Le voci di Map vengono inviate come coppie [key, value]. I tipi scalari vengono inviati come un singolo chunk. Ogni emissione non Bytes è una BARRIERA DI ORDINAMENTO: svuota prima il buffer di accorpamento, così nulla sorpassa i byte bufferizzati. Gli elementi di sequenza (emit_list_item) non vengono mai accorpati; le loro sequenze di chunk delimitano gli elementi.
Elemento di lista (per l’output di liste in streaming):
for item in items {
req.output().emit_list_item(&item, None).await?;
}
Il secondo argomento sono i metadati propri dell’elemento, portati sul suo chunk.
Ogni chiamata codifica il valore in CBOR e invia i byte grezzi come payload dei chunk. Il ricevente concatena i payload dei chunk per produrre una sequenza CBOR RFC 8742: un valore CBOR autodelimitante per ogni chiamata a emit_list_item. Questo differisce da emit_cbor, che incapsula di nuovo ogni pezzo come valore CBOR separato.
Avanzamento e logging:
req.output().progress(0.5, "Processing page 3 of 6");
req.output().log("info", "Found 12 images");
Questi emettono frame LOG che viaggiano accanto ai frame di dati. Vedi Avanzamento e logging.
Chiusura:
req.output().close().await?;
Svuota gli eventuali byte di coda accorpati, poi invia STREAM_END con il conteggio totale (accorpato) dei chunk; svuotare prima di promettere il conteggio è ciò che rende l’accorpamento senza perdite. close è asincrono perché lo svuotamento della coda può attendere il credito; blocking_close() è la controparte per i thread FFI. Idempotente: chiamarlo due volte è sicuro.
Che cosa fa il runtime per l’handler
sequenceDiagram
participant H as Handler
participant OS as OutputStream
participant W as Wire
H->>OS: start(is_sequence, meta)
OS->>W: STREAM_START
H->>OS: write(data) o emit_cbor(value)
OS->>W: CHUNK (dati)
H->>OS: write(more)
OS->>W: CHUNK (altri dati)
Note over H: l'handler restituisce Ok(())
Note over OS: il runtime chiama close()
OS->>W: STREAM_END
Note over OS: il runtime invia
OS->>W: END
- STREAM_START: inviato da
start(), che l’handler chiama. Sullo stream di un output diverso da quello principale nomina quell’output (chiave del frame 21). - STREAM_END: inviato da
close(). Il runtime chiude ogni stream che l’handler ha aperto una volta che l’handler restituisceOk(()), quindi un handler non deve farlo. - Un output che l’handler non ha mai aperto: vedi Output. Uno obbligatorio viene aperto e chiuso dal runtime come valore vuoto; uno facoltativo non invia nulla.
- END: inviato dal runtime dopo che l’handler è terminato.
- In caso di errore: il runtime invia un frame ERR al posto di END, e non chiude nulla: ciò che l’handler aveva scritto resta com’era, e la richiesta è fallita.
Una cartuccia eseguita dalla propria riga di comando
Una cartuccia invocata direttamente (<cartridge> <alias> ...) ha un solo standard output, e questo
porta l’output principale della cap: lo stream che non nomina alcun output. Un altro output che
scatta non viene stampato: la cartuccia lo dice sullo standard error, nominando l’output. È una macchina
a instradare gli altri output di una cap.
Convenzioni di codifica CBOR
Argomenti e risultati fluiscono come valori CBOR nei payload dei CHUNK. Le convenzioni di codifica:
- Argomenti testuali (prompt, percorsi di file, stringhe di configurazione): inviati come
Value::Bytescontenenti testo codificato in UTF-8, non tra virgolette JSON. Una stringa di prompt"hello world"diventa i byte grezzihello world, non"hello world". - Dati binari (immagini, PDF, pesi di modelli): inviati come
Value::Bytescontenenti i dati binari grezzi. - Strutture JSON (specifiche di modelli, oggetti di configurazione): inviate come
Value::Bytescontenenti la stringa JSON in UTF-8. - Argomenti numerici: inviati come
Value::Bytescontenenti la rappresentazione testuale in UTF-8.
Questo corrisponde alla convenzione json_value_to_bytes usata nel modulo del planner (vedi Planner): le stringhe diventano byte UTF-8 grezzi (senza virgolette JSON), tutto il resto diventa byte codificati in JSON.
Equivalente Swift
OutputStream e InputStream in Swift, in capdag-objc/Sources/Bifaci/CartridgeRuntime.swift, seguono la stessa semantica con tipi adatti alla piattaforma:
Datainvece diVec<u8>, valoriCBORinvece diciborium::Value.- I metodi lanciano eccezioni invece di restituire
Result. start(isSequence:meta:),write(_:),emitCbor(_:),emitListItem(_:meta:),close(), come in Rust;blockingClose()per un thread che non può attendere.log(level:attributionClass:message:argUrn:)eprogress(_:message:)per la diagnostica;finish(progress:message:)dichiara lo stato terminale della richiesta.runWithKeepalive()usa unTaskin background invece ditokio::task::spawn_blocking.CborRequest.output()è l’output principale;output(for:)trova un altro output dichiarato tramite il suo media URN, come faRequest::output_for.
Il ciclo di vita dello stream è identico: STREAM_START quando viene chiamato start, STREAM_END alla chiusura, END al completamento dell’handler; quando l’handler restituisce, un output obbligatorio su cui non ha mai scritto viene inviato vuoto e uno facoltativo non viene inviato.