From f04da407514808be8af76e0de1a7093898a1e198 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Thu, 7 May 2026 14:02:43 +0200 Subject: [PATCH] feat: testSystemAi accepts draft config; list+pull models (v0.6.0) `testSystemAi` now takes optional provider/endpoint/model/ apiKeyEnv/privacyMode params; the editor passes form values so Test Connection works before Save. New `listSystemAiModels` fetches the provider's `/v1/models` listing, `pullSystemAiModel` calls Ollama's `/api/pull` synchronously. Signed-off-by: flemming-it --- lib/src/generated/fai/v1/hub.pb.dart | 379 +++++++++++++++++++++++ lib/src/generated/fai/v1/hub.pbgrpc.dart | 70 ++++- lib/src/generated/fai/v1/hub.pbjson.dart | 79 +++++ lib/src/hub_client.dart | 56 +++- pubspec.yaml | 2 +- 5 files changed, 570 insertions(+), 16 deletions(-) diff --git a/lib/src/generated/fai/v1/hub.pb.dart b/lib/src/generated/fai/v1/hub.pb.dart index 814fd8d..98bca3c 100644 --- a/lib/src/generated/fai/v1/hub.pb.dart +++ b/lib/src/generated/fai/v1/hub.pb.dart @@ -3145,6 +3145,385 @@ class UpdateSystemAiRequest extends $pb.GeneratedMessage { void clearPrivacyMode() => $_clearField(5); } +class TestSystemAiRequest extends $pb.GeneratedMessage { + factory TestSystemAiRequest({ + $core.String? provider, + $core.String? endpoint, + $core.String? model, + $core.String? apiKeyEnv, + $core.String? privacyMode, + }) { + final $result = create(); + if (provider != null) { + $result.provider = provider; + } + if (endpoint != null) { + $result.endpoint = endpoint; + } + if (model != null) { + $result.model = model; + } + if (apiKeyEnv != null) { + $result.apiKeyEnv = apiKeyEnv; + } + if (privacyMode != null) { + $result.privacyMode = privacyMode; + } + return $result; + } + TestSystemAiRequest._() : super(); + factory TestSystemAiRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory TestSystemAiRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'TestSystemAiRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'provider') + ..aOS(2, _omitFieldNames ? '' : 'endpoint') + ..aOS(3, _omitFieldNames ? '' : 'model') + ..aOS(4, _omitFieldNames ? '' : 'apiKeyEnv') + ..aOS(5, _omitFieldNames ? '' : 'privacyMode') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TestSystemAiRequest clone() => TestSystemAiRequest()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TestSystemAiRequest copyWith(void Function(TestSystemAiRequest) updates) => super.copyWith((message) => updates(message as TestSystemAiRequest)) as TestSystemAiRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TestSystemAiRequest create() => TestSystemAiRequest._(); + TestSystemAiRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static TestSystemAiRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static TestSystemAiRequest? _defaultInstance; + + /// Draft system-AI config to probe with. Empty fields fall + /// back to the live in-memory config — handy for "test current + /// settings" without re-sending them. + @$pb.TagNumber(1) + $core.String get provider => $_getSZ(0); + @$pb.TagNumber(1) + set provider($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasProvider() => $_has(0); + @$pb.TagNumber(1) + void clearProvider() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get endpoint => $_getSZ(1); + @$pb.TagNumber(2) + set endpoint($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasEndpoint() => $_has(1); + @$pb.TagNumber(2) + void clearEndpoint() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get model => $_getSZ(2); + @$pb.TagNumber(3) + set model($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasModel() => $_has(2); + @$pb.TagNumber(3) + void clearModel() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get apiKeyEnv => $_getSZ(3); + @$pb.TagNumber(4) + set apiKeyEnv($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasApiKeyEnv() => $_has(3); + @$pb.TagNumber(4) + void clearApiKeyEnv() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get privacyMode => $_getSZ(4); + @$pb.TagNumber(5) + set privacyMode($core.String v) { $_setString(4, v); } + @$pb.TagNumber(5) + $core.bool hasPrivacyMode() => $_has(4); + @$pb.TagNumber(5) + void clearPrivacyMode() => $_clearField(5); +} + +class ListSystemAiModelsResponse extends $pb.GeneratedMessage { + factory ListSystemAiModelsResponse({ + $core.String? errorKind, + $core.String? text, + $core.Iterable? models, + }) { + final $result = create(); + if (errorKind != null) { + $result.errorKind = errorKind; + } + if (text != null) { + $result.text = text; + } + if (models != null) { + $result.models.addAll(models); + } + return $result; + } + ListSystemAiModelsResponse._() : super(); + factory ListSystemAiModelsResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ListSystemAiModelsResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ListSystemAiModelsResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'errorKind') + ..aOS(2, _omitFieldNames ? '' : 'text') + ..pc(3, _omitFieldNames ? '' : 'models', $pb.PbFieldType.PM, subBuilder: SystemAiModel.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSystemAiModelsResponse clone() => ListSystemAiModelsResponse()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSystemAiModelsResponse copyWith(void Function(ListSystemAiModelsResponse) updates) => super.copyWith((message) => updates(message as ListSystemAiModelsResponse)) as ListSystemAiModelsResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListSystemAiModelsResponse create() => ListSystemAiModelsResponse._(); + ListSystemAiModelsResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ListSystemAiModelsResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ListSystemAiModelsResponse? _defaultInstance; + + /// Empty on success. Otherwise: same error-kind taxonomy as + /// AskAi (network / http / parse / disabled / env_missing). + @$pb.TagNumber(1) + $core.String get errorKind => $_getSZ(0); + @$pb.TagNumber(1) + set errorKind($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasErrorKind() => $_has(0); + @$pb.TagNumber(1) + void clearErrorKind() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get text => $_getSZ(1); + @$pb.TagNumber(2) + set text($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasText() => $_has(1); + @$pb.TagNumber(2) + void clearText() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList get models => $_getList(2); +} + +class SystemAiModel extends $pb.GeneratedMessage { + factory SystemAiModel({ + $core.String? id, + $core.String? ownedBy, + }) { + final $result = create(); + if (id != null) { + $result.id = id; + } + if (ownedBy != null) { + $result.ownedBy = ownedBy; + } + return $result; + } + SystemAiModel._() : super(); + factory SystemAiModel.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SystemAiModel.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SystemAiModel', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'ownedBy') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SystemAiModel clone() => SystemAiModel()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SystemAiModel copyWith(void Function(SystemAiModel) updates) => super.copyWith((message) => updates(message as SystemAiModel)) as SystemAiModel; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SystemAiModel create() => SystemAiModel._(); + SystemAiModel createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SystemAiModel getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SystemAiModel? _defaultInstance; + + /// Model identifier the operator passes as `model:`. + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + /// Optional family / owner string the provider returned. + @$pb.TagNumber(2) + $core.String get ownedBy => $_getSZ(1); + @$pb.TagNumber(2) + set ownedBy($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasOwnedBy() => $_has(1); + @$pb.TagNumber(2) + void clearOwnedBy() => $_clearField(2); +} + +class PullSystemAiModelRequest extends $pb.GeneratedMessage { + factory PullSystemAiModelRequest({ + $core.String? endpoint, + $core.String? apiKeyEnv, + $core.String? model, + }) { + final $result = create(); + if (endpoint != null) { + $result.endpoint = endpoint; + } + if (apiKeyEnv != null) { + $result.apiKeyEnv = apiKeyEnv; + } + if (model != null) { + $result.model = model; + } + return $result; + } + PullSystemAiModelRequest._() : super(); + factory PullSystemAiModelRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory PullSystemAiModelRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PullSystemAiModelRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'endpoint') + ..aOS(2, _omitFieldNames ? '' : 'apiKeyEnv') + ..aOS(3, _omitFieldNames ? '' : 'model') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullSystemAiModelRequest clone() => PullSystemAiModelRequest()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullSystemAiModelRequest copyWith(void Function(PullSystemAiModelRequest) updates) => super.copyWith((message) => updates(message as PullSystemAiModelRequest)) as PullSystemAiModelRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PullSystemAiModelRequest create() => PullSystemAiModelRequest._(); + PullSystemAiModelRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static PullSystemAiModelRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static PullSystemAiModelRequest? _defaultInstance; + + /// Draft endpoint + auth. Required; the model is the operator's + /// pick. Currently only Ollama-style /api/pull is supported. + @$pb.TagNumber(1) + $core.String get endpoint => $_getSZ(0); + @$pb.TagNumber(1) + set endpoint($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasEndpoint() => $_has(0); + @$pb.TagNumber(1) + void clearEndpoint() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get apiKeyEnv => $_getSZ(1); + @$pb.TagNumber(2) + set apiKeyEnv($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasApiKeyEnv() => $_has(1); + @$pb.TagNumber(2) + void clearApiKeyEnv() => $_clearField(2); + + /// Model id to pull, e.g. `gemma3:4b`, `llama3.2:3b`. + @$pb.TagNumber(3) + $core.String get model => $_getSZ(2); + @$pb.TagNumber(3) + set model($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasModel() => $_has(2); + @$pb.TagNumber(3) + void clearModel() => $_clearField(3); +} + +class PullSystemAiModelResponse extends $pb.GeneratedMessage { + factory PullSystemAiModelResponse({ + $core.String? errorKind, + $core.String? text, + $core.int? elapsedMs, + }) { + final $result = create(); + if (errorKind != null) { + $result.errorKind = errorKind; + } + if (text != null) { + $result.text = text; + } + if (elapsedMs != null) { + $result.elapsedMs = elapsedMs; + } + return $result; + } + PullSystemAiModelResponse._() : super(); + factory PullSystemAiModelResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory PullSystemAiModelResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PullSystemAiModelResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'errorKind') + ..aOS(2, _omitFieldNames ? '' : 'text') + ..a<$core.int>(3, _omitFieldNames ? '' : 'elapsedMs', $pb.PbFieldType.OU3) + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullSystemAiModelResponse clone() => PullSystemAiModelResponse()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PullSystemAiModelResponse copyWith(void Function(PullSystemAiModelResponse) updates) => super.copyWith((message) => updates(message as PullSystemAiModelResponse)) as PullSystemAiModelResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PullSystemAiModelResponse create() => PullSystemAiModelResponse._(); + PullSystemAiModelResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static PullSystemAiModelResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static PullSystemAiModelResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get errorKind => $_getSZ(0); + @$pb.TagNumber(1) + set errorKind($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasErrorKind() => $_has(0); + @$pb.TagNumber(1) + void clearErrorKind() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get text => $_getSZ(1); + @$pb.TagNumber(2) + set text($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasText() => $_has(1); + @$pb.TagNumber(2) + void clearText() => $_clearField(2); + + /// Total wall-clock time the pull took, in milliseconds. + @$pb.TagNumber(3) + $core.int get elapsedMs => $_getIZ(2); + @$pb.TagNumber(3) + set elapsedMs($core.int v) { $_setUnsignedInt32(2, v); } + @$pb.TagNumber(3) + $core.bool hasElapsedMs() => $_has(2); + @$pb.TagNumber(3) + void clearElapsedMs() => $_clearField(3); +} + const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/src/generated/fai/v1/hub.pbgrpc.dart b/lib/src/generated/fai/v1/hub.pbgrpc.dart index 0c5cd2d..27d909b 100644 --- a/lib/src/generated/fai/v1/hub.pbgrpc.dart +++ b/lib/src/generated/fai/v1/hub.pbgrpc.dart @@ -199,10 +199,18 @@ class HubAdminClient extends $grpc.Client { '/fai.v1.HubAdmin/UpdateSystemAi', ($0.UpdateSystemAiRequest value) => value.writeToBuffer(), ($core.List<$core.int> value) => $0.SystemAiStatusResponse.fromBuffer(value)); - static final _$testSystemAi = $grpc.ClientMethod<$1.Empty, $0.AskAiResponse>( + static final _$testSystemAi = $grpc.ClientMethod<$0.TestSystemAiRequest, $0.AskAiResponse>( '/fai.v1.HubAdmin/TestSystemAi', - ($1.Empty value) => value.writeToBuffer(), + ($0.TestSystemAiRequest value) => value.writeToBuffer(), ($core.List<$core.int> value) => $0.AskAiResponse.fromBuffer(value)); + static final _$listSystemAiModels = $grpc.ClientMethod<$0.TestSystemAiRequest, $0.ListSystemAiModelsResponse>( + '/fai.v1.HubAdmin/ListSystemAiModels', + ($0.TestSystemAiRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.ListSystemAiModelsResponse.fromBuffer(value)); + static final _$pullSystemAiModel = $grpc.ClientMethod<$0.PullSystemAiModelRequest, $0.PullSystemAiModelResponse>( + '/fai.v1.HubAdmin/PullSystemAiModel', + ($0.PullSystemAiModelRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.PullSystemAiModelResponse.fromBuffer(value)); HubAdminClient(super.channel, {super.options, super.interceptors}); @@ -324,13 +332,29 @@ class HubAdminClient extends $grpc.Client { return $createUnaryCall(_$updateSystemAi, request, options: options); } - /// Probe the configured provider with an empty ping (model - /// listing or short message). Returns the same error_kind - /// taxonomy as AskAi so Studio's "Test connection" button - /// can render the same inline fix copy. - $grpc.ResponseFuture<$0.AskAiResponse> testSystemAi($1.Empty request, {$grpc.CallOptions? options}) { + /// Probe a system-AI configuration with a tiny ping. The + /// request body carries the *draft* config the operator is + /// editing — Test Connection works *before* Save so the user + /// doesn't have to commit a broken config to validate it. + /// An all-empty body falls back to the live in-memory config. + $grpc.ResponseFuture<$0.AskAiResponse> testSystemAi($0.TestSystemAiRequest request, {$grpc.CallOptions? options}) { return $createUnaryCall(_$testSystemAi, request, options: options); } + + /// List the models the configured provider exposes via + /// `GET /v1/models`. Studio's editor populates the model + /// dropdown from this. Same draft-vs-live semantics as + /// TestSystemAi. + $grpc.ResponseFuture<$0.ListSystemAiModelsResponse> listSystemAiModels($0.TestSystemAiRequest request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$listSystemAiModels, request, options: options); + } + + /// Pull (download + install) an Ollama model via the native + /// /api/pull endpoint. Synchronous, may take minutes for + /// large models. Same error-kind taxonomy as AskAi. + $grpc.ResponseFuture<$0.PullSystemAiModelResponse> pullSystemAiModel($0.PullSystemAiModelRequest request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$pullSystemAiModel, request, options: options); + } } @$pb.GrpcServiceName('fai.v1.HubAdmin') @@ -471,13 +495,27 @@ abstract class HubAdminServiceBase extends $grpc.Service { false, ($core.List<$core.int> value) => $0.UpdateSystemAiRequest.fromBuffer(value), ($0.SystemAiStatusResponse value) => value.writeToBuffer())); - $addMethod($grpc.ServiceMethod<$1.Empty, $0.AskAiResponse>( + $addMethod($grpc.ServiceMethod<$0.TestSystemAiRequest, $0.AskAiResponse>( 'TestSystemAi', testSystemAi_Pre, false, false, - ($core.List<$core.int> value) => $1.Empty.fromBuffer(value), + ($core.List<$core.int> value) => $0.TestSystemAiRequest.fromBuffer(value), ($0.AskAiResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.TestSystemAiRequest, $0.ListSystemAiModelsResponse>( + 'ListSystemAiModels', + listSystemAiModels_Pre, + false, + false, + ($core.List<$core.int> value) => $0.TestSystemAiRequest.fromBuffer(value), + ($0.ListSystemAiModelsResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.PullSystemAiModelRequest, $0.PullSystemAiModelResponse>( + 'PullSystemAiModel', + pullSystemAiModel_Pre, + false, + false, + ($core.List<$core.int> value) => $0.PullSystemAiModelRequest.fromBuffer(value), + ($0.PullSystemAiModelResponse value) => value.writeToBuffer())); } $async.Future<$0.CapabilityList> listCapabilities_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { @@ -556,10 +594,18 @@ abstract class HubAdminServiceBase extends $grpc.Service { return updateSystemAi($call, await $request); } - $async.Future<$0.AskAiResponse> testSystemAi_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { + $async.Future<$0.AskAiResponse> testSystemAi_Pre($grpc.ServiceCall $call, $async.Future<$0.TestSystemAiRequest> $request) async { return testSystemAi($call, await $request); } + $async.Future<$0.ListSystemAiModelsResponse> listSystemAiModels_Pre($grpc.ServiceCall $call, $async.Future<$0.TestSystemAiRequest> $request) async { + return listSystemAiModels($call, await $request); + } + + $async.Future<$0.PullSystemAiModelResponse> pullSystemAiModel_Pre($grpc.ServiceCall $call, $async.Future<$0.PullSystemAiModelRequest> $request) async { + return pullSystemAiModel($call, await $request); + } + $async.Future<$0.CapabilityList> listCapabilities($grpc.ServiceCall call, $1.Empty request); $async.Future<$0.ModuleInfoResponse> moduleInfo($grpc.ServiceCall call, $0.ModuleInfoRequest request); $async.Future<$0.CheckPermissionResponse> checkPermission($grpc.ServiceCall call, $0.CheckPermissionRequest request); @@ -579,5 +625,7 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.SystemAiStatusResponse> systemAiStatus($grpc.ServiceCall call, $1.Empty request); $async.Future<$0.AskAiResponse> askAi($grpc.ServiceCall call, $0.AskAiRequest request); $async.Future<$0.SystemAiStatusResponse> updateSystemAi($grpc.ServiceCall call, $0.UpdateSystemAiRequest request); - $async.Future<$0.AskAiResponse> testSystemAi($grpc.ServiceCall call, $1.Empty request); + $async.Future<$0.AskAiResponse> testSystemAi($grpc.ServiceCall call, $0.TestSystemAiRequest request); + $async.Future<$0.ListSystemAiModelsResponse> listSystemAiModels($grpc.ServiceCall call, $0.TestSystemAiRequest request); + $async.Future<$0.PullSystemAiModelResponse> pullSystemAiModel($grpc.ServiceCall call, $0.PullSystemAiModelRequest request); } diff --git a/lib/src/generated/fai/v1/hub.pbjson.dart b/lib/src/generated/fai/v1/hub.pbjson.dart index 48df095..d82eaba 100644 --- a/lib/src/generated/fai/v1/hub.pbjson.dart +++ b/lib/src/generated/fai/v1/hub.pbjson.dart @@ -735,3 +735,82 @@ final $typed_data.Uint8List updateSystemAiRequestDescriptor = $convert.base64Dec 'eV9lbnYYBCABKAlSCWFwaUtleUVudhIhCgxwcml2YWN5X21vZGUYBSABKAlSC3ByaXZhY3lNb2' 'Rl'); +@$core.Deprecated('Use testSystemAiRequestDescriptor instead') +const TestSystemAiRequest$json = { + '1': 'TestSystemAiRequest', + '2': [ + {'1': 'provider', '3': 1, '4': 1, '5': 9, '10': 'provider'}, + {'1': 'endpoint', '3': 2, '4': 1, '5': 9, '10': 'endpoint'}, + {'1': 'model', '3': 3, '4': 1, '5': 9, '10': 'model'}, + {'1': 'api_key_env', '3': 4, '4': 1, '5': 9, '10': 'apiKeyEnv'}, + {'1': 'privacy_mode', '3': 5, '4': 1, '5': 9, '10': 'privacyMode'}, + ], +}; + +/// Descriptor for `TestSystemAiRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List testSystemAiRequestDescriptor = $convert.base64Decode( + 'ChNUZXN0U3lzdGVtQWlSZXF1ZXN0EhoKCHByb3ZpZGVyGAEgASgJUghwcm92aWRlchIaCghlbm' + 'Rwb2ludBgCIAEoCVIIZW5kcG9pbnQSFAoFbW9kZWwYAyABKAlSBW1vZGVsEh4KC2FwaV9rZXlf' + 'ZW52GAQgASgJUglhcGlLZXlFbnYSIQoMcHJpdmFjeV9tb2RlGAUgASgJUgtwcml2YWN5TW9kZQ' + '=='); + +@$core.Deprecated('Use listSystemAiModelsResponseDescriptor instead') +const ListSystemAiModelsResponse$json = { + '1': 'ListSystemAiModelsResponse', + '2': [ + {'1': 'error_kind', '3': 1, '4': 1, '5': 9, '10': 'errorKind'}, + {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'}, + {'1': 'models', '3': 3, '4': 3, '5': 11, '6': '.fai.v1.SystemAiModel', '10': 'models'}, + ], +}; + +/// Descriptor for `ListSystemAiModelsResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listSystemAiModelsResponseDescriptor = $convert.base64Decode( + 'ChpMaXN0U3lzdGVtQWlNb2RlbHNSZXNwb25zZRIdCgplcnJvcl9raW5kGAEgASgJUgllcnJvck' + 'tpbmQSEgoEdGV4dBgCIAEoCVIEdGV4dBItCgZtb2RlbHMYAyADKAsyFS5mYWkudjEuU3lzdGVt' + 'QWlNb2RlbFIGbW9kZWxz'); + +@$core.Deprecated('Use systemAiModelDescriptor instead') +const SystemAiModel$json = { + '1': 'SystemAiModel', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'owned_by', '3': 2, '4': 1, '5': 9, '10': 'ownedBy'}, + ], +}; + +/// Descriptor for `SystemAiModel`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List systemAiModelDescriptor = $convert.base64Decode( + 'Cg1TeXN0ZW1BaU1vZGVsEg4KAmlkGAEgASgJUgJpZBIZCghvd25lZF9ieRgCIAEoCVIHb3duZW' + 'RCeQ=='); + +@$core.Deprecated('Use pullSystemAiModelRequestDescriptor instead') +const PullSystemAiModelRequest$json = { + '1': 'PullSystemAiModelRequest', + '2': [ + {'1': 'endpoint', '3': 1, '4': 1, '5': 9, '10': 'endpoint'}, + {'1': 'api_key_env', '3': 2, '4': 1, '5': 9, '10': 'apiKeyEnv'}, + {'1': 'model', '3': 3, '4': 1, '5': 9, '10': 'model'}, + ], +}; + +/// Descriptor for `PullSystemAiModelRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List pullSystemAiModelRequestDescriptor = $convert.base64Decode( + 'ChhQdWxsU3lzdGVtQWlNb2RlbFJlcXVlc3QSGgoIZW5kcG9pbnQYASABKAlSCGVuZHBvaW50Eh' + '4KC2FwaV9rZXlfZW52GAIgASgJUglhcGlLZXlFbnYSFAoFbW9kZWwYAyABKAlSBW1vZGVs'); + +@$core.Deprecated('Use pullSystemAiModelResponseDescriptor instead') +const PullSystemAiModelResponse$json = { + '1': 'PullSystemAiModelResponse', + '2': [ + {'1': 'error_kind', '3': 1, '4': 1, '5': 9, '10': 'errorKind'}, + {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'}, + {'1': 'elapsed_ms', '3': 3, '4': 1, '5': 13, '10': 'elapsedMs'}, + ], +}; + +/// Descriptor for `PullSystemAiModelResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List pullSystemAiModelResponseDescriptor = $convert.base64Decode( + 'ChlQdWxsU3lzdGVtQWlNb2RlbFJlc3BvbnNlEh0KCmVycm9yX2tpbmQYASABKAlSCWVycm9yS2' + 'luZBISCgR0ZXh0GAIgASgJUgR0ZXh0Eh0KCmVsYXBzZWRfbXMYAyABKA1SCWVsYXBzZWRNcw=='); + diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 7898190..6bc1146 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -46,6 +46,9 @@ typedef ChannelStatusResponse = pb.ChannelStatusResponse; typedef ChannelEntry = pb.ChannelEntry; typedef SystemAiStatusResponse = pb.SystemAiStatusResponse; typedef AskAiResponse = pb.AskAiResponse; +typedef ListSystemAiModelsResponse = pb.ListSystemAiModelsResponse; +typedef SystemAiModel = pb.SystemAiModel; +typedef PullSystemAiModelResponse = pb.PullSystemAiModelResponse; typedef ModuleInfoResponse = pb.ModuleInfoResponse; typedef FlowSummary = pb.FlowSummary; typedef StoreEntry = pb.StoreEntry; @@ -219,10 +222,55 @@ class HubClient { )); } - /// Probe the configured provider with a tiny ping. Same - /// error-kind taxonomy as [askAi]. - Future testSystemAi() { - return _admin.testSystemAi(Empty()); + /// Probe a (possibly draft) System-AI configuration. Pass the + /// form values from the editor so "Test connection" works + /// without a save first. Empty fields fall back to the live + /// in-memory config. + Future testSystemAi({ + String provider = '', + String endpoint = '', + String model = '', + String apiKeyEnv = '', + String privacyMode = '', + }) { + return _admin.testSystemAi(pb.TestSystemAiRequest( + provider: provider, + endpoint: endpoint, + model: model, + apiKeyEnv: apiKeyEnv, + privacyMode: privacyMode, + )); + } + + /// List the models the configured provider exposes via + /// `GET /v1/models`. Same draft-vs-live semantics as + /// [testSystemAi]. Errors come back inside + /// [ListSystemAiModelsResponse.errorKind]. + Future listSystemAiModels({ + String provider = '', + String endpoint = '', + String apiKeyEnv = '', + }) { + return _admin.listSystemAiModels(pb.TestSystemAiRequest( + provider: provider, + endpoint: endpoint, + apiKeyEnv: apiKeyEnv, + )); + } + + /// Pull (download + install) an Ollama model via /api/pull. + /// Synchronous; takes minutes for large models. Errors come + /// back inside [PullSystemAiModelResponse.errorKind]. + Future pullSystemAiModel({ + required String endpoint, + required String model, + String apiKeyEnv = '', + }) { + return _admin.pullSystemAiModel(pb.PullSystemAiModelRequest( + endpoint: endpoint, + model: model, + apiKeyEnv: apiKeyEnv, + )); } /// Search the hub's bundled store index. All filters are diff --git a/pubspec.yaml b/pubspec.yaml index ede5387..4b7d09e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fai_dart_sdk description: gRPC client SDK for the F∆I Platform hub. Used by F∆I Studio and Tier-3 domain apps. -version: 0.5.0 +version: 0.6.0 publish_to: 'none' repository: https://git.flemming.ws/fai/dart-sdk