// Host-supplied bridge between the editor and the hub. // // The editor package can't talk to the hub directly — that's // the host's job (Studio uses the gRPC SDK, a CLI host could // shell out to `fai run`, a test harness can stub everything // in-memory). The bridge keeps the editor reusable across // any host that can implement the two methods below. // // Lifecycle expected by the editor: // // 1. Subscribe to `events()` BEFORE calling `runFlow()`. // The hub starts emitting `step.started` events the // instant the flow begins; the host's subscription must // already be live so the first one doesn't get lost. // 2. Call `runFlow()` with the flow name + collected inputs. // The returned Future resolves with the final outputs // (or throws on failure). // 3. Unsubscribe from `events()` when the run resolves. import 'dart:typed_data'; /// A file chosen by the host's native picker for a flow file input. class PickedFileData { final String fileName; final Uint8List bytes; const PickedFileData({required this.fileName, required this.bytes}); } /// Host-side native file picker. Returns null when the operator /// cancels. When a host does not supply one, the Run tab falls back /// to its manual path-entry dialog. typedef PickFileCallback = Future Function(); abstract class FlowRunDriver { /// Trigger a run of [flowName] with the supplied inputs. /// Text + file inputs come in separate maps so the host /// can wire them to the right payload shape. Returns the /// flow's final outputs map keyed by output name. Future> runFlow({ required String flowName, required Map textInputs, required Map fileInputs, required Map fileMimes, }); /// Live event stream scoped to whatever the host can /// observe — typically the hub's `StreamEvents` RPC /// filtered to step.* event types for the running flow. /// May arrive late (subscribe BEFORE calling runFlow!) and /// may emit events for unrelated runs; the editor filters /// by [FlowRunEvent.flowName] before applying. Stream events(); /// Look up an installed module's manifest by capability /// reference. The editor uses this to render per-field /// input/output ports with their declared names + types + /// tooltips, instead of collapsing every downstream reference /// into a single output anchor. /// /// Default returns `null` so legacy hosts that didn't update /// their FlowRunDriver implementation keep compiling — the /// editor falls back to the YAML-reference-derived ports. /// A real implementation hits the hub's ModuleInfo RPC and /// maps the result to [ModuleSpec]. Future moduleInfo(String capability) async => null; /// Resolve the pending approval id for `(flowName, stepId)` /// when the flow is paused on a `system.approval@^0` step. /// Default returns `null` so legacy hosts that didn't update /// keep compiling — the editor's Run tab then falls back to /// directing the operator at the standalone Approvals page. Future pendingApprovalIdForStep({ required String flowName, required String stepId, }) async => null; /// Approve the named approval. `reviewer` is the audit-log /// identity the host wants attributed (typically OS user + /// '@studio'). Throws on hub-side failure; UI surfaces it. Future approveApproval({ required String approvalId, required String reviewer, }) async { throw UnsupportedError( 'FlowRunDriver host did not implement approveApproval', ); } /// Reject the named approval with a free-form reason. Future rejectApproval({ required String approvalId, required String reviewer, required String reason, }) async { throw UnsupportedError( 'FlowRunDriver host did not implement rejectApproval', ); } } /// Declared inputs + outputs of one installed module, as seen /// by the editor. The shape mirrors `ModuleInfoResponse.inputs` /// / `outputs` over gRPC but without proto dependencies. class ModuleSpec { /// Capability identifier the host resolved (e.g. `text.summarize`). final String capability; /// Declared inputs, in stable alphabetical order. The editor /// draws one input port per entry. final List inputs; /// Declared outputs, in stable alphabetical order. The editor /// draws one output port per entry — this is what makes /// `summarize.response`, `summarize.model_endpoint`, /// `summarize.model_name`, `summarize.model_digest` appear as /// four distinct anchors instead of one. final List outputs; const ModuleSpec({ required this.capability, required this.inputs, required this.outputs, }); } /// One declared input or output field. Carries the type /// descriptor (`text` / `json` / `bytes` / `file`) and a locale /// → description map for tooltip rendering. class ModuleField { /// Field name, e.g. `prompt` or `model_endpoint`. final String name; /// Type descriptor. final String type; /// IETF locale tag → description string. Empty when the /// manifest used the shorthand form (no description). final Map description; const ModuleField({ required this.name, required this.type, this.description = const {}, }); /// Description in [locale] with English fallback. Returns /// null when the manifest carries no description for either. String? descriptionFor(String locale) { final exact = description[locale]; if (exact != null && exact.isNotEmpty) return exact; final en = description['en']; if (en != null && en.isNotEmpty) return en; return null; } } /// What the editor needs out of a single event tick. Maps /// 1:1 to the hub's `step.*` events but with the typing /// projected into the editor's vocabulary so we don't carry /// the SDK's proto types across the package boundary. sealed class FlowRunEvent { final String flowName; final String stepId; const FlowRunEvent({required this.flowName, required this.stepId}); } class StepStarted extends FlowRunEvent { const StepStarted({required super.flowName, required super.stepId}); } class StepCompleted extends FlowRunEvent { final int durationMs; const StepCompleted({ required super.flowName, required super.stepId, required this.durationMs, }); } class StepFailed extends FlowRunEvent { final String error; const StepFailed({ required super.flowName, required super.stepId, required this.error, }); } class StepAwaitingApproval extends FlowRunEvent { const StepAwaitingApproval({required super.flowName, required super.stepId}); } /// One output value, after the run completed. The editor /// renders text inline, JSON pretty-printed, bytes as a /// download badge. sealed class FlowOutputValue { const FlowOutputValue(); } class FlowOutputText extends FlowOutputValue { final String value; const FlowOutputText(this.value); } class FlowOutputJson extends FlowOutputValue { final Object? value; const FlowOutputJson(this.value); } class FlowOutputBytes extends FlowOutputValue { final Uint8List value; final String mimeType; const FlowOutputBytes(this.value, this.mimeType); }