Errors and streaming
Report expected application errors and return NDJSON streams from command leaves.
Errors and streaming
Throw LeafError when an expected application error should be reported to crtr. Its code must be lowercase snake case and cannot be internal, unknown_path, command_collision, or cli_protocol_error. status defaults to 400 and must be an integer from 400 through 599. Use field, next, and received when they make the fix clearer.
import { LeafError } from '@north-light/crouter-plugin';
function missingApp(appId: string): never {
throw new LeafError({
code: 'app_not_found',
message: `No application ${appId}.`,
status: 404,
field: 'app-id',
next: 'Run acme app create first.',
});
}
The handler turns that error into a non-2xx response with exactly { "error": { "code", "message", "field?", "next?", "received?", "manifest_stale?" } }. A 400, 401, 403, or 422 response is a crtr usage error; 404 is not found; 409 is ambiguous; other statuses are general errors. cli_protocol_error is reserved for crtr itself.
For a non-streaming leaf, an unexpected exception calls onError(error, commandPath) when supplied to createFetchHandler, then returns status 500 with code handler_failed. A streaming handler that throws before it yields its first frame follows the same path. After the first frame starts the 200 application/x-ndjson response, a later exception calls onError and interrupts the response; it cannot send a handler_failed envelope or append an error frame. LeafError({ manifestStale: true }) adds manifest_stale: true to the error response. Crtr refetches the archive and replays the original command once, so use it only when the refreshed manifest differs and replaying the command is safe.
A streaming handler declared with defineStreamingLeaf returns an async iterable. Each yielded object becomes JSON.stringify(frame) + '\n' in an application/x-ndjson response. Crtr relays each nonblank line without parsing it, so streaming frames have no package-defined schema and there is no terminal frame to yield. The declared output fields describe frames in command help but are not validated during streaming.
async function* lines(): AsyncIterable<object> {
yield { line: 'Starting deployment.' };
yield { line: 'Deployment complete.' };
}
Pass lines as a defineStreamingLeaf handler when the yielded objects match the fields you want agents to see.