From 1d3d33d55a8842a8de27f7b6497c69bd7103476f Mon Sep 17 00:00:00 2001 From: flemming-it Date: Tue, 8 Sep 2026 13:38:35 +0200 Subject: [PATCH 1/3] feat: installModuleStreaming for live install progress Wraps the new HubAdmin/InstallModuleStream: yields one update per phase and, during the download, as bytes arrive; completes on success and surfaces the hub's failure text as a stream error, so callers handle failures exactly as with the unary installModule. Generated stubs regenerated from the protos (additive only). Signed-off-by: flemming-it --- lib/src/generated/chain/v1/hub.pb.dart | 240 +++++++++++++++++++++ lib/src/generated/chain/v1/hub.pbgrpc.dart | 38 ++++ lib/src/generated/chain/v1/hub.pbjson.dart | 67 ++++++ lib/src/hub_client.dart | 36 ++++ 4 files changed, 381 insertions(+) diff --git a/lib/src/generated/chain/v1/hub.pb.dart b/lib/src/generated/chain/v1/hub.pb.dart index a5b8d0f..0ff9c0c 100644 --- a/lib/src/generated/chain/v1/hub.pb.dart +++ b/lib/src/generated/chain/v1/hub.pb.dart @@ -3576,6 +3576,246 @@ class InstallModuleResponse extends $pb.GeneratedMessage { void clearCapabilityCount() => $_clearField(3); } +enum InstallProgressEvent_Event { update, finished, failed, notSet } + +/// One message in an InstallModuleStream. +class InstallProgressEvent extends $pb.GeneratedMessage { + factory InstallProgressEvent({ + InstallProgressUpdate? update, + InstallModuleResponse? finished, + $core.String? failed, + }) { + final result = create(); + if (update != null) result.update = update; + if (finished != null) result.finished = finished; + if (failed != null) result.failed = failed; + return result; + } + + InstallProgressEvent._(); + + factory InstallProgressEvent.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory InstallProgressEvent.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, InstallProgressEvent_Event> + _InstallProgressEvent_EventByTag = { + 1: InstallProgressEvent_Event.update, + 2: InstallProgressEvent_Event.finished, + 3: InstallProgressEvent_Event.failed, + 0: InstallProgressEvent_Event.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'InstallProgressEvent', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3]) + ..aOM(1, _omitFieldNames ? '' : 'update', + subBuilder: InstallProgressUpdate.create) + ..aOM(2, _omitFieldNames ? '' : 'finished', + subBuilder: InstallModuleResponse.create) + ..aOS(3, _omitFieldNames ? '' : 'failed') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InstallProgressEvent clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InstallProgressEvent copyWith(void Function(InstallProgressEvent) updates) => + super.copyWith((message) => updates(message as InstallProgressEvent)) + as InstallProgressEvent; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static InstallProgressEvent create() => InstallProgressEvent._(); + @$core.override + InstallProgressEvent createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static InstallProgressEvent getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static InstallProgressEvent? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + InstallProgressEvent_Event whichEvent() => + _InstallProgressEvent_EventByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + void clearEvent() => $_clearField($_whichOneof(0)); + + /// Progress moved, or a phase started or finished. + @$pb.TagNumber(1) + InstallProgressUpdate get update => $_getN(0); + @$pb.TagNumber(1) + set update(InstallProgressUpdate value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasUpdate() => $_has(0); + @$pb.TagNumber(1) + void clearUpdate() => $_clearField(1); + @$pb.TagNumber(1) + InstallProgressUpdate ensureUpdate() => $_ensure(0); + + /// The install succeeded; always the final message. + @$pb.TagNumber(2) + InstallModuleResponse get finished => $_getN(1); + @$pb.TagNumber(2) + set finished(InstallModuleResponse value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasFinished() => $_has(1); + @$pb.TagNumber(2) + void clearFinished() => $_clearField(2); + @$pb.TagNumber(2) + InstallModuleResponse ensureFinished() => $_ensure(1); + + /// The install failed; always the final message. Carries the same + /// text the unary call would have returned as its status message. + @$pb.TagNumber(3) + $core.String get failed => $_getSZ(2); + @$pb.TagNumber(3) + set failed($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasFailed() => $_has(2); + @$pb.TagNumber(3) + void clearFailed() => $_clearField(3); +} + +/// A single progress observation. +class InstallProgressUpdate extends $pb.GeneratedMessage { + factory InstallProgressUpdate({ + $core.String? phase, + $core.String? stage, + $core.double? percent, + $fixnum.Int64? bytesReceived, + $fixnum.Int64? bytesTotal, + $core.bool? phaseDone, + }) { + final result = create(); + if (phase != null) result.phase = phase; + if (stage != null) result.stage = stage; + if (percent != null) result.percent = percent; + if (bytesReceived != null) result.bytesReceived = bytesReceived; + if (bytesTotal != null) result.bytesTotal = bytesTotal; + if (phaseDone != null) result.phaseDone = phaseDone; + return result; + } + + InstallProgressUpdate._(); + + factory InstallProgressUpdate.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory InstallProgressUpdate.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'InstallProgressUpdate', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'phase') + ..aOS(2, _omitFieldNames ? '' : 'stage') + ..aD(3, _omitFieldNames ? '' : 'percent', fieldType: $pb.PbFieldType.OF) + ..a<$fixnum.Int64>( + 4, _omitFieldNames ? '' : 'bytesReceived', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$fixnum.Int64>( + 5, _omitFieldNames ? '' : 'bytesTotal', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOB(6, _omitFieldNames ? '' : 'phaseDone') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InstallProgressUpdate clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InstallProgressUpdate copyWith( + void Function(InstallProgressUpdate) updates) => + super.copyWith((message) => updates(message as InstallProgressUpdate)) + as InstallProgressUpdate; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static InstallProgressUpdate create() => InstallProgressUpdate._(); + @$core.override + InstallProgressUpdate createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static InstallProgressUpdate getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static InstallProgressUpdate? _defaultInstance; + + /// Stable phase identifier: "resolve", "download" or "install". + /// Clients map it to their own wording; it is not display text. + @$pb.TagNumber(1) + $core.String get phase => $_getSZ(0); + @$pb.TagNumber(1) + set phase($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasPhase() => $_has(0); + @$pb.TagNumber(1) + void clearPhase() => $_clearField(1); + + /// English fallback label for the phase ("Downloading"). Clients with + /// their own translations key off `phase` and ignore this. + @$pb.TagNumber(2) + $core.String get stage => $_getSZ(1); + @$pb.TagNumber(2) + set stage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasStage() => $_has(1); + @$pb.TagNumber(2) + void clearStage() => $_clearField(2); + + /// Overall progress across all phases, 0..100, monotonic. Absent + /// means the extent is not knowable yet — show an indeterminate + /// indicator, never a bar parked at zero. + @$pb.TagNumber(3) + $core.double get percent => $_getN(2); + @$pb.TagNumber(3) + set percent($core.double value) => $_setFloat(2, value); + @$pb.TagNumber(3) + $core.bool hasPercent() => $_has(2); + @$pb.TagNumber(3) + void clearPercent() => $_clearField(3); + + /// Bytes received so far during the download phase. + @$pb.TagNumber(4) + $fixnum.Int64 get bytesReceived => $_getI64(3); + @$pb.TagNumber(4) + set bytesReceived($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasBytesReceived() => $_has(3); + @$pb.TagNumber(4) + void clearBytesReceived() => $_clearField(4); + + /// Total bytes the server announced. Zero means the server sent no + /// content-length, so only `bytes_received` is meaningful. + @$pb.TagNumber(5) + $fixnum.Int64 get bytesTotal => $_getI64(4); + @$pb.TagNumber(5) + set bytesTotal($fixnum.Int64 value) => $_setInt64(4, value); + @$pb.TagNumber(5) + $core.bool hasBytesTotal() => $_has(4); + @$pb.TagNumber(5) + void clearBytesTotal() => $_clearField(5); + + /// The named phase just finished. + @$pb.TagNumber(6) + $core.bool get phaseDone => $_getBF(5); + @$pb.TagNumber(6) + set phaseDone($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasPhaseDone() => $_has(5); + @$pb.TagNumber(6) + void clearPhaseDone() => $_clearField(6); +} + class UninstallModuleRequest extends $pb.GeneratedMessage { factory UninstallModuleRequest({ $core.String? name, diff --git a/lib/src/generated/chain/v1/hub.pbgrpc.dart b/lib/src/generated/chain/v1/hub.pbgrpc.dart index 5a4754d..67faea6 100644 --- a/lib/src/generated/chain/v1/hub.pbgrpc.dart +++ b/lib/src/generated/chain/v1/hub.pbgrpc.dart @@ -442,6 +442,21 @@ class HubAdminClient extends $grpc.Client { return $createUnaryCall(_$installModule, request, options: options); } + /// Same install, with live progress. Emits an update per phase and, + /// during the download, as bytes arrive; the last message carries + /// either the finished response or the error. Clients that only need + /// the outcome keep using the unary InstallModule above — this exists + /// so a waiting operator can see that a slow download is progressing + /// rather than hung. + $grpc.ResponseStream<$0.InstallProgressEvent> installModuleStream( + $0.InstallModuleRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$installModuleStream, $async.Stream.fromIterable([request]), + options: options); + } + /// Remove an installed module by name. Wipes the module /// directory and re-scans the registry; writes a /// `module.uninstalled` audit event. Surfaced by the Studio @@ -956,6 +971,11 @@ class HubAdminClient extends $grpc.Client { '/chain.v1.HubAdmin/InstallModule', ($0.InstallModuleRequest value) => value.writeToBuffer(), $0.InstallModuleResponse.fromBuffer); + static final _$installModuleStream = + $grpc.ClientMethod<$0.InstallModuleRequest, $0.InstallProgressEvent>( + '/chain.v1.HubAdmin/InstallModuleStream', + ($0.InstallModuleRequest value) => value.writeToBuffer(), + $0.InstallProgressEvent.fromBuffer); static final _$uninstallModule = $grpc.ClientMethod<$0.UninstallModuleRequest, $0.UninstallModuleResponse>( '/chain.v1.HubAdmin/UninstallModule', @@ -1285,6 +1305,15 @@ abstract class HubAdminServiceBase extends $grpc.Service { ($core.List<$core.int> value) => $0.InstallModuleRequest.fromBuffer(value), ($0.InstallModuleResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.InstallModuleRequest, $0.InstallProgressEvent>( + 'InstallModuleStream', + installModuleStream_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.InstallModuleRequest.fromBuffer(value), + ($0.InstallProgressEvent value) => value.writeToBuffer())); $addMethod($grpc.ServiceMethod<$0.UninstallModuleRequest, $0.UninstallModuleResponse>( 'UninstallModule', @@ -1737,6 +1766,15 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.InstallModuleResponse> installModule( $grpc.ServiceCall call, $0.InstallModuleRequest request); + $async.Stream<$0.InstallProgressEvent> installModuleStream_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.InstallModuleRequest> $request) async* { + yield* installModuleStream($call, await $request); + } + + $async.Stream<$0.InstallProgressEvent> installModuleStream( + $grpc.ServiceCall call, $0.InstallModuleRequest request); + $async.Future<$0.UninstallModuleResponse> uninstallModule_Pre( $grpc.ServiceCall $call, $async.Future<$0.UninstallModuleRequest> $request) async { diff --git a/lib/src/generated/chain/v1/hub.pbjson.dart b/lib/src/generated/chain/v1/hub.pbjson.dart index d1029df..1e40f80 100644 --- a/lib/src/generated/chain/v1/hub.pbjson.dart +++ b/lib/src/generated/chain/v1/hub.pbjson.dart @@ -998,6 +998,73 @@ final $typed_data.Uint8List installModuleResponseDescriptor = $convert.base64Dec 'IgASgJUgd2ZXJzaW9uEikKEGNhcGFiaWxpdHlfY291bnQYAyABKAVSD2NhcGFiaWxpdHlDb3Vu' 'dA=='); +@$core.Deprecated('Use installProgressEventDescriptor instead') +const InstallProgressEvent$json = { + '1': 'InstallProgressEvent', + '2': [ + { + '1': 'update', + '3': 1, + '4': 1, + '5': 11, + '6': '.chain.v1.InstallProgressUpdate', + '9': 0, + '10': 'update' + }, + { + '1': 'finished', + '3': 2, + '4': 1, + '5': 11, + '6': '.chain.v1.InstallModuleResponse', + '9': 0, + '10': 'finished' + }, + {'1': 'failed', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'failed'}, + ], + '8': [ + {'1': 'event'}, + ], +}; + +/// Descriptor for `InstallProgressEvent`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List installProgressEventDescriptor = $convert.base64Decode( + 'ChRJbnN0YWxsUHJvZ3Jlc3NFdmVudBI5CgZ1cGRhdGUYASABKAsyHy5jaGFpbi52MS5JbnN0YW' + 'xsUHJvZ3Jlc3NVcGRhdGVIAFIGdXBkYXRlEj0KCGZpbmlzaGVkGAIgASgLMh8uY2hhaW4udjEu' + 'SW5zdGFsbE1vZHVsZVJlc3BvbnNlSABSCGZpbmlzaGVkEhgKBmZhaWxlZBgDIAEoCUgAUgZmYW' + 'lsZWRCBwoFZXZlbnQ='); + +@$core.Deprecated('Use installProgressUpdateDescriptor instead') +const InstallProgressUpdate$json = { + '1': 'InstallProgressUpdate', + '2': [ + {'1': 'phase', '3': 1, '4': 1, '5': 9, '10': 'phase'}, + {'1': 'stage', '3': 2, '4': 1, '5': 9, '10': 'stage'}, + { + '1': 'percent', + '3': 3, + '4': 1, + '5': 2, + '9': 0, + '10': 'percent', + '17': true + }, + {'1': 'bytes_received', '3': 4, '4': 1, '5': 4, '10': 'bytesReceived'}, + {'1': 'bytes_total', '3': 5, '4': 1, '5': 4, '10': 'bytesTotal'}, + {'1': 'phase_done', '3': 6, '4': 1, '5': 8, '10': 'phaseDone'}, + ], + '8': [ + {'1': '_percent'}, + ], +}; + +/// Descriptor for `InstallProgressUpdate`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List installProgressUpdateDescriptor = $convert.base64Decode( + 'ChVJbnN0YWxsUHJvZ3Jlc3NVcGRhdGUSFAoFcGhhc2UYASABKAlSBXBoYXNlEhQKBXN0YWdlGA' + 'IgASgJUgVzdGFnZRIdCgdwZXJjZW50GAMgASgCSABSB3BlcmNlbnSIAQESJQoOYnl0ZXNfcmVj' + 'ZWl2ZWQYBCABKARSDWJ5dGVzUmVjZWl2ZWQSHwoLYnl0ZXNfdG90YWwYBSABKARSCmJ5dGVzVG' + '90YWwSHQoKcGhhc2VfZG9uZRgGIAEoCFIJcGhhc2VEb25lQgoKCF9wZXJjZW50'); + @$core.Deprecated('Use uninstallModuleRequestDescriptor instead') const UninstallModuleRequest$json = { '1': 'UninstallModuleRequest', diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 2b8b53d..63af57b 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -110,6 +110,7 @@ typedef ProjectInfo = pb.ProjectInfo; typedef StoreEntry = pb.StoreEntry; typedef StoreSearchResponse = pb.StoreSearchResponse; typedef InstallModuleResponse = pb.InstallModuleResponse; +typedef InstallProgressUpdate = pb.InstallProgressUpdate; typedef Payload = pb_common.Payload; typedef SubmitResponse = pb.SubmitResponse; // Live-stream (T2) + detached-invocation (T3) wire types, re-exported @@ -831,6 +832,41 @@ class HubClient { )); } + /// Install a module while watching it happen. + /// + /// Emits an update per phase and, during the download, as bytes + /// arrive. The stream completes when the install succeeds, and + /// carries the hub's failure text as a stream error when it does + /// not, so callers handle failures the same way as with + /// [installModule]. Use [onFinished] to receive the installed + /// module's name and version. + /// + /// A caller that only needs the outcome should keep using the + /// unary [installModule]; this exists so a waiting operator can + /// see that a slow download is progressing rather than hung. + Stream installModuleStreaming({ + required String source, + String expectedSha256 = '', + String version = '', + void Function(InstallModuleResponse result)? onFinished, + }) async* { + final stream = _admin.installModuleStream(pb.InstallModuleRequest( + source: source, + expectedSha256: expectedSha256, + version: version, + )); + await for (final event in stream) { + if (event.hasUpdate()) { + yield event.update; + } else if (event.hasFinished()) { + onFinished?.call(event.finished); + return; + } else if (event.hasFailed()) { + throw Exception(event.failed); + } + } + } + /// All saved flows known to the hub. Each entry carries the /// flow name, on-disk path and byte size. Future> listFlows() async { From 4c7db58bce440d0a0ba64982eb4d680a1db3f584 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Tue, 8 Sep 2026 17:51:28 +0200 Subject: [PATCH 2/3] feat: pullSystemAiModelStreaming for live model-download progress Wraps HubAdmin/PullSystemAiModelStream: yields the backend's own status and byte counts per layer, and hands the terminal response to onFinished, whose errorKind stays the failure signal. Stubs regenerated from the protos (additive only). Signed-off-by: flemming-it --- lib/src/generated/chain/v1/hub.pb.dart | 180 +++++++++++++++++++++ lib/src/generated/chain/v1/hub.pbgrpc.dart | 36 +++++ lib/src/generated/chain/v1/hub.pbjson.dart | 49 ++++++ lib/src/hub_client.dart | 29 ++++ 4 files changed, 294 insertions(+) diff --git a/lib/src/generated/chain/v1/hub.pb.dart b/lib/src/generated/chain/v1/hub.pb.dart index 0ff9c0c..dad3c65 100644 --- a/lib/src/generated/chain/v1/hub.pb.dart +++ b/lib/src/generated/chain/v1/hub.pb.dart @@ -7230,6 +7230,186 @@ class PullSystemAiModelResponse extends $pb.GeneratedMessage { void clearElapsedMs() => $_clearField(3); } +enum PullProgressEvent_Event { update, finished, notSet } + +/// One message in a PullSystemAiModelStream. +class PullProgressEvent extends $pb.GeneratedMessage { + factory PullProgressEvent({ + PullProgressUpdate? update, + PullSystemAiModelResponse? finished, + }) { + final result = create(); + if (update != null) result.update = update; + if (finished != null) result.finished = finished; + return result; + } + + PullProgressEvent._(); + + factory PullProgressEvent.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PullProgressEvent.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, PullProgressEvent_Event> + _PullProgressEvent_EventByTag = { + 1: PullProgressEvent_Event.update, + 2: PullProgressEvent_Event.finished, + 0: PullProgressEvent_Event.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PullProgressEvent', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'update', + subBuilder: PullProgressUpdate.create) + ..aOM(2, _omitFieldNames ? '' : 'finished', + subBuilder: PullSystemAiModelResponse.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullProgressEvent clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullProgressEvent copyWith(void Function(PullProgressEvent) updates) => + super.copyWith((message) => updates(message as PullProgressEvent)) + as PullProgressEvent; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PullProgressEvent create() => PullProgressEvent._(); + @$core.override + PullProgressEvent createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PullProgressEvent getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PullProgressEvent? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + PullProgressEvent_Event whichEvent() => + _PullProgressEvent_EventByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearEvent() => $_clearField($_whichOneof(0)); + + /// The pull moved on. + @$pb.TagNumber(1) + PullProgressUpdate get update => $_getN(0); + @$pb.TagNumber(1) + set update(PullProgressUpdate value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasUpdate() => $_has(0); + @$pb.TagNumber(1) + void clearUpdate() => $_clearField(1); + @$pb.TagNumber(1) + PullProgressUpdate ensureUpdate() => $_ensure(0); + + /// Terminal message, success or failure (see its error_kind). + @$pb.TagNumber(2) + PullSystemAiModelResponse get finished => $_getN(1); + @$pb.TagNumber(2) + set finished(PullSystemAiModelResponse value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasFinished() => $_has(1); + @$pb.TagNumber(2) + void clearFinished() => $_clearField(2); + @$pb.TagNumber(2) + PullSystemAiModelResponse ensureFinished() => $_ensure(1); +} + +/// A single observation while a model downloads. +class PullProgressUpdate extends $pb.GeneratedMessage { + factory PullProgressUpdate({ + $core.String? status, + $fixnum.Int64? completed, + $fixnum.Int64? total, + }) { + final result = create(); + if (status != null) result.status = status; + if (completed != null) result.completed = completed; + if (total != null) result.total = total; + return result; + } + + PullProgressUpdate._(); + + factory PullProgressUpdate.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PullProgressUpdate.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PullProgressUpdate', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'status') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'completed', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$fixnum.Int64>(3, _omitFieldNames ? '' : 'total', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullProgressUpdate clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullProgressUpdate copyWith(void Function(PullProgressUpdate) updates) => + super.copyWith((message) => updates(message as PullProgressUpdate)) + as PullProgressUpdate; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PullProgressUpdate create() => PullProgressUpdate._(); + @$core.override + PullProgressUpdate createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PullProgressUpdate getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PullProgressUpdate? _defaultInstance; + + /// The backend's own status text, e.g. "pulling manifest". Passed + /// through verbatim rather than translated: it names layers and + /// digests a client cannot reconstruct. + @$pb.TagNumber(1) + $core.String get status => $_getSZ(0); + @$pb.TagNumber(1) + set status($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasStatus() => $_has(0); + @$pb.TagNumber(1) + void clearStatus() => $_clearField(1); + + /// Bytes fetched so far for the layer this status refers to. + @$pb.TagNumber(2) + $fixnum.Int64 get completed => $_getI64(1); + @$pb.TagNumber(2) + set completed($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasCompleted() => $_has(1); + @$pb.TagNumber(2) + void clearCompleted() => $_clearField(2); + + /// Size of that layer; zero for statuses that carry no size, which + /// clients show as indeterminate rather than as zero progress. + @$pb.TagNumber(3) + $fixnum.Int64 get total => $_getI64(2); + @$pb.TagNumber(3) + set total($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasTotal() => $_has(2); + @$pb.TagNumber(3) + void clearTotal() => $_clearField(3); +} + class HardwareInfoResponse extends $pb.GeneratedMessage { factory HardwareInfoResponse({ $core.String? tier, diff --git a/lib/src/generated/chain/v1/hub.pbgrpc.dart b/lib/src/generated/chain/v1/hub.pbgrpc.dart index 67faea6..50b3928 100644 --- a/lib/src/generated/chain/v1/hub.pbgrpc.dart +++ b/lib/src/generated/chain/v1/hub.pbgrpc.dart @@ -818,6 +818,19 @@ class HubAdminClient extends $grpc.Client { return $createUnaryCall(_$pullSystemAiModel, request, options: options); } + /// Same pull, with live progress. A model pull moves gigabytes, so + /// the unary form above leaves a person waiting minutes with nothing + /// to look at. Emits Ollama's own status plus byte counts per layer, + /// ending in the response. + $grpc.ResponseStream<$0.PullProgressEvent> pullSystemAiModelStream( + $0.PullSystemAiModelRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$pullSystemAiModelStream, $async.Stream.fromIterable([request]), + options: options); + } + /// Drop every cached System-AI explanation. Surfaced in /// Studio's Settings so operators can force a clean slate /// without changing model/privacy mode (which also flushes). @@ -1139,6 +1152,11 @@ class HubAdminClient extends $grpc.Client { '/chain.v1.HubAdmin/PullSystemAiModel', ($0.PullSystemAiModelRequest value) => value.writeToBuffer(), $0.PullSystemAiModelResponse.fromBuffer); + static final _$pullSystemAiModelStream = + $grpc.ClientMethod<$0.PullSystemAiModelRequest, $0.PullProgressEvent>( + '/chain.v1.HubAdmin/PullSystemAiModelStream', + ($0.PullSystemAiModelRequest value) => value.writeToBuffer(), + $0.PullProgressEvent.fromBuffer); static final _$clearSystemLlmCache = $grpc.ClientMethod<$1.Empty, $0.ClearSystemLlmCacheResponse>( '/chain.v1.HubAdmin/ClearSystemLlmCache', @@ -1579,6 +1597,15 @@ abstract class HubAdminServiceBase extends $grpc.Service { ($core.List<$core.int> value) => $0.PullSystemAiModelRequest.fromBuffer(value), ($0.PullSystemAiModelResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.PullSystemAiModelRequest, $0.PullProgressEvent>( + 'PullSystemAiModelStream', + pullSystemAiModelStream_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.PullSystemAiModelRequest.fromBuffer(value), + ($0.PullProgressEvent value) => value.writeToBuffer())); $addMethod($grpc.ServiceMethod<$1.Empty, $0.ClearSystemLlmCacheResponse>( 'ClearSystemLlmCache', clearSystemLlmCache_Pre, @@ -2055,6 +2082,15 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.PullSystemAiModelResponse> pullSystemAiModel( $grpc.ServiceCall call, $0.PullSystemAiModelRequest request); + $async.Stream<$0.PullProgressEvent> pullSystemAiModelStream_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.PullSystemAiModelRequest> $request) async* { + yield* pullSystemAiModelStream($call, await $request); + } + + $async.Stream<$0.PullProgressEvent> pullSystemAiModelStream( + $grpc.ServiceCall call, $0.PullSystemAiModelRequest request); + $async.Future<$0.ClearSystemLlmCacheResponse> clearSystemLlmCache_Pre( $grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { return clearSystemLlmCache($call, await $request); diff --git a/lib/src/generated/chain/v1/hub.pbjson.dart b/lib/src/generated/chain/v1/hub.pbjson.dart index 1e40f80..8374ea2 100644 --- a/lib/src/generated/chain/v1/hub.pbjson.dart +++ b/lib/src/generated/chain/v1/hub.pbjson.dart @@ -1768,6 +1768,55 @@ final $typed_data.Uint8List pullSystemAiModelResponseDescriptor = $convert.base6 'ChlQdWxsU3lzdGVtQWlNb2RlbFJlc3BvbnNlEh0KCmVycm9yX2tpbmQYASABKAlSCWVycm9yS2' 'luZBISCgR0ZXh0GAIgASgJUgR0ZXh0Eh0KCmVsYXBzZWRfbXMYAyABKA1SCWVsYXBzZWRNcw=='); +@$core.Deprecated('Use pullProgressEventDescriptor instead') +const PullProgressEvent$json = { + '1': 'PullProgressEvent', + '2': [ + { + '1': 'update', + '3': 1, + '4': 1, + '5': 11, + '6': '.chain.v1.PullProgressUpdate', + '9': 0, + '10': 'update' + }, + { + '1': 'finished', + '3': 2, + '4': 1, + '5': 11, + '6': '.chain.v1.PullSystemAiModelResponse', + '9': 0, + '10': 'finished' + }, + ], + '8': [ + {'1': 'event'}, + ], +}; + +/// Descriptor for `PullProgressEvent`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List pullProgressEventDescriptor = $convert.base64Decode( + 'ChFQdWxsUHJvZ3Jlc3NFdmVudBI2CgZ1cGRhdGUYASABKAsyHC5jaGFpbi52MS5QdWxsUHJvZ3' + 'Jlc3NVcGRhdGVIAFIGdXBkYXRlEkEKCGZpbmlzaGVkGAIgASgLMiMuY2hhaW4udjEuUHVsbFN5' + 'c3RlbUFpTW9kZWxSZXNwb25zZUgAUghmaW5pc2hlZEIHCgVldmVudA=='); + +@$core.Deprecated('Use pullProgressUpdateDescriptor instead') +const PullProgressUpdate$json = { + '1': 'PullProgressUpdate', + '2': [ + {'1': 'status', '3': 1, '4': 1, '5': 9, '10': 'status'}, + {'1': 'completed', '3': 2, '4': 1, '5': 4, '10': 'completed'}, + {'1': 'total', '3': 3, '4': 1, '5': 4, '10': 'total'}, + ], +}; + +/// Descriptor for `PullProgressUpdate`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List pullProgressUpdateDescriptor = $convert.base64Decode( + 'ChJQdWxsUHJvZ3Jlc3NVcGRhdGUSFgoGc3RhdHVzGAEgASgJUgZzdGF0dXMSHAoJY29tcGxldG' + 'VkGAIgASgEUgljb21wbGV0ZWQSFAoFdG90YWwYAyABKARSBXRvdGFs'); + @$core.Deprecated('Use hardwareInfoResponseDescriptor instead') const HardwareInfoResponse$json = { '1': 'HardwareInfoResponse', diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 63af57b..3a712b9 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -111,6 +111,7 @@ typedef StoreEntry = pb.StoreEntry; typedef StoreSearchResponse = pb.StoreSearchResponse; typedef InstallModuleResponse = pb.InstallModuleResponse; typedef InstallProgressUpdate = pb.InstallProgressUpdate; +typedef PullProgressUpdate = pb.PullProgressUpdate; typedef Payload = pb_common.Payload; typedef SubmitResponse = pb.SubmitResponse; // Live-stream (T2) + detached-invocation (T3) wire types, re-exported @@ -867,6 +868,34 @@ class HubClient { } } + /// Pull a model while watching it happen. + /// + /// A model pull moves gigabytes; the unary [pullSystemAiModel] + /// leaves the caller waiting minutes with nothing to show. Emits the + /// backend's own status plus byte counts per layer, and calls + /// [onFinished] with the terminal response, whose `errorKind` is + /// non-empty when the pull failed. + Stream pullSystemAiModelStreaming({ + required String endpoint, + required String model, + String apiKeyEnv = '', + void Function(PullSystemAiModelResponse result)? onFinished, + }) async* { + final stream = _admin.pullSystemAiModelStream(pb.PullSystemAiModelRequest( + endpoint: endpoint, + model: model, + apiKeyEnv: apiKeyEnv, + )); + await for (final event in stream) { + if (event.hasUpdate()) { + yield event.update; + } else if (event.hasFinished()) { + onFinished?.call(event.finished); + return; + } + } + } + /// All saved flows known to the hub. Each entry carries the /// flow name, on-disk path and byte size. Future> listFlows() async { From aacb2406b45d61f5f429a7b1922121b0f465d14d Mon Sep 17 00:00:00 2001 From: flemming-it Date: Wed, 9 Sep 2026 12:36:44 +0200 Subject: [PATCH 3/3] chore: regenerate stubs for chain.v1.DataSource Signed-off-by: flemming-it --- lib/src/generated/chain/v1/hub.pb.dart | 120 +++++++++++++++++++++ lib/src/generated/chain/v1/hub.pbjson.dart | 33 +++++- 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/lib/src/generated/chain/v1/hub.pb.dart b/lib/src/generated/chain/v1/hub.pb.dart index dad3c65..1284e45 100644 --- a/lib/src/generated/chain/v1/hub.pb.dart +++ b/lib/src/generated/chain/v1/hub.pb.dart @@ -5493,6 +5493,7 @@ class StoreEntry extends $pb.GeneratedMessage { $core.String? canonicalCategoryLabel, $core.String? installVerification, $core.Iterable<$core.String>? maintainers, + DataSource? dataSource, }) { final result = create(); if (name != null) result.name = name; @@ -5525,6 +5526,7 @@ class StoreEntry extends $pb.GeneratedMessage { if (installVerification != null) result.installVerification = installVerification; if (maintainers != null) result.maintainers.addAll(maintainers); + if (dataSource != null) result.dataSource = dataSource; return result; } @@ -5567,6 +5569,8 @@ class StoreEntry extends $pb.GeneratedMessage { ..aOS(24, _omitFieldNames ? '' : 'canonicalCategoryLabel') ..aOS(25, _omitFieldNames ? '' : 'installVerification') ..pPS(26, _omitFieldNames ? '' : 'maintainers') + ..aOM(27, _omitFieldNames ? '' : 'dataSource', + subBuilder: DataSource.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -5860,6 +5864,122 @@ class StoreEntry extends $pb.GeneratedMessage { /// any — clients render an honest "not specified". @$pb.TagNumber(26) $pb.PbList<$core.String> get maintainers => $_getList(25); + + /// Where the module's data comes from, for `source.*` capabilities. + /// Absent for every other module: a text splitter reaches no + /// third-party material and has no provenance to declare. + @$pb.TagNumber(27) + DataSource get dataSource => $_getN(26); + @$pb.TagNumber(27) + set dataSource(DataSource value) => $_setField(27, value); + @$pb.TagNumber(27) + $core.bool hasDataSource() => $_has(26); + @$pb.TagNumber(27) + void clearDataSource() => $_clearField(27); + @$pb.TagNumber(27) + DataSource ensureDataSource() => $_ensure(26); +} + +/// Provenance of the material a `source.*` module fetches. +/// +/// Separate from the module's own licence, which covers its code. A +/// module can be Apache-2.0 while the corpus it reaches is +/// public-domain statute, CC-BY, or licence-gated. Clients show this +/// before the install button so an operator sees the terms they are +/// taking on. +class DataSource extends $pb.GeneratedMessage { + factory DataSource({ + $core.String? name, + $core.String? url, + $core.String? license, + $core.String? attribution, + }) { + final result = create(); + if (name != null) result.name = name; + if (url != null) result.url = url; + if (license != null) result.license = license; + if (attribution != null) result.attribution = attribution; + return result; + } + + DataSource._(); + + factory DataSource.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory DataSource.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DataSource', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..aOS(2, _omitFieldNames ? '' : 'url') + ..aOS(3, _omitFieldNames ? '' : 'license') + ..aOS(4, _omitFieldNames ? '' : 'attribution') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DataSource clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DataSource copyWith(void Function(DataSource) updates) => + super.copyWith((message) => updates(message as DataSource)) as DataSource; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DataSource create() => DataSource._(); + @$core.override + DataSource createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static DataSource getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static DataSource? _defaultInstance; + + /// Publisher, as a person would name it. + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$pb.TagNumber(1) + set name($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$pb.TagNumber(1) + void clearName() => $_clearField(1); + + /// Canonical URL of the upstream source. + @$pb.TagNumber(2) + $core.String get url => $_getSZ(1); + @$pb.TagNumber(2) + set url($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasUrl() => $_has(1); + @$pb.TagNumber(2) + void clearUrl() => $_clearField(2); + + /// Terms in plain words, not SPDX: the material often carries no + /// software licence at all. + @$pb.TagNumber(3) + $core.String get license => $_getSZ(2); + @$pb.TagNumber(3) + set license($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasLicense() => $_has(2); + @$pb.TagNumber(3) + void clearLicense() => $_clearField(3); + + /// Attribution the operator must carry with the output. Empty when + /// the upstream requires none. + @$pb.TagNumber(4) + $core.String get attribution => $_getSZ(3); + @$pb.TagNumber(4) + set attribution($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasAttribution() => $_has(3); + @$pb.TagNumber(4) + void clearAttribution() => $_clearField(4); } class ChannelStatusResponse extends $pb.GeneratedMessage { diff --git a/lib/src/generated/chain/v1/hub.pbjson.dart b/lib/src/generated/chain/v1/hub.pbjson.dart index 8374ea2..42c136f 100644 --- a/lib/src/generated/chain/v1/hub.pbjson.dart +++ b/lib/src/generated/chain/v1/hub.pbjson.dart @@ -1481,6 +1481,19 @@ const StoreEntry$json = { '10': 'installVerification' }, {'1': 'maintainers', '3': 26, '4': 3, '5': 9, '10': 'maintainers'}, + { + '1': 'data_source', + '3': 27, + '4': 1, + '5': 11, + '6': '.chain.v1.DataSource', + '9': 0, + '10': 'dataSource', + '17': true + }, + ], + '8': [ + {'1': '_data_source'}, ], }; @@ -1501,7 +1514,25 @@ final $typed_data.Uint8List storeEntryDescriptor = $convert.base64Decode( 'aW5kEhYKBnNvdXJjZRgWIAEoCVIGc291cmNlEi0KEmNhbm9uaWNhbF9jYXRlZ29yeRgXIAEoCV' 'IRY2Fub25pY2FsQ2F0ZWdvcnkSOAoYY2Fub25pY2FsX2NhdGVnb3J5X2xhYmVsGBggASgJUhZj' 'YW5vbmljYWxDYXRlZ29yeUxhYmVsEjEKFGluc3RhbGxfdmVyaWZpY2F0aW9uGBkgASgJUhNpbn' - 'N0YWxsVmVyaWZpY2F0aW9uEiAKC21haW50YWluZXJzGBogAygJUgttYWludGFpbmVycw=='); + 'N0YWxsVmVyaWZpY2F0aW9uEiAKC21haW50YWluZXJzGBogAygJUgttYWludGFpbmVycxI6Cgtk' + 'YXRhX3NvdXJjZRgbIAEoCzIULmNoYWluLnYxLkRhdGFTb3VyY2VIAFIKZGF0YVNvdXJjZYgBAU' + 'IOCgxfZGF0YV9zb3VyY2U='); + +@$core.Deprecated('Use dataSourceDescriptor instead') +const DataSource$json = { + '1': 'DataSource', + '2': [ + {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, + {'1': 'url', '3': 2, '4': 1, '5': 9, '10': 'url'}, + {'1': 'license', '3': 3, '4': 1, '5': 9, '10': 'license'}, + {'1': 'attribution', '3': 4, '4': 1, '5': 9, '10': 'attribution'}, + ], +}; + +/// Descriptor for `DataSource`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List dataSourceDescriptor = $convert.base64Decode( + 'CgpEYXRhU291cmNlEhIKBG5hbWUYASABKAlSBG5hbWUSEAoDdXJsGAIgASgJUgN1cmwSGAoHbG' + 'ljZW5zZRgDIAEoCVIHbGljZW5zZRIgCgthdHRyaWJ1dGlvbhgEIAEoCVILYXR0cmlidXRpb24='); @$core.Deprecated('Use channelStatusResponseDescriptor instead') const ChannelStatusResponse$json = {