feat(base,learn,todo): the operator's assistants, and the instance's privacy page (ADR 0021, step 2)

The server half of the assistants (points 4 to 8, D27 to D46): the
operator sets language models in deploy/assistants.env, grants them to
people with limits, and the module forwards their questions with the
operator's key, counting what each used, never what was asked.

- assistantsFrom: ASSISTANTS, ASSISTANT_ID_KEY, ASSISTANT_<ID>_NAME,
  _PROTOCOL (anthropic, openai, openai-responses), _BASE_URL, _MODEL,
  _KEY, _IMAGES, _PRICE_INPUT and _PRICE_OUTPUT (per million tokens),
  ASSISTANT_CURRENCY, ASSISTANT_OWN_KEYS; anything unusable stops the
  server as it starts, naming the variable.
- The assistant endpoint (identified): offered, agree (D40), ask as a
  Serverpod stream (D35): the text as the model writes it, then the
  whole turn, sent once the request is counted; figures, the caller's
  own. Checked in order: at most 2 questions on their way per person
  (tooManyAtOnce), the grant, the agreement, limits in the pricing's
  unit now (limitOutdated), the month's and the day's limits (with when
  they reset, UTC), the question (malformed, imagesNotRead, tooLarge
  past 400,000 characters, an image over 3,932,160 bytes or more than
  20 images); output capped at 16,384 tokens. Each request carries the
  person's opaque ID (HMAC-SHA256 under ASSISTANT_ID_KEY, D30). Once
  sent, the provider's reply is read to its end even if the app
  cancels, and counted on a session of its own; a provider's failure is
  logged by kind and status, told as providerKey, providerBusy (with
  its wait), providerRefused or providerFailed.
- The assistantOperator endpoint (identified and operator, on the
  register): setUp, grants, grant (assistantNotSet, invalidLimit,
  notFound), revoke, figures of a UTC month.
- Tables assistant_grant and assistant_figures (per person, assistant
  and UTC day), in the module's, Learn's and Todo's migrations. The
  daily pruning keeps this month and the two before
  (forgetOldAssistantFigures, D41); deletePerson deletes both.
  A member offers no assistant (D43).
- instance.basics() gains assistantOwnKeys.
- /privacy (D39, addPrivacyPageRoute, on the register's web server,
  before the profile pages' route): made from the instance's settings,
  English or German, no JavaScript: who may sign up, where the data and
  backups are, what the module keeps (the account, sessions, IP
  addresses, the server's log), the host's privacyPagePoints (Learn's:
  studysets synced, private ones encrypted on the devices with what
  stays readable, what is published, dictionary lookups, the learner
  count), each assistant with its model and host, what goes and what is
  counted, and whether personal keys are allowed. Never a key.

Tests: 70 on the assistants (grants, limits, refusals in order,
streaming, cancelling, counting, concurrency, the operator's denials,
deletion, pruning), 19 on the settings, 8 on the privacy page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 18:07:01 +01:00
commit dbae802915
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
90 changed files with 56851 additions and 33 deletions

View file

@ -0,0 +1,150 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_message.dart' as _irryv85c;
import '../assistant/assistant_stop.dart' as _is7w56fp;
/// A piece of an assistant's answer, as it streams (Halkyon Learn's ADR
/// 0021, D35): text as the model writes it, then the whole turn.
abstract class AssistantAnswer
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantAnswer._({
this.delta,
this.message,
this.stop,
this.inputTokens,
this.outputTokens,
});
factory AssistantAnswer({
String? delta,
_irryv85c.AssistantMessage? message,
_is7w56fp.AssistantStop? stop,
int? inputTokens,
int? outputTokens,
}) = _AssistantAnswerImpl;
factory AssistantAnswer.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantAnswer(
delta: jsonSerialization['delta'] as String?,
message: jsonSerialization['message'] == null
? null
: _iwgbpyo5.Protocol().deserialize<_irryv85c.AssistantMessage>(
jsonSerialization['message'],
),
stop: jsonSerialization['stop'] == null
? null
: _is7w56fp.AssistantStop.fromJson(
(jsonSerialization['stop'] as String),
),
inputTokens: jsonSerialization['inputTokens'] as int?,
outputTokens: jsonSerialization['outputTokens'] as int?,
);
}
/// More of the text; null in the last piece.
String? delta;
/// The model's whole turn, to be sent back with the next question; only
/// in the last piece.
_irryv85c.AssistantMessage? message;
/// Why it stopped; only in the last piece.
_is7w56fp.AssistantStop? stop;
/// The input tokens counted; only in the last piece.
int? inputTokens;
/// The output tokens counted; only in the last piece.
int? outputTokens;
/// Returns a shallow copy of this [AssistantAnswer]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantAnswer copyWith({
String? delta,
_irryv85c.AssistantMessage? message,
_is7w56fp.AssistantStop? stop,
int? inputTokens,
int? outputTokens,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantAnswer',
if (delta != null) 'delta': delta,
if (message != null) 'message': message?.toJson(),
if (stop != null) 'stop': stop?.toJson(),
if (inputTokens != null) 'inputTokens': inputTokens,
if (outputTokens != null) 'outputTokens': outputTokens,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantAnswer',
if (delta != null) 'delta': delta,
if (message != null) 'message': message?.toJsonForProtocol(),
if (stop != null) 'stop': stop?.toJson(),
if (inputTokens != null) 'inputTokens': inputTokens,
if (outputTokens != null) 'outputTokens': outputTokens,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantAnswerImpl extends AssistantAnswer {
_AssistantAnswerImpl({
String? delta,
_irryv85c.AssistantMessage? message,
_is7w56fp.AssistantStop? stop,
int? inputTokens,
int? outputTokens,
}) : super._(
delta: delta,
message: message,
stop: stop,
inputTokens: inputTokens,
outputTokens: outputTokens,
);
/// Returns a shallow copy of this [AssistantAnswer]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantAnswer copyWith({
Object? delta = _Undefined,
Object? message = _Undefined,
Object? stop = _Undefined,
Object? inputTokens = _Undefined,
Object? outputTokens = _Undefined,
}) {
return AssistantAnswer(
delta: delta is String? ? delta : this.delta,
message: message is _irryv85c.AssistantMessage?
? message
: this.message?.copyWith(),
stop: stop is _is7w56fp.AssistantStop? ? stop : this.stop,
inputTokens: inputTokens is int? ? inputTokens : this.inputTokens,
outputTokens: outputTokens is int? ? outputTokens : this.outputTokens,
);
}
}

View file

@ -0,0 +1,215 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_limit_unit.dart' as _il94nvme;
/// A grant as the operator sees it (Halkyon Learn's ADR 0021, point 6).
abstract class AssistantGrantOverview
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantGrantOverview._({
required this.account,
this.handle,
required this.assistant,
required this.assistantSet,
required this.grantedAt,
required this.limitUnit,
this.dailyLimit,
this.monthlyLimit,
this.agreedAt,
required this.outdated,
});
factory AssistantGrantOverview({
required _isc.UuidValue account,
String? handle,
required String assistant,
required bool assistantSet,
required DateTime grantedAt,
required _il94nvme.AssistantLimitUnit limitUnit,
int? dailyLimit,
int? monthlyLimit,
DateTime? agreedAt,
required bool outdated,
}) = _AssistantGrantOverviewImpl;
factory AssistantGrantOverview.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return AssistantGrantOverview(
account: _isc.UuidValueJsonExtension.fromJson(
jsonSerialization['account'],
),
handle: jsonSerialization['handle'] as String?,
assistant: jsonSerialization['assistant'] as String,
assistantSet: _isc.BoolJsonExtension.fromJson(
jsonSerialization['assistantSet'],
),
grantedAt: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['grantedAt'],
),
limitUnit: _il94nvme.AssistantLimitUnit.fromJson(
(jsonSerialization['limitUnit'] as String),
),
dailyLimit: jsonSerialization['dailyLimit'] as int?,
monthlyLimit: jsonSerialization['monthlyLimit'] as int?,
agreedAt: jsonSerialization['agreedAt'] == null
? null
: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['agreedAt']),
outdated: _isc.BoolJsonExtension.fromJson(jsonSerialization['outdated']),
);
}
/// The person's account.
_isc.UuidValue account;
/// Their handle; null for an account without one.
String? handle;
/// The assistant's ID.
String assistant;
/// Whether the instance still sets that assistant (D44).
bool assistantSet;
/// When it was granted, or its limits last changed.
DateTime grantedAt;
/// What its limits count.
_il94nvme.AssistantLimitUnit limitUnit;
/// The most a UTC day may use; null for none.
int? dailyLimit;
/// The most a UTC month may use; null for none.
int? monthlyLimit;
/// When the person agreed to the assistant's provider as it is now;
/// null until they have.
DateTime? agreedAt;
/// Whether the limits count what the assistant counts no more, as its
/// prices came or went.
bool outdated;
/// Returns a shallow copy of this [AssistantGrantOverview]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantGrantOverview copyWith({
_isc.UuidValue? account,
String? handle,
String? assistant,
bool? assistantSet,
DateTime? grantedAt,
_il94nvme.AssistantLimitUnit? limitUnit,
int? dailyLimit,
int? monthlyLimit,
DateTime? agreedAt,
bool? outdated,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantGrantOverview',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'assistant': assistant,
'assistantSet': assistantSet,
'grantedAt': grantedAt.toJson(),
'limitUnit': limitUnit.toJson(),
if (dailyLimit != null) 'dailyLimit': dailyLimit,
if (monthlyLimit != null) 'monthlyLimit': monthlyLimit,
if (agreedAt != null) 'agreedAt': agreedAt?.toJson(),
'outdated': outdated,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantGrantOverview',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'assistant': assistant,
'assistantSet': assistantSet,
'grantedAt': grantedAt.toJson(),
'limitUnit': limitUnit.toJson(),
if (dailyLimit != null) 'dailyLimit': dailyLimit,
if (monthlyLimit != null) 'monthlyLimit': monthlyLimit,
if (agreedAt != null) 'agreedAt': agreedAt?.toJson(),
'outdated': outdated,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantGrantOverviewImpl extends AssistantGrantOverview {
_AssistantGrantOverviewImpl({
required _isc.UuidValue account,
String? handle,
required String assistant,
required bool assistantSet,
required DateTime grantedAt,
required _il94nvme.AssistantLimitUnit limitUnit,
int? dailyLimit,
int? monthlyLimit,
DateTime? agreedAt,
required bool outdated,
}) : super._(
account: account,
handle: handle,
assistant: assistant,
assistantSet: assistantSet,
grantedAt: grantedAt,
limitUnit: limitUnit,
dailyLimit: dailyLimit,
monthlyLimit: monthlyLimit,
agreedAt: agreedAt,
outdated: outdated,
);
/// Returns a shallow copy of this [AssistantGrantOverview]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantGrantOverview copyWith({
_isc.UuidValue? account,
Object? handle = _Undefined,
String? assistant,
bool? assistantSet,
DateTime? grantedAt,
_il94nvme.AssistantLimitUnit? limitUnit,
Object? dailyLimit = _Undefined,
Object? monthlyLimit = _Undefined,
Object? agreedAt = _Undefined,
bool? outdated,
}) {
return AssistantGrantOverview(
account: account ?? this.account,
handle: handle is String? ? handle : this.handle,
assistant: assistant ?? this.assistant,
assistantSet: assistantSet ?? this.assistantSet,
grantedAt: grantedAt ?? this.grantedAt,
limitUnit: limitUnit ?? this.limitUnit,
dailyLimit: dailyLimit is int? ? dailyLimit : this.dailyLimit,
monthlyLimit: monthlyLimit is int? ? monthlyLimit : this.monthlyLimit,
agreedAt: agreedAt is DateTime? ? agreedAt : this.agreedAt,
outdated: outdated ?? this.outdated,
);
}
}

View file

@ -0,0 +1,176 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// An assistant as the operator set it (Halkyon Learn's ADR 0021, point
/// 4), without its key: what their screen of grants shows.
abstract class AssistantInfo
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantInfo._({
required this.id,
required this.name,
required this.protocol,
required this.host,
required this.model,
required this.images,
this.priceInput,
this.priceOutput,
});
factory AssistantInfo({
required String id,
required String name,
required String protocol,
required String host,
required String model,
required bool images,
double? priceInput,
double? priceOutput,
}) = _AssistantInfoImpl;
factory AssistantInfo.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantInfo(
id: jsonSerialization['id'] as String,
name: jsonSerialization['name'] as String,
protocol: jsonSerialization['protocol'] as String,
host: jsonSerialization['host'] as String,
model: jsonSerialization['model'] as String,
images: _isc.BoolJsonExtension.fromJson(jsonSerialization['images']),
priceInput: (jsonSerialization['priceInput'] as num?)?.toDouble(),
priceOutput: (jsonSerialization['priceOutput'] as num?)?.toDouble(),
);
}
/// Its ID.
String id;
/// The name people see.
String name;
/// Its protocol.
String protocol;
/// The provider's host.
String host;
/// The model.
String model;
/// Whether it reads images.
bool images;
/// Its price per million input tokens; null where it has none, so its
/// limits count tokens.
double? priceInput;
/// Its price per million output tokens.
double? priceOutput;
/// Returns a shallow copy of this [AssistantInfo]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantInfo copyWith({
String? id,
String? name,
String? protocol,
String? host,
String? model,
bool? images,
double? priceInput,
double? priceOutput,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantInfo',
'id': id,
'name': name,
'protocol': protocol,
'host': host,
'model': model,
'images': images,
if (priceInput != null) 'priceInput': priceInput,
if (priceOutput != null) 'priceOutput': priceOutput,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantInfo',
'id': id,
'name': name,
'protocol': protocol,
'host': host,
'model': model,
'images': images,
if (priceInput != null) 'priceInput': priceInput,
if (priceOutput != null) 'priceOutput': priceOutput,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantInfoImpl extends AssistantInfo {
_AssistantInfoImpl({
required String id,
required String name,
required String protocol,
required String host,
required String model,
required bool images,
double? priceInput,
double? priceOutput,
}) : super._(
id: id,
name: name,
protocol: protocol,
host: host,
model: model,
images: images,
priceInput: priceInput,
priceOutput: priceOutput,
);
/// Returns a shallow copy of this [AssistantInfo]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantInfo copyWith({
String? id,
String? name,
String? protocol,
String? host,
String? model,
bool? images,
Object? priceInput = _Undefined,
Object? priceOutput = _Undefined,
}) {
return AssistantInfo(
id: id ?? this.id,
name: name ?? this.name,
protocol: protocol ?? this.protocol,
host: host ?? this.host,
model: model ?? this.model,
images: images ?? this.images,
priceInput: priceInput is double? ? priceInput : this.priceInput,
priceOutput: priceOutput is double? ? priceOutput : this.priceOutput,
);
}
}

View file

@ -0,0 +1,42 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// What a grant's limits count (Halkyon Learn's ADR 0021, point 8).
enum AssistantLimitUnit implements _isc.SerializableModel {
/// The cost, in millionths of the instance's currency, at the prices
/// the operator set.
costMicros,
/// Input and output tokens together, where the operator set no prices.
tokens;
static AssistantLimitUnit fromJson(String name) {
switch (name) {
case 'costMicros':
return AssistantLimitUnit.costMicros;
case 'tokens':
return AssistantLimitUnit.tokens;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AssistantLimitUnit"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,101 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_part.dart' as _ijm4wwsu;
import '../assistant/assistant_role.dart' as _i5olt3ik;
/// A turn of a conversation with an assistant.
abstract class AssistantMessage
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantMessage._({
required this.role,
required this.parts,
});
factory AssistantMessage({
required _i5olt3ik.AssistantRole role,
required List<_ijm4wwsu.AssistantPart> parts,
}) = _AssistantMessageImpl;
factory AssistantMessage.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantMessage(
role: _i5olt3ik.AssistantRole.fromJson(
(jsonSerialization['role'] as String),
),
parts: _iwgbpyo5.Protocol().deserialize<List<_ijm4wwsu.AssistantPart>>(
jsonSerialization['parts'],
),
);
}
/// Who said it.
_i5olt3ik.AssistantRole role;
/// What it holds, in order.
List<_ijm4wwsu.AssistantPart> parts;
/// Returns a shallow copy of this [AssistantMessage]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantMessage copyWith({
_i5olt3ik.AssistantRole? role,
List<_ijm4wwsu.AssistantPart>? parts,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantMessage',
'role': role.toJson(),
'parts': parts.toJson(valueToJson: (v) => v.toJson()),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantMessage',
'role': role.toJson(),
'parts': parts.toJson(valueToJson: (v) => v.toJsonForProtocol()),
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _AssistantMessageImpl extends AssistantMessage {
_AssistantMessageImpl({
required _i5olt3ik.AssistantRole role,
required List<_ijm4wwsu.AssistantPart> parts,
}) : super._(
role: role,
parts: parts,
);
/// Returns a shallow copy of this [AssistantMessage]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantMessage copyWith({
_i5olt3ik.AssistantRole? role,
List<_ijm4wwsu.AssistantPart>? parts,
}) {
return AssistantMessage(
role: role ?? this.role,
parts: parts ?? this.parts.map((e0) => e0.copyWith()).toList(),
);
}
}

View file

@ -0,0 +1,181 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// What one person used of one assistant in one UTC month (Halkyon
/// Learn's ADR 0021, D3 and point 8): the operator sees everyone's, a
/// person their own; never what was asked.
abstract class AssistantMonthFigures
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantMonthFigures._({
required this.account,
this.handle,
required this.assistant,
required this.month,
required this.requests,
required this.inputTokens,
required this.outputTokens,
required this.costMicros,
});
factory AssistantMonthFigures({
required _isc.UuidValue account,
String? handle,
required String assistant,
required DateTime month,
required int requests,
required int inputTokens,
required int outputTokens,
required int costMicros,
}) = _AssistantMonthFiguresImpl;
factory AssistantMonthFigures.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return AssistantMonthFigures(
account: _isc.UuidValueJsonExtension.fromJson(
jsonSerialization['account'],
),
handle: jsonSerialization['handle'] as String?,
assistant: jsonSerialization['assistant'] as String,
month: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['month']),
requests: jsonSerialization['requests'] as int,
inputTokens: jsonSerialization['inputTokens'] as int,
outputTokens: jsonSerialization['outputTokens'] as int,
costMicros: jsonSerialization['costMicros'] as int,
);
}
/// The person's account.
_isc.UuidValue account;
/// Their handle; null for an account without one, or in a person's own.
String? handle;
/// The assistant's ID.
String assistant;
/// The month, at midnight UTC of its first day.
DateTime month;
/// The requests forwarded.
int requests;
/// The input tokens counted.
int inputTokens;
/// The output tokens counted.
int outputTokens;
/// What they cost, in millionths of the currency; 0 for an assistant
/// without prices.
int costMicros;
/// Returns a shallow copy of this [AssistantMonthFigures]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantMonthFigures copyWith({
_isc.UuidValue? account,
String? handle,
String? assistant,
DateTime? month,
int? requests,
int? inputTokens,
int? outputTokens,
int? costMicros,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantMonthFigures',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'assistant': assistant,
'month': month.toJson(),
'requests': requests,
'inputTokens': inputTokens,
'outputTokens': outputTokens,
'costMicros': costMicros,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantMonthFigures',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'assistant': assistant,
'month': month.toJson(),
'requests': requests,
'inputTokens': inputTokens,
'outputTokens': outputTokens,
'costMicros': costMicros,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantMonthFiguresImpl extends AssistantMonthFigures {
_AssistantMonthFiguresImpl({
required _isc.UuidValue account,
String? handle,
required String assistant,
required DateTime month,
required int requests,
required int inputTokens,
required int outputTokens,
required int costMicros,
}) : super._(
account: account,
handle: handle,
assistant: assistant,
month: month,
requests: requests,
inputTokens: inputTokens,
outputTokens: outputTokens,
costMicros: costMicros,
);
/// Returns a shallow copy of this [AssistantMonthFigures]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantMonthFigures copyWith({
_isc.UuidValue? account,
Object? handle = _Undefined,
String? assistant,
DateTime? month,
int? requests,
int? inputTokens,
int? outputTokens,
int? costMicros,
}) {
return AssistantMonthFigures(
account: account ?? this.account,
handle: handle is String? ? handle : this.handle,
assistant: assistant ?? this.assistant,
month: month ?? this.month,
requests: requests ?? this.requests,
inputTokens: inputTokens ?? this.inputTokens,
outputTokens: outputTokens ?? this.outputTokens,
costMicros: costMicros ?? this.costMicros,
);
}
}

View file

@ -0,0 +1,247 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_limit_unit.dart' as _il94nvme;
/// An assistant granted to the person who asks (Halkyon Learn's ADR 0021,
/// point 6 and D27): what it is, what goes where, and how much is left.
abstract class AssistantOffer
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantOffer._({
required this.id,
required this.name,
required this.protocol,
required this.host,
required this.model,
required this.images,
this.agreedAt,
required this.limitUnit,
this.dailyLimit,
this.monthlyLimit,
required this.usedToday,
required this.usedThisMonth,
required this.outdated,
});
factory AssistantOffer({
required String id,
required String name,
required String protocol,
required String host,
required String model,
required bool images,
DateTime? agreedAt,
required _il94nvme.AssistantLimitUnit limitUnit,
int? dailyLimit,
int? monthlyLimit,
required int usedToday,
required int usedThisMonth,
required bool outdated,
}) = _AssistantOfferImpl;
factory AssistantOffer.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantOffer(
id: jsonSerialization['id'] as String,
name: jsonSerialization['name'] as String,
protocol: jsonSerialization['protocol'] as String,
host: jsonSerialization['host'] as String,
model: jsonSerialization['model'] as String,
images: _isc.BoolJsonExtension.fromJson(jsonSerialization['images']),
agreedAt: jsonSerialization['agreedAt'] == null
? null
: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['agreedAt']),
limitUnit: _il94nvme.AssistantLimitUnit.fromJson(
(jsonSerialization['limitUnit'] as String),
),
dailyLimit: jsonSerialization['dailyLimit'] as int?,
monthlyLimit: jsonSerialization['monthlyLimit'] as int?,
usedToday: jsonSerialization['usedToday'] as int,
usedThisMonth: jsonSerialization['usedThisMonth'] as int,
outdated: _isc.BoolJsonExtension.fromJson(jsonSerialization['outdated']),
);
}
/// Its ID, which a question names.
String id;
/// The name people see.
String name;
/// Its protocol (`anthropic`, `openai`, `openai-responses`).
String protocol;
/// The provider's host, where what is asked goes (`api.anthropic.com`).
String host;
/// The model.
String model;
/// Whether it reads images (D12).
bool images;
/// When the person agreed; null until they have, or since it moved to
/// another provider: then they are asked first (D40).
DateTime? agreedAt;
/// What its limits count.
_il94nvme.AssistantLimitUnit limitUnit;
/// The most a UTC day may use; null for none.
int? dailyLimit;
/// The most a UTC month may use; null for none.
int? monthlyLimit;
/// What this UTC day used so far, in [limitUnit].
int usedToday;
/// What this UTC month used so far, in [limitUnit].
int usedThisMonth;
/// Whether the grant holds no more until the operator sets its limits
/// again, as the assistant's prices came or went.
bool outdated;
/// Returns a shallow copy of this [AssistantOffer]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantOffer copyWith({
String? id,
String? name,
String? protocol,
String? host,
String? model,
bool? images,
DateTime? agreedAt,
_il94nvme.AssistantLimitUnit? limitUnit,
int? dailyLimit,
int? monthlyLimit,
int? usedToday,
int? usedThisMonth,
bool? outdated,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantOffer',
'id': id,
'name': name,
'protocol': protocol,
'host': host,
'model': model,
'images': images,
if (agreedAt != null) 'agreedAt': agreedAt?.toJson(),
'limitUnit': limitUnit.toJson(),
if (dailyLimit != null) 'dailyLimit': dailyLimit,
if (monthlyLimit != null) 'monthlyLimit': monthlyLimit,
'usedToday': usedToday,
'usedThisMonth': usedThisMonth,
'outdated': outdated,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantOffer',
'id': id,
'name': name,
'protocol': protocol,
'host': host,
'model': model,
'images': images,
if (agreedAt != null) 'agreedAt': agreedAt?.toJson(),
'limitUnit': limitUnit.toJson(),
if (dailyLimit != null) 'dailyLimit': dailyLimit,
if (monthlyLimit != null) 'monthlyLimit': monthlyLimit,
'usedToday': usedToday,
'usedThisMonth': usedThisMonth,
'outdated': outdated,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantOfferImpl extends AssistantOffer {
_AssistantOfferImpl({
required String id,
required String name,
required String protocol,
required String host,
required String model,
required bool images,
DateTime? agreedAt,
required _il94nvme.AssistantLimitUnit limitUnit,
int? dailyLimit,
int? monthlyLimit,
required int usedToday,
required int usedThisMonth,
required bool outdated,
}) : super._(
id: id,
name: name,
protocol: protocol,
host: host,
model: model,
images: images,
agreedAt: agreedAt,
limitUnit: limitUnit,
dailyLimit: dailyLimit,
monthlyLimit: monthlyLimit,
usedToday: usedToday,
usedThisMonth: usedThisMonth,
outdated: outdated,
);
/// Returns a shallow copy of this [AssistantOffer]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantOffer copyWith({
String? id,
String? name,
String? protocol,
String? host,
String? model,
bool? images,
Object? agreedAt = _Undefined,
_il94nvme.AssistantLimitUnit? limitUnit,
Object? dailyLimit = _Undefined,
Object? monthlyLimit = _Undefined,
int? usedToday,
int? usedThisMonth,
bool? outdated,
}) {
return AssistantOffer(
id: id ?? this.id,
name: name ?? this.name,
protocol: protocol ?? this.protocol,
host: host ?? this.host,
model: model ?? this.model,
images: images ?? this.images,
agreedAt: agreedAt is DateTime? ? agreedAt : this.agreedAt,
limitUnit: limitUnit ?? this.limitUnit,
dailyLimit: dailyLimit is int? ? dailyLimit : this.dailyLimit,
monthlyLimit: monthlyLimit is int? ? monthlyLimit : this.monthlyLimit,
usedToday: usedToday ?? this.usedToday,
usedThisMonth: usedThisMonth ?? this.usedThisMonth,
outdated: outdated ?? this.outdated,
);
}
}

View file

@ -0,0 +1,209 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:typed_data' as _idt;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_part_kind.dart' as _i3armmew;
/// A part of a turn: one class for every kind, so a turn is a plain list.
abstract class AssistantPart
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantPart._({
required this.kind,
this.text,
this.mediaType,
this.bytes,
this.callId,
this.name,
this.json,
this.extra,
this.isError,
this.protocol,
});
factory AssistantPart({
required _i3armmew.AssistantPartKind kind,
String? text,
String? mediaType,
_idt.ByteData? bytes,
String? callId,
String? name,
String? json,
String? extra,
bool? isError,
String? protocol,
}) = _AssistantPartImpl;
factory AssistantPart.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantPart(
kind: _i3armmew.AssistantPartKind.fromJson(
(jsonSerialization['kind'] as String),
),
text: jsonSerialization['text'] as String?,
mediaType: jsonSerialization['mediaType'] as String?,
bytes: jsonSerialization['bytes'] == null
? null
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['bytes']),
callId: jsonSerialization['callId'] as String?,
name: jsonSerialization['name'] as String?,
json: jsonSerialization['json'] as String?,
extra: jsonSerialization['extra'] as String?,
isError: jsonSerialization['isError'] == null
? null
: _isc.BoolJsonExtension.fromJson(jsonSerialization['isError']),
protocol: jsonSerialization['protocol'] as String?,
);
}
/// What it is, and so which fields it has.
_i3armmew.AssistantPartKind kind;
/// A text's text, or a tool's result.
String? text;
/// An image's type (`image/png`).
String? mediaType;
/// An image's bytes.
_idt.ByteData? bytes;
/// A tool call's ID, or the call a result answers.
String? callId;
/// A tool call's tool.
String? name;
/// A tool call's input, or a provider's block, as JSON.
String? json;
/// What the provider wants back with a text of the model's or a tool
/// call, as JSON; null for nothing.
String? extra;
/// Whether a tool's result says it failed.
bool? isError;
/// A provider's block's protocol.
String? protocol;
/// Returns a shallow copy of this [AssistantPart]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantPart copyWith({
_i3armmew.AssistantPartKind? kind,
String? text,
String? mediaType,
_idt.ByteData? bytes,
String? callId,
String? name,
String? json,
String? extra,
bool? isError,
String? protocol,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantPart',
'kind': kind.toJson(),
if (text != null) 'text': text,
if (mediaType != null) 'mediaType': mediaType,
if (bytes != null) 'bytes': bytes?.toJson(),
if (callId != null) 'callId': callId,
if (name != null) 'name': name,
if (json != null) 'json': json,
if (extra != null) 'extra': extra,
if (isError != null) 'isError': isError,
if (protocol != null) 'protocol': protocol,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantPart',
'kind': kind.toJson(),
if (text != null) 'text': text,
if (mediaType != null) 'mediaType': mediaType,
if (bytes != null) 'bytes': bytes?.toJson(),
if (callId != null) 'callId': callId,
if (name != null) 'name': name,
if (json != null) 'json': json,
if (extra != null) 'extra': extra,
if (isError != null) 'isError': isError,
if (protocol != null) 'protocol': protocol,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AssistantPartImpl extends AssistantPart {
_AssistantPartImpl({
required _i3armmew.AssistantPartKind kind,
String? text,
String? mediaType,
_idt.ByteData? bytes,
String? callId,
String? name,
String? json,
String? extra,
bool? isError,
String? protocol,
}) : super._(
kind: kind,
text: text,
mediaType: mediaType,
bytes: bytes,
callId: callId,
name: name,
json: json,
extra: extra,
isError: isError,
protocol: protocol,
);
/// Returns a shallow copy of this [AssistantPart]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantPart copyWith({
_i3armmew.AssistantPartKind? kind,
Object? text = _Undefined,
Object? mediaType = _Undefined,
Object? bytes = _Undefined,
Object? callId = _Undefined,
Object? name = _Undefined,
Object? json = _Undefined,
Object? extra = _Undefined,
Object? isError = _Undefined,
Object? protocol = _Undefined,
}) {
return AssistantPart(
kind: kind ?? this.kind,
text: text is String? ? text : this.text,
mediaType: mediaType is String? ? mediaType : this.mediaType,
bytes: bytes is _idt.ByteData? ? bytes : this.bytes?.clone(),
callId: callId is String? ? callId : this.callId,
name: name is String? ? name : this.name,
json: json is String? ? json : this.json,
extra: extra is String? ? extra : this.extra,
isError: isError is bool? ? isError : this.isError,
protocol: protocol is String? ? protocol : this.protocol,
);
}
}

View file

@ -0,0 +1,61 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// What a part of a turn is.
enum AssistantPartKind implements _isc.SerializableModel {
/// Text ([AssistantPart.text]; in the model's turn, what the provider
/// wants back with it in [AssistantPart.extra]).
text,
/// An image ([AssistantPart.mediaType], [AssistantPart.bytes]).
image,
/// A call of a tool ([AssistantPart.callId], [AssistantPart.name], its
/// input as JSON in [AssistantPart.json], what the provider wants back
/// with it in [AssistantPart.extra]).
toolCall,
/// A tool's result ([AssistantPart.callId], [AssistantPart.text],
/// [AssistantPart.isError]).
toolResult,
/// A block only the provider reads ([AssistantPart.protocol], the block
/// as JSON in [AssistantPart.json]), sent back to it unchanged.
providerBlock;
static AssistantPartKind fromJson(String name) {
switch (name) {
case 'text':
return AssistantPartKind.text;
case 'image':
return AssistantPartKind.image;
case 'toolCall':
return AssistantPartKind.toolCall;
case 'toolResult':
return AssistantPartKind.toolResult;
case 'providerBlock':
return AssistantPartKind.providerBlock;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AssistantPartKind"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,143 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_message.dart' as _irryv85c;
import '../assistant/assistant_tool.dart' as _ienotap4;
/// What the app asks an operator's assistant (Halkyon Learn's ADR 0021,
/// point 7): the whole conversation each time, as the instance keeps none.
abstract class AssistantQuestion
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantQuestion._({
required this.assistant,
required this.system,
required this.messages,
required this.tools,
required this.maxOutputTokens,
});
factory AssistantQuestion({
required String assistant,
required String system,
required List<_irryv85c.AssistantMessage> messages,
required List<_ienotap4.AssistantTool> tools,
required int maxOutputTokens,
}) = _AssistantQuestionImpl;
factory AssistantQuestion.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantQuestion(
assistant: jsonSerialization['assistant'] as String,
system: jsonSerialization['system'] as String,
messages: _iwgbpyo5.Protocol()
.deserialize<List<_irryv85c.AssistantMessage>>(
jsonSerialization['messages'],
),
tools: _iwgbpyo5.Protocol().deserialize<List<_ienotap4.AssistantTool>>(
jsonSerialization['tools'],
),
maxOutputTokens: jsonSerialization['maxOutputTokens'] as int,
);
}
/// The assistant's ID.
String assistant;
/// What the model is told first; empty for nothing.
String system;
/// The conversation, ending with the person's turn.
List<_irryv85c.AssistantMessage> messages;
/// The tools it may call.
List<_ienotap4.AssistantTool> tools;
/// The ceiling on the answer's tokens, the instance's own ceiling at
/// most.
int maxOutputTokens;
/// Returns a shallow copy of this [AssistantQuestion]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantQuestion copyWith({
String? assistant,
String? system,
List<_irryv85c.AssistantMessage>? messages,
List<_ienotap4.AssistantTool>? tools,
int? maxOutputTokens,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantQuestion',
'assistant': assistant,
'system': system,
'messages': messages.toJson(valueToJson: (v) => v.toJson()),
'tools': tools.toJson(valueToJson: (v) => v.toJson()),
'maxOutputTokens': maxOutputTokens,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantQuestion',
'assistant': assistant,
'system': system,
'messages': messages.toJson(valueToJson: (v) => v.toJsonForProtocol()),
'tools': tools.toJson(valueToJson: (v) => v.toJsonForProtocol()),
'maxOutputTokens': maxOutputTokens,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _AssistantQuestionImpl extends AssistantQuestion {
_AssistantQuestionImpl({
required String assistant,
required String system,
required List<_irryv85c.AssistantMessage> messages,
required List<_ienotap4.AssistantTool> tools,
required int maxOutputTokens,
}) : super._(
assistant: assistant,
system: system,
messages: messages,
tools: tools,
maxOutputTokens: maxOutputTokens,
);
/// Returns a shallow copy of this [AssistantQuestion]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantQuestion copyWith({
String? assistant,
String? system,
List<_irryv85c.AssistantMessage>? messages,
List<_ienotap4.AssistantTool>? tools,
int? maxOutputTokens,
}) {
return AssistantQuestion(
assistant: assistant ?? this.assistant,
system: system ?? this.system,
messages: messages ?? this.messages.map((e0) => e0.copyWith()).toList(),
tools: tools ?? this.tools.map((e0) => e0.copyWith()).toList(),
maxOutputTokens: maxOutputTokens ?? this.maxOutputTokens,
);
}
}

View file

@ -0,0 +1,98 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Why an assistant refused, or failed (Halkyon Learn's ADR 0021).
enum AssistantRefusal implements _isc.SerializableModel {
/// The person has no grant of it, or the instance doesn't set it (any
/// more), or this service offers no assistant (D43).
notGranted,
/// The person hasn't agreed to what goes where, or it moved to another
/// provider since (D40).
notAgreed,
/// A day's or a month's limit is reached;
/// [AssistantRefusedException.resetsAt] says when it resets.
limitReached,
/// The grant's limits count what the assistant counts no more, as its
/// prices came or went: the operator sets them again.
limitOutdated,
/// The question holds an image, and the assistant reads none.
imagesNotRead,
/// The question can't be sent: a turn out of place, or a tool call's
/// JSON unreadable.
malformed,
/// The question is larger than the instance forwards: its text, or an
/// image.
tooLarge,
/// The person has as many questions on their way as they may at once.
tooManyAtOnce,
/// The provider refuses the instance's key or its account.
providerKey,
/// The provider is busy or overloaded for now;
/// [AssistantRefusedException.retryAfterSeconds] may say for how long.
providerBusy,
/// The provider refused the question: too long for the model, say.
providerRefused,
/// The provider failed, couldn't be reached, or answered unreadably.
providerFailed;
static AssistantRefusal fromJson(String name) {
switch (name) {
case 'notGranted':
return AssistantRefusal.notGranted;
case 'notAgreed':
return AssistantRefusal.notAgreed;
case 'limitReached':
return AssistantRefusal.limitReached;
case 'limitOutdated':
return AssistantRefusal.limitOutdated;
case 'imagesNotRead':
return AssistantRefusal.imagesNotRead;
case 'malformed':
return AssistantRefusal.malformed;
case 'tooLarge':
return AssistantRefusal.tooLarge;
case 'tooManyAtOnce':
return AssistantRefusal.tooManyAtOnce;
case 'providerKey':
return AssistantRefusal.providerKey;
case 'providerBusy':
return AssistantRefusal.providerBusy;
case 'providerRefused':
return AssistantRefusal.providerRefused;
case 'providerFailed':
return AssistantRefusal.providerFailed;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AssistantRefusal"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,122 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_refusal.dart' as _ilkzkcy6;
/// An assistant refused a question, or failed on it.
abstract class AssistantRefusedException
implements
_isc.SerializableException,
_isc.SerializableModel,
_isc.ProtocolSerialization {
AssistantRefusedException._({
required this.refusal,
this.resetsAt,
this.retryAfterSeconds,
});
factory AssistantRefusedException({
required _ilkzkcy6.AssistantRefusal refusal,
DateTime? resetsAt,
int? retryAfterSeconds,
}) = _AssistantRefusedExceptionImpl;
factory AssistantRefusedException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return AssistantRefusedException(
refusal: _ilkzkcy6.AssistantRefusal.fromJson(
(jsonSerialization['refusal'] as String),
),
resetsAt: jsonSerialization['resetsAt'] == null
? null
: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['resetsAt']),
retryAfterSeconds: jsonSerialization['retryAfterSeconds'] as int?,
);
}
/// Why.
_ilkzkcy6.AssistantRefusal refusal;
/// When the limit reached resets (UTC midnight, or the first of next
/// month); only for [AssistantRefusal.limitReached].
DateTime? resetsAt;
/// How long the provider asks to wait, in seconds, where it says.
int? retryAfterSeconds;
/// Returns a shallow copy of this [AssistantRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantRefusedException copyWith({
_ilkzkcy6.AssistantRefusal? refusal,
DateTime? resetsAt,
int? retryAfterSeconds,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantRefusedException',
'refusal': refusal.toJson(),
if (resetsAt != null) 'resetsAt': resetsAt?.toJson(),
if (retryAfterSeconds != null) 'retryAfterSeconds': retryAfterSeconds,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantRefusedException',
'refusal': refusal.toJson(),
if (resetsAt != null) 'resetsAt': resetsAt?.toJson(),
if (retryAfterSeconds != null) 'retryAfterSeconds': retryAfterSeconds,
};
}
@override
String toString() {
return 'AssistantRefusedException(refusal: $refusal, resetsAt: $resetsAt, retryAfterSeconds: $retryAfterSeconds)';
}
}
class _Undefined {}
class _AssistantRefusedExceptionImpl extends AssistantRefusedException {
_AssistantRefusedExceptionImpl({
required _ilkzkcy6.AssistantRefusal refusal,
DateTime? resetsAt,
int? retryAfterSeconds,
}) : super._(
refusal: refusal,
resetsAt: resetsAt,
retryAfterSeconds: retryAfterSeconds,
);
/// Returns a shallow copy of this [AssistantRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantRefusedException copyWith({
_ilkzkcy6.AssistantRefusal? refusal,
Object? resetsAt = _Undefined,
Object? retryAfterSeconds = _Undefined,
}) {
return AssistantRefusedException(
refusal: refusal ?? this.refusal,
resetsAt: resetsAt is DateTime? ? resetsAt : this.resetsAt,
retryAfterSeconds: retryAfterSeconds is int?
? retryAfterSeconds
: this.retryAfterSeconds,
);
}
}

View file

@ -0,0 +1,41 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Who said a turn of a conversation with an assistant.
enum AssistantRole implements _isc.SerializableModel {
/// The person, with the tools' results their app adds.
user,
/// The model.
assistant;
static AssistantRole fromJson(String name) {
switch (name) {
case 'user':
return AssistantRole.user;
case 'assistant':
return AssistantRole.assistant;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AssistantRole"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,56 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Why the model stopped.
enum AssistantStop implements _isc.SerializableModel {
/// It finished its answer.
end,
/// It reached the ceiling on tokens, or the model's context.
maxTokens,
/// It calls tools, whose results the app sends in the next question.
toolCalls,
/// It declined, or the provider's filter stopped it.
refused,
/// Something else.
other;
static AssistantStop fromJson(String name) {
switch (name) {
case 'end':
return AssistantStop.end;
case 'maxTokens':
return AssistantStop.maxTokens;
case 'toolCalls':
return AssistantStop.toolCalls;
case 'refused':
return AssistantStop.refused;
case 'other':
return AssistantStop.other;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AssistantStop"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,108 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// A tool the model may call, which the app runs (Halkyon Learn's ADR
/// 0021, point 7).
abstract class AssistantTool
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantTool._({
required this.name,
required this.description,
required this.inputSchema,
});
factory AssistantTool({
required String name,
required String description,
required String inputSchema,
}) = _AssistantToolImpl;
factory AssistantTool.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantTool(
name: jsonSerialization['name'] as String,
description: jsonSerialization['description'] as String,
inputSchema: jsonSerialization['inputSchema'] as String,
);
}
/// Its name.
String name;
/// What it does, for the model.
String description;
/// Its input, as a JSON Schema of an object, as JSON.
String inputSchema;
/// Returns a shallow copy of this [AssistantTool]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantTool copyWith({
String? name,
String? description,
String? inputSchema,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantTool',
'name': name,
'description': description,
'inputSchema': inputSchema,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantTool',
'name': name,
'description': description,
'inputSchema': inputSchema,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _AssistantToolImpl extends AssistantTool {
_AssistantToolImpl({
required String name,
required String description,
required String inputSchema,
}) : super._(
name: name,
description: description,
inputSchema: inputSchema,
);
/// Returns a shallow copy of this [AssistantTool]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantTool copyWith({
String? name,
String? description,
String? inputSchema,
}) {
return AssistantTool(
name: name ?? this.name,
description: description ?? this.description,
inputSchema: inputSchema ?? this.inputSchema,
);
}
}

View file

@ -0,0 +1,117 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_offer.dart' as _ify4died;
/// What an instance offers the person who asks, of assistants (Halkyon
/// Learn's ADR 0021): those granted to them, and whether they may add their
/// own keys.
abstract class AssistantsOffered
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantsOffered._({
required this.assistants,
required this.ownKeys,
required this.currency,
});
factory AssistantsOffered({
required List<_ify4died.AssistantOffer> assistants,
required bool ownKeys,
required String currency,
}) = _AssistantsOfferedImpl;
factory AssistantsOffered.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantsOffered(
assistants: _iwgbpyo5.Protocol()
.deserialize<List<_ify4died.AssistantOffer>>(
jsonSerialization['assistants'],
),
ownKeys: _isc.BoolJsonExtension.fromJson(jsonSerialization['ownKeys']),
currency: jsonSerialization['currency'] as String,
);
}
/// The assistants granted to them that are set, in the operator's order.
List<_ify4died.AssistantOffer> assistants;
/// Whether they may add their own keys here (D5, D6).
bool ownKeys;
/// The currency costs are in (ISO 4217).
String currency;
/// Returns a shallow copy of this [AssistantsOffered]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantsOffered copyWith({
List<_ify4died.AssistantOffer>? assistants,
bool? ownKeys,
String? currency,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantsOffered',
'assistants': assistants.toJson(valueToJson: (v) => v.toJson()),
'ownKeys': ownKeys,
'currency': currency,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantsOffered',
'assistants': assistants.toJson(
valueToJson: (v) => v.toJsonForProtocol(),
),
'ownKeys': ownKeys,
'currency': currency,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _AssistantsOfferedImpl extends AssistantsOffered {
_AssistantsOfferedImpl({
required List<_ify4died.AssistantOffer> assistants,
required bool ownKeys,
required String currency,
}) : super._(
assistants: assistants,
ownKeys: ownKeys,
currency: currency,
);
/// Returns a shallow copy of this [AssistantsOffered]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantsOffered copyWith({
List<_ify4died.AssistantOffer>? assistants,
bool? ownKeys,
String? currency,
}) {
return AssistantsOffered(
assistants:
assistants ?? this.assistants.map((e0) => e0.copyWith()).toList(),
ownKeys: ownKeys ?? this.ownKeys,
currency: currency ?? this.currency,
);
}
}

View file

@ -0,0 +1,116 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../assistant/assistant_info.dart' as _iuj14wfe;
/// The assistants an instance's operator set (Halkyon Learn's ADR 0021):
/// what they grant from.
abstract class AssistantsSetUp
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AssistantsSetUp._({
required this.assistants,
required this.currency,
required this.ownKeys,
});
factory AssistantsSetUp({
required List<_iuj14wfe.AssistantInfo> assistants,
required String currency,
required bool ownKeys,
}) = _AssistantsSetUpImpl;
factory AssistantsSetUp.fromJson(Map<String, dynamic> jsonSerialization) {
return AssistantsSetUp(
assistants: _iwgbpyo5.Protocol()
.deserialize<List<_iuj14wfe.AssistantInfo>>(
jsonSerialization['assistants'],
),
currency: jsonSerialization['currency'] as String,
ownKeys: _isc.BoolJsonExtension.fromJson(jsonSerialization['ownKeys']),
);
}
/// Each assistant, in the order set.
List<_iuj14wfe.AssistantInfo> assistants;
/// The currency of the prices and costs (ISO 4217).
String currency;
/// Whether people may add their own keys.
bool ownKeys;
/// Returns a shallow copy of this [AssistantsSetUp]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AssistantsSetUp copyWith({
List<_iuj14wfe.AssistantInfo>? assistants,
String? currency,
bool? ownKeys,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AssistantsSetUp',
'assistants': assistants.toJson(valueToJson: (v) => v.toJson()),
'currency': currency,
'ownKeys': ownKeys,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AssistantsSetUp',
'assistants': assistants.toJson(
valueToJson: (v) => v.toJsonForProtocol(),
),
'currency': currency,
'ownKeys': ownKeys,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _AssistantsSetUpImpl extends AssistantsSetUp {
_AssistantsSetUpImpl({
required List<_iuj14wfe.AssistantInfo> assistants,
required String currency,
required bool ownKeys,
}) : super._(
assistants: assistants,
currency: currency,
ownKeys: ownKeys,
);
/// Returns a shallow copy of this [AssistantsSetUp]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AssistantsSetUp copyWith({
List<_iuj14wfe.AssistantInfo>? assistants,
String? currency,
bool? ownKeys,
}) {
return AssistantsSetUp(
assistants:
assistants ?? this.assistants.map((e0) => e0.copyWith()).toList(),
currency: currency ?? this.currency,
ownKeys: ownKeys ?? this.ownKeys,
);
}
}

View file

@ -14,6 +14,18 @@ import 'dart:async' as _ida;
import 'dart:typed_data' as _idt;
import 'package:halkyon_base_client/src/protocol/admission/waiting_state.dart'
as _i1sx1z35;
import 'package:halkyon_base_client/src/protocol/assistant/assistant_answer.dart'
as _il9lgl6t;
import 'package:halkyon_base_client/src/protocol/assistant/assistant_grant_overview.dart'
as _iddnouvb;
import 'package:halkyon_base_client/src/protocol/assistant/assistant_month_figures.dart'
as _igllc81w;
import 'package:halkyon_base_client/src/protocol/assistant/assistant_question.dart'
as _imspm6kl;
import 'package:halkyon_base_client/src/protocol/assistant/assistants_offered.dart'
as _ig7pm61n;
import 'package:halkyon_base_client/src/protocol/assistant/assistants_set_up.dart'
as _ie204iej;
import 'package:halkyon_base_client/src/protocol/auth/email_code_purpose.dart'
as _i6rtanuw;
import 'package:halkyon_base_client/src/protocol/auth/sign_in_ways.dart'
@ -109,6 +121,140 @@ class EndpointWaiting extends _isc.EndpointRef {
);
}
/// The operator's assistants as a person uses them (Halkyon Learn's ADR
/// 0021, points 6 to 9): those granted to them, their agreement, their
/// questions with the answers streamed (D35), and their own figures.
/// Refusals are `AssistantRefusedException`s.
/// {@category Endpoint}
class EndpointAssistant extends _isc.EndpointRef {
EndpointAssistant(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.assistant';
/// The assistants granted to the caller that are set, with what goes
/// where and what is left of their limits, and whether they may add their
/// own keys; none on a member (D43).
_ida.Future<_ig7pm61n.AssistantsOffered> offered() =>
caller.callServerEndpoint<_ig7pm61n.AssistantsOffered>(
'halkyon_base.assistant',
'offered',
{},
);
/// The caller agrees to what goes where with [assistant], its provider as
/// it is now (D40); refused without a grant of it
/// (`AssistantRefusal.notGranted`).
_ida.Future<void> agree({required String assistant}) =>
caller.callServerEndpoint<void>(
'halkyon_base.assistant',
'agree',
{'assistant': assistant},
);
/// Forwards [question] to its assistant and streams the answer: the text
/// as the model writes it, then the whole turn with what was counted.
/// Refused before anything goes without a grant, an agreement or room
/// within the limits, for a question that can't be sent, and with more
/// than `maxQuestionsAtOnce` on their way; then for the provider's
/// failure. Cancelled, the answer is still read to its end and counted.
_ida.Stream<_il9lgl6t.AssistantAnswer> ask({
required _imspm6kl.AssistantQuestion question,
}) =>
caller.callStreamingServerEndpoint<
_ida.Stream<_il9lgl6t.AssistantAnswer>,
_il9lgl6t.AssistantAnswer
>(
'halkyon_base.assistant',
'ask',
{'question': question},
{},
);
/// The caller's own figures, month by month, the latest first.
_ida.Future<List<_igllc81w.AssistantMonthFigures>> figures() =>
caller.callServerEndpoint<List<_igllc81w.AssistantMonthFigures>>(
'halkyon_base.assistant',
'figures',
{},
);
}
/// The operator's assistants as the operator grants them (Halkyon Learn's
/// ADR 0021, points 6 and 8): the assistants set, the grants with their
/// limits, and everyone's figures, never what was asked. The operator's
/// scope only the register's sessions carry. Refusals are
/// `OperatorRefusedException`s.
/// {@category Endpoint}
class EndpointAssistantOperator extends _isc.EndpointRef {
EndpointAssistantOperator(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.assistantOperator';
/// The assistants set in `deploy/assistants.env`, without their keys.
_ida.Future<_ie204iej.AssistantsSetUp> setUp() =>
caller.callServerEndpoint<_ie204iej.AssistantsSetUp>(
'halkyon_base.assistantOperator',
'setUp',
{},
);
/// Every grant, by handle.
_ida.Future<List<_iddnouvb.AssistantGrantOverview>> grants() =>
caller.callServerEndpoint<List<_iddnouvb.AssistantGrantOverview>>(
'halkyon_base.assistantOperator',
'grants',
{},
);
/// Grants [account] [assistant] with [dailyLimit] and [monthlyLimit] (null:
/// none), in the unit of its pricing; granted already, the limits change
/// and the agreement stays. Refused for an assistant not set
/// (`OperatorRefusal.assistantNotSet`), a limit below 0
/// (`OperatorRefusal.invalidLimit`) and an account unknown
/// (`OperatorRefusal.notFound`).
_ida.Future<void> grant({
required _isc.UuidValue account,
required String assistant,
required int? dailyLimit,
required int? monthlyLimit,
}) => caller.callServerEndpoint<void>(
'halkyon_base.assistantOperator',
'grant',
{
'account': account,
'assistant': assistant,
'dailyLimit': dailyLimit,
'monthlyLimit': monthlyLimit,
},
);
/// Takes [assistant] back from [account]; refused where it isn't granted
/// (`OperatorRefusal.notFound`).
_ida.Future<void> revoke({
required _isc.UuidValue account,
required String assistant,
}) => caller.callServerEndpoint<void>(
'halkyon_base.assistantOperator',
'revoke',
{
'account': account,
'assistant': assistant,
},
);
/// Everyone's figures of the UTC month [month] is in, per person and
/// assistant.
_ida.Future<List<_igllc81w.AssistantMonthFigures>> figures({
required DateTime month,
}) => caller.callServerEndpoint<List<_igllc81w.AssistantMonthFigures>>(
'halkyon_base.assistantOperator',
'figures',
{'month': month},
);
}
/// The signed-in person's account.
/// {@category Endpoint}
class EndpointAccount extends _isc.EndpointRef {
@ -207,8 +353,9 @@ class EndpointInstance extends _isc.EndpointRef {
/// ADR 0016), password sign-in where the register holds its keys,
/// SimpleLogin where the register has the instance's SimpleLogin app
/// (ADR 0020), each as its operator offers it, who may make an account
/// (Halkyon Learn's ADR 0021, the register's operator's word), where its
/// source is, the oldest app build it works with, and where it keeps
/// (Halkyon Learn's ADR 0021, the register's operator's word), whether
/// people may add assistants of their own keys (none on a member, D43),
/// where its source is, the oldest app build it works with, and where it keeps
/// people's data and its backups.
_ida.Future<_isnyur2i.InstanceBasics> basics() =>
caller.callServerEndpoint<_isnyur2i.InstanceBasics>(
@ -1195,6 +1342,8 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
class Caller extends _isc.ModuleEndpointCaller {
Caller(_isc.ServerpodClientShared client) : super(client) {
waiting = EndpointWaiting(this);
assistant = EndpointAssistant(this);
assistantOperator = EndpointAssistantOperator(this);
account = EndpointAccount(this);
emailCode = EndpointEmailCode(this);
instance = EndpointInstance(this);
@ -1211,6 +1360,10 @@ class Caller extends _isc.ModuleEndpointCaller {
late final EndpointWaiting waiting;
late final EndpointAssistant assistant;
late final EndpointAssistantOperator assistantOperator;
late final EndpointAccount account;
late final EndpointEmailCode emailCode;
@ -1238,6 +1391,8 @@ class Caller extends _isc.ModuleEndpointCaller {
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {
'halkyon_base.waiting': waiting,
'halkyon_base.assistant': assistant,
'halkyon_base.assistantOperator': assistantOperator,
'halkyon_base.account': account,
'halkyon_base.emailCode': emailCode,
'halkyon_base.instance': instance,

View file

@ -29,9 +29,11 @@ abstract class InstanceBasics
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
bool? assistantOwnKeys,
}) : passwordSignIn = passwordSignIn ?? false,
simpleLogin = simpleLogin ?? false,
signUp = signUp ?? _iypagcol.SignUpMode.open;
signUp = signUp ?? _iypagcol.SignUpMode.open,
assistantOwnKeys = assistantOwnKeys ?? false;
factory InstanceBasics({
required String app,
@ -43,6 +45,7 @@ abstract class InstanceBasics
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
bool? assistantOwnKeys,
}) = _InstanceBasicsImpl;
factory InstanceBasics.fromJson(Map<String, dynamic> jsonSerialization) {
@ -68,6 +71,11 @@ abstract class InstanceBasics
: _iypagcol.SignUpMode.fromJson(
(jsonSerialization['signUp'] as String),
),
assistantOwnKeys: jsonSerialization['assistantOwnKeys'] == null
? null
: _isc.BoolJsonExtension.fromJson(
jsonSerialization['assistantOwnKeys'],
),
);
}
@ -110,6 +118,12 @@ abstract class InstanceBasics
/// nothing.
_iypagcol.SignUpMode signUp;
/// Whether people may add assistants of their own, with their own keys,
/// which the app then calls directly (Halkyon Learn's ADR 0021, D5 and
/// D6): the operator's word (ASSISTANT_OWN_KEYS); false on a member
/// service, which offers no assistant yet (D43).
bool assistantOwnKeys;
/// Returns a shallow copy of this [InstanceBasics]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -123,6 +137,7 @@ abstract class InstanceBasics
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
bool? assistantOwnKeys,
});
@override
Map<String, dynamic> toJson() {
@ -137,6 +152,7 @@ abstract class InstanceBasics
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
'signUp': signUp.toJson(),
'assistantOwnKeys': assistantOwnKeys,
};
}
@ -153,6 +169,7 @@ abstract class InstanceBasics
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
'signUp': signUp.toJson(),
'assistantOwnKeys': assistantOwnKeys,
};
}
@ -175,6 +192,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
bool? assistantOwnKeys,
}) : super._(
app: app,
emailSignIn: emailSignIn,
@ -185,6 +203,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
passwordSignIn: passwordSignIn,
simpleLogin: simpleLogin,
signUp: signUp,
assistantOwnKeys: assistantOwnKeys,
);
/// Returns a shallow copy of this [InstanceBasics]
@ -201,6 +220,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
bool? assistantOwnKeys,
}) {
return InstanceBasics(
app: app ?? this.app,
@ -214,6 +234,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
passwordSignIn: passwordSignIn ?? this.passwordSignIn,
simpleLogin: simpleLogin ?? this.simpleLogin,
signUp: signUp ?? this.signUp,
assistantOwnKeys: assistantOwnKeys ?? this.assistantOwnKeys,
);
}
}

View file

@ -23,7 +23,14 @@ enum OperatorRefusal implements _isc.SerializableModel {
isOperator,
/// The last operator keeps the role.
lastOperator;
lastOperator,
/// No assistant of that ID is set (`deploy/assistants.env`), so none is
/// granted (Halkyon Learn's ADR 0021, D44).
assistantNotSet,
/// A limit below 0.
invalidLimit;
static OperatorRefusal fromJson(String name) {
switch (name) {
@ -33,6 +40,10 @@ enum OperatorRefusal implements _isc.SerializableModel {
return OperatorRefusal.isOperator;
case 'lastOperator':
return OperatorRefusal.lastOperator;
case 'assistantNotSet':
return OperatorRefusal.assistantNotSet;
case 'invalidLimit':
return OperatorRefusal.invalidLimit;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "OperatorRefusal"',

Some files were not shown because too many files have changed in this diff Show more