@ancatag/nova-proto 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
- package/src/engine.proto +431 -0
- package/src/nova-service.proto +472 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,QAAwC,CAAC;AACxE,eAAO,MAAM,YAAY,QAAkC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
4
|
+
export const NOVA_SERVICE_PROTO = join(__dirname, "nova-service.proto");
|
|
5
|
+
export const ENGINE_PROTO = join(__dirname, "engine.proto");
|
|
6
|
+
// export * from "./generated/nova-service";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;AACxE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;AAC5D,4CAA4C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ancatag/nova-proto",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src/**/*.proto"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"generate": "tsx ./scripts/generate.ts",
|
|
15
|
+
"prepublishOnly": "pnpm build"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.3.0",
|
|
19
|
+
"ts-node": "^10.9.2",
|
|
20
|
+
"ts-proto": "^2.11.2",
|
|
21
|
+
"tsx": "^4.21.0",
|
|
22
|
+
"typescript": "^5.9.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@grpc/proto-loader": "^0.8.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/engine.proto
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package engine;
|
|
4
|
+
|
|
5
|
+
// ===================================================================
|
|
6
|
+
// NOVA ENGINE SERVICE
|
|
7
|
+
// ===================================================================
|
|
8
|
+
// Multi-modal AI generation service with intelligent model selection
|
|
9
|
+
|
|
10
|
+
service EngineService {
|
|
11
|
+
// Model Selection
|
|
12
|
+
rpc SelectModel(SelectModelRequest) returns (SelectModelResponse);
|
|
13
|
+
rpc GetAvailableModels(GetModelsRequest) returns (GetModelsResponse);
|
|
14
|
+
rpc GetModelPresets(GetPresetsRequest) returns (GetPresetsResponse);
|
|
15
|
+
|
|
16
|
+
// Generation - Text
|
|
17
|
+
rpc GenerateText(GenerateTextRequest) returns (stream GenerateTextResponse);
|
|
18
|
+
|
|
19
|
+
// Generation - Image (Future)
|
|
20
|
+
rpc GenerateImage(GenerateImageRequest) returns (GenerateImageResponse);
|
|
21
|
+
|
|
22
|
+
// Generation - Video (Future)
|
|
23
|
+
rpc GenerateVideo(GenerateVideoRequest) returns (GenerateVideoResponse);
|
|
24
|
+
|
|
25
|
+
// Generation - Audio (Future)
|
|
26
|
+
rpc GenerateAudio(GenerateAudioRequest) returns (GenerateAudioResponse);
|
|
27
|
+
|
|
28
|
+
// Provider Management
|
|
29
|
+
rpc GetProviders(GetProvidersRequest) returns (GetProvidersResponse);
|
|
30
|
+
rpc UpdateProvider(UpdateProviderRequest) returns (UpdateProviderResponse);
|
|
31
|
+
rpc CheckProviderHealth(CheckHealthRequest) returns (CheckHealthResponse);
|
|
32
|
+
|
|
33
|
+
// Analytics & Metrics
|
|
34
|
+
rpc GetGenerationMetrics(GetMetricsRequest) returns (GetMetricsResponse);
|
|
35
|
+
rpc GetUsageStats(GetUsageStatsRequest) returns (GetUsageStatsResponse);
|
|
36
|
+
|
|
37
|
+
// Cache Management
|
|
38
|
+
rpc ClearCache(ClearCacheRequest) returns (ClearCacheResponse);
|
|
39
|
+
rpc GetCacheStats(CacheStatsRequest) returns (CacheStatsResponse);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ===================================================================
|
|
43
|
+
// COMMON MESSAGES
|
|
44
|
+
// ===================================================================
|
|
45
|
+
|
|
46
|
+
message Empty {}
|
|
47
|
+
|
|
48
|
+
message ErrorResponse {
|
|
49
|
+
string code = 1;
|
|
50
|
+
string message = 2;
|
|
51
|
+
map<string, string> details = 3;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ===================================================================
|
|
55
|
+
// MODEL SELECTION MESSAGES
|
|
56
|
+
// ===================================================================
|
|
57
|
+
|
|
58
|
+
message SelectModelRequest {
|
|
59
|
+
string character_id = 1;
|
|
60
|
+
string generation_type = 2; // "text", "image", "video", "audio"
|
|
61
|
+
optional string preferred_provider = 3;
|
|
62
|
+
optional string preferred_model = 4;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
message SelectModelResponse {
|
|
66
|
+
string model_id = 1;
|
|
67
|
+
string model_name = 2;
|
|
68
|
+
string model_identifier = 3;
|
|
69
|
+
string provider = 4;
|
|
70
|
+
string integration_id = 5;
|
|
71
|
+
ModelConfig config = 6;
|
|
72
|
+
bool success = 7;
|
|
73
|
+
optional string error_message = 8;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
message ModelConfig {
|
|
77
|
+
optional double temperature = 1;
|
|
78
|
+
optional int32 max_tokens = 2;
|
|
79
|
+
optional double top_p = 3;
|
|
80
|
+
repeated string stop_sequences = 4;
|
|
81
|
+
map<string, string> additional_params = 5;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
message GetModelsRequest {
|
|
85
|
+
string generation_type = 1;
|
|
86
|
+
optional bool active_only = 2;
|
|
87
|
+
optional string provider = 3;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
message GetModelsResponse {
|
|
91
|
+
repeated ModelInfo models = 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
message ModelInfo {
|
|
95
|
+
string id = 1;
|
|
96
|
+
string name = 2;
|
|
97
|
+
string identifier = 3;
|
|
98
|
+
string type = 4;
|
|
99
|
+
string provider = 5;
|
|
100
|
+
string provider_name = 6;
|
|
101
|
+
bool is_active = 7;
|
|
102
|
+
bool is_default = 8;
|
|
103
|
+
int32 priority = 9;
|
|
104
|
+
optional ModelConfig default_config = 10;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
message GetPresetsRequest {
|
|
108
|
+
optional string tier = 1;
|
|
109
|
+
optional bool active_only = 2;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
message GetPresetsResponse {
|
|
113
|
+
repeated PresetInfo presets = 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
message PresetInfo {
|
|
117
|
+
string id = 1;
|
|
118
|
+
string name = 2;
|
|
119
|
+
string description = 3;
|
|
120
|
+
string tier = 4;
|
|
121
|
+
bool is_active = 5;
|
|
122
|
+
bool is_default = 6;
|
|
123
|
+
optional ModelInfo model = 7;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ===================================================================
|
|
127
|
+
// TEXT GENERATION MESSAGES
|
|
128
|
+
// ===================================================================
|
|
129
|
+
|
|
130
|
+
message GenerateTextRequest {
|
|
131
|
+
string request_id = 1;
|
|
132
|
+
string model_id = 2;
|
|
133
|
+
repeated ChatMessage messages = 3;
|
|
134
|
+
optional string character_id = 4;
|
|
135
|
+
optional string user_id = 5;
|
|
136
|
+
optional string message_id = 6;
|
|
137
|
+
map<string, string> metadata = 7;
|
|
138
|
+
ModelConfig config = 8;
|
|
139
|
+
bool stream = 9;
|
|
140
|
+
optional string prompt = 10;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
message ChatMessage {
|
|
144
|
+
string role = 1; // "user", "assistant", "system"
|
|
145
|
+
string content = 2;
|
|
146
|
+
optional string name = 3;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
message GenerateTextResponse {
|
|
150
|
+
string request_id = 1;
|
|
151
|
+
string content = 2; // Text chunk for streaming
|
|
152
|
+
bool is_complete = 3;
|
|
153
|
+
optional GenerationMetadata generation_metadata = 4;
|
|
154
|
+
optional string error_message = 5;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
message GenerationMetadata {
|
|
158
|
+
int32 tokens_used = 1;
|
|
159
|
+
int32 input_tokens = 2;
|
|
160
|
+
int32 output_tokens = 3;
|
|
161
|
+
int32 response_time_ms = 4;
|
|
162
|
+
bool cache_hit = 5;
|
|
163
|
+
bool semantic_match = 6;
|
|
164
|
+
string model_used = 7;
|
|
165
|
+
string provider_used = 8;
|
|
166
|
+
optional double estimated_cost = 9;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ===================================================================
|
|
170
|
+
// IMAGE GENERATION MESSAGES
|
|
171
|
+
// ===================================================================
|
|
172
|
+
|
|
173
|
+
message GenerateImageRequest {
|
|
174
|
+
string request_id = 1;
|
|
175
|
+
string model_id = 2;
|
|
176
|
+
string prompt = 3;
|
|
177
|
+
optional string negative_prompt = 4;
|
|
178
|
+
optional string character_id = 5;
|
|
179
|
+
optional string user_id = 6;
|
|
180
|
+
ImageConfig config = 7;
|
|
181
|
+
map<string, string> metadata = 8;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
message ImageConfig {
|
|
185
|
+
int32 width = 1;
|
|
186
|
+
int32 height = 2;
|
|
187
|
+
int32 num_images = 3;
|
|
188
|
+
optional string style = 4;
|
|
189
|
+
optional double guidance_scale = 5;
|
|
190
|
+
optional int32 steps = 6;
|
|
191
|
+
optional string quality = 7; // "standard", "hd"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
message GenerateImageResponse {
|
|
195
|
+
string request_id = 1;
|
|
196
|
+
repeated string image_urls = 2;
|
|
197
|
+
repeated bytes image_data = 3; // Optional raw image data
|
|
198
|
+
GenerationMetadata generation_metadata = 4;
|
|
199
|
+
bool success = 5;
|
|
200
|
+
optional string error_message = 6;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ===================================================================
|
|
204
|
+
// VIDEO GENERATION MESSAGES
|
|
205
|
+
// ===================================================================
|
|
206
|
+
|
|
207
|
+
message GenerateVideoRequest {
|
|
208
|
+
string request_id = 1;
|
|
209
|
+
string model_id = 2;
|
|
210
|
+
string prompt = 3;
|
|
211
|
+
optional string character_id = 4;
|
|
212
|
+
optional string user_id = 5;
|
|
213
|
+
VideoConfig config = 6;
|
|
214
|
+
map<string, string> metadata = 7;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
message VideoConfig {
|
|
218
|
+
int32 duration_seconds = 1;
|
|
219
|
+
optional int32 fps = 2;
|
|
220
|
+
optional string resolution = 3; // "720p", "1080p", "4k"
|
|
221
|
+
optional string style = 4;
|
|
222
|
+
optional string aspect_ratio = 5; // "16:9", "9:16", "1:1"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
message GenerateVideoResponse {
|
|
226
|
+
string request_id = 1;
|
|
227
|
+
string video_url = 2;
|
|
228
|
+
optional string thumbnail_url = 3;
|
|
229
|
+
GenerationMetadata generation_metadata = 4;
|
|
230
|
+
bool success = 5;
|
|
231
|
+
optional string error_message = 6;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ===================================================================
|
|
235
|
+
// AUDIO GENERATION MESSAGES
|
|
236
|
+
// ===================================================================
|
|
237
|
+
|
|
238
|
+
message GenerateAudioRequest {
|
|
239
|
+
string request_id = 1;
|
|
240
|
+
string model_id = 2;
|
|
241
|
+
string text = 3;
|
|
242
|
+
optional string character_id = 4;
|
|
243
|
+
optional string user_id = 5;
|
|
244
|
+
AudioConfig config = 6;
|
|
245
|
+
map<string, string> metadata = 7;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
message AudioConfig {
|
|
249
|
+
optional string voice = 1;
|
|
250
|
+
optional double speed = 2;
|
|
251
|
+
optional int32 sample_rate = 3;
|
|
252
|
+
optional string format = 4; // "mp3", "wav", "ogg"
|
|
253
|
+
optional double stability = 5;
|
|
254
|
+
optional double similarity_boost = 6;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
message GenerateAudioResponse {
|
|
258
|
+
string request_id = 1;
|
|
259
|
+
string audio_url = 2;
|
|
260
|
+
optional bytes audio_data = 3;
|
|
261
|
+
GenerationMetadata generation_metadata = 4;
|
|
262
|
+
bool success = 5;
|
|
263
|
+
optional string error_message = 6;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ===================================================================
|
|
267
|
+
// PROVIDER MANAGEMENT MESSAGES
|
|
268
|
+
// ===================================================================
|
|
269
|
+
|
|
270
|
+
message GetProvidersRequest {
|
|
271
|
+
optional string type = 1; // Filter by type: "text", "image", "video", "audio"
|
|
272
|
+
optional bool enabled_only = 2;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
message GetProvidersResponse {
|
|
276
|
+
repeated ProviderInfo providers = 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
message ProviderInfo {
|
|
280
|
+
string id = 1;
|
|
281
|
+
string name = 2;
|
|
282
|
+
string provider = 3;
|
|
283
|
+
string type = 4;
|
|
284
|
+
bool is_enabled = 5;
|
|
285
|
+
string status = 6;
|
|
286
|
+
int32 priority = 7;
|
|
287
|
+
ProviderHealth health = 8;
|
|
288
|
+
repeated string supported_features = 9;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
message ProviderHealth {
|
|
292
|
+
bool is_healthy = 1;
|
|
293
|
+
int64 last_check_timestamp = 2;
|
|
294
|
+
optional int32 response_time_ms = 3;
|
|
295
|
+
optional string error_message = 4;
|
|
296
|
+
double uptime_percentage = 5;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
message UpdateProviderRequest {
|
|
300
|
+
string provider_id = 1;
|
|
301
|
+
optional bool is_enabled = 2;
|
|
302
|
+
optional string status = 3;
|
|
303
|
+
optional int32 priority = 4;
|
|
304
|
+
map<string, string> config_updates = 5;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
message UpdateProviderResponse {
|
|
308
|
+
bool success = 1;
|
|
309
|
+
string message = 2;
|
|
310
|
+
optional ProviderInfo updated_provider = 3;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
message CheckHealthRequest {
|
|
314
|
+
optional string provider_id = 1; // Check specific provider or all if empty
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
message CheckHealthResponse {
|
|
318
|
+
map<string, ProviderHealth> health_status = 1;
|
|
319
|
+
int32 healthy_count = 2;
|
|
320
|
+
int32 total_count = 3;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ===================================================================
|
|
324
|
+
// ANALYTICS MESSAGES
|
|
325
|
+
// ===================================================================
|
|
326
|
+
|
|
327
|
+
message GetMetricsRequest {
|
|
328
|
+
string period_type = 1; // "hourly", "daily", "monthly"
|
|
329
|
+
int64 start_timestamp = 2;
|
|
330
|
+
int64 end_timestamp = 3;
|
|
331
|
+
optional string generation_type = 4;
|
|
332
|
+
optional string provider_id = 5;
|
|
333
|
+
optional string model_id = 6;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
message GetMetricsResponse {
|
|
337
|
+
repeated MetricData metrics = 1;
|
|
338
|
+
MetricSummary summary = 2;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
message MetricData {
|
|
342
|
+
int64 period_start = 1;
|
|
343
|
+
int64 period_end = 2;
|
|
344
|
+
string generation_type = 3;
|
|
345
|
+
int32 total_requests = 4;
|
|
346
|
+
int32 successful_requests = 5;
|
|
347
|
+
int32 failed_requests = 6;
|
|
348
|
+
int32 cached_requests = 7;
|
|
349
|
+
double avg_response_time = 8;
|
|
350
|
+
double p95_response_time = 9;
|
|
351
|
+
double cache_hit_rate = 10;
|
|
352
|
+
TokenUsage token_usage = 11;
|
|
353
|
+
double total_cost = 12;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
message MetricSummary {
|
|
357
|
+
int32 total_requests = 1;
|
|
358
|
+
int32 total_successful = 2;
|
|
359
|
+
int32 total_failed = 3;
|
|
360
|
+
int32 total_cached = 4;
|
|
361
|
+
double overall_cache_hit_rate = 5;
|
|
362
|
+
double total_cost = 6;
|
|
363
|
+
TokenUsage total_tokens = 7;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
message TokenUsage {
|
|
367
|
+
int32 input_tokens = 1;
|
|
368
|
+
int32 output_tokens = 2;
|
|
369
|
+
int32 total_tokens = 3;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
message GetUsageStatsRequest {
|
|
373
|
+
optional string character_id = 1;
|
|
374
|
+
optional string user_id = 2;
|
|
375
|
+
int64 start_timestamp = 3;
|
|
376
|
+
int64 end_timestamp = 4;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
message GetUsageStatsResponse {
|
|
380
|
+
map<string, int32> generations_by_type = 1;
|
|
381
|
+
TokenUsage total_tokens = 2;
|
|
382
|
+
double total_cost = 3;
|
|
383
|
+
int32 cache_hits = 4;
|
|
384
|
+
double cache_hit_rate = 5;
|
|
385
|
+
repeated ModelUsage model_usage = 6;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
message ModelUsage {
|
|
389
|
+
string model_id = 1;
|
|
390
|
+
string model_name = 2;
|
|
391
|
+
int32 request_count = 3;
|
|
392
|
+
TokenUsage tokens = 4;
|
|
393
|
+
double cost = 5;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ===================================================================
|
|
397
|
+
// CACHE MANAGEMENT MESSAGES
|
|
398
|
+
// ===================================================================
|
|
399
|
+
|
|
400
|
+
message ClearCacheRequest {
|
|
401
|
+
optional string cache_type = 1; // "all", "generation", "model", "embedding"
|
|
402
|
+
optional string generation_type = 2; // Clear cache for specific type
|
|
403
|
+
optional bool force = 3; // Force clear even if recently used
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
message ClearCacheResponse {
|
|
407
|
+
bool success = 1;
|
|
408
|
+
string message = 2;
|
|
409
|
+
int32 entries_cleared = 3;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
message CacheStatsRequest {
|
|
413
|
+
optional string cache_type = 1;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
message CacheStatsResponse {
|
|
417
|
+
int64 total_entries = 1;
|
|
418
|
+
int64 total_hits = 2;
|
|
419
|
+
int64 total_misses = 3;
|
|
420
|
+
double hit_rate = 4;
|
|
421
|
+
int64 memory_usage_bytes = 5;
|
|
422
|
+
map<string, CacheTypeStats> stats_by_type = 6;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
message CacheTypeStats {
|
|
426
|
+
string type = 1;
|
|
427
|
+
int64 entries = 2;
|
|
428
|
+
int64 hits = 3;
|
|
429
|
+
int64 misses = 4;
|
|
430
|
+
double hit_rate = 5;
|
|
431
|
+
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// ===================================================================
|
|
2
|
+
// NOVA SERVICE gRPC DEFINITION
|
|
3
|
+
// ===================================================================
|
|
4
|
+
// Multi-modal AI generation service with flexible model selection
|
|
5
|
+
// Supports text, image, video, and audio generation
|
|
6
|
+
|
|
7
|
+
syntax = "proto3";
|
|
8
|
+
|
|
9
|
+
package nova.service;
|
|
10
|
+
|
|
11
|
+
import "google/protobuf/timestamp.proto";
|
|
12
|
+
import "google/protobuf/duration.proto";
|
|
13
|
+
|
|
14
|
+
// ===================================================================
|
|
15
|
+
// SERVICE DEFINITION
|
|
16
|
+
// ===================================================================
|
|
17
|
+
|
|
18
|
+
service NovaService {
|
|
19
|
+
// Text Generation (FULLY IMPLEMENTED with Ollama)
|
|
20
|
+
rpc GenerateText(GenerateTextRequest) returns (GenerateTextResponse);
|
|
21
|
+
rpc StreamText(GenerateTextRequest) returns (stream GenerateTextStreamResponse);
|
|
22
|
+
|
|
23
|
+
// Image Generation (NULL IMPLEMENTATION - Coming Soon)
|
|
24
|
+
rpc GenerateImage(GenerateImageRequest) returns (GenerateImageResponse);
|
|
25
|
+
|
|
26
|
+
// Audio Generation (NULL IMPLEMENTATION - Coming Soon)
|
|
27
|
+
rpc GenerateAudio(GenerateAudioRequest) returns (GenerateAudioResponse);
|
|
28
|
+
|
|
29
|
+
// Video Generation (NULL IMPLEMENTATION - Coming Soon)
|
|
30
|
+
rpc GenerateVideo(GenerateVideoRequest) returns (GenerateVideoResponse);
|
|
31
|
+
|
|
32
|
+
// Administrative & Management
|
|
33
|
+
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
|
|
34
|
+
rpc ListModels(ListModelsRequest) returns (ListModelsResponse);
|
|
35
|
+
rpc ListPresets(ListPresetsRequest) returns (ListPresetsResponse);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===================================================================
|
|
39
|
+
// GENERATION TYPE ENUMS
|
|
40
|
+
// ===================================================================
|
|
41
|
+
|
|
42
|
+
enum GenerationType {
|
|
43
|
+
GENERATION_TYPE_UNSPECIFIED = 0;
|
|
44
|
+
GENERATION_TYPE_TEXT = 1;
|
|
45
|
+
GENERATION_TYPE_IMAGE = 2;
|
|
46
|
+
GENERATION_TYPE_VIDEO = 3;
|
|
47
|
+
GENERATION_TYPE_AUDIO = 4;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
enum StreamingMode {
|
|
51
|
+
STREAMING_MODE_NONE = 0;
|
|
52
|
+
STREAMING_MODE_TOKEN = 1; // Stream token by token
|
|
53
|
+
STREAMING_MODE_CHUNK = 2; // Stream in chunks
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ===================================================================
|
|
57
|
+
// MODEL SELECTION STRATEGIES
|
|
58
|
+
// ===================================================================
|
|
59
|
+
|
|
60
|
+
message ModelSelection {
|
|
61
|
+
oneof selection_strategy {
|
|
62
|
+
// Option 1: Use a preset ID (RECOMMENDED)
|
|
63
|
+
string preset_id = 1;
|
|
64
|
+
|
|
65
|
+
// Option 2: Specify exact model UUID
|
|
66
|
+
string model_id = 2;
|
|
67
|
+
|
|
68
|
+
// Option 3: Provider + Generation Type + Parameters
|
|
69
|
+
ProviderSelection provider_selection = 3;
|
|
70
|
+
|
|
71
|
+
// Option 4: Auto-select best model for generation type
|
|
72
|
+
GenerationType auto_select = 4;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
message ProviderSelection {
|
|
77
|
+
string provider_id = 1; // ProviderIntegration.id
|
|
78
|
+
GenerationType type = 2; // Text, Image, Video, Audio
|
|
79
|
+
map<string, string> parameters = 3; // Provider-specific params
|
|
80
|
+
int32 priority = 4; // Selection priority
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ===================================================================
|
|
84
|
+
// REQUEST/RESPONSE MESSAGES
|
|
85
|
+
// ===================================================================
|
|
86
|
+
|
|
87
|
+
// ----- TEXT GENERATION -----
|
|
88
|
+
|
|
89
|
+
message GenerateTextRequest {
|
|
90
|
+
string prompt = 1;
|
|
91
|
+
GenerationContext context = 2;
|
|
92
|
+
ModelSelection model_selection = 3;
|
|
93
|
+
|
|
94
|
+
// Generation parameters
|
|
95
|
+
int32 max_tokens = 4;
|
|
96
|
+
float temperature = 5;
|
|
97
|
+
float top_p = 6;
|
|
98
|
+
float top_k = 7;
|
|
99
|
+
string stop_sequences = 8;
|
|
100
|
+
bool include_context = 9;
|
|
101
|
+
|
|
102
|
+
// Streaming
|
|
103
|
+
StreamingMode streaming_mode = 10;
|
|
104
|
+
bool enable_streaming = 11;
|
|
105
|
+
|
|
106
|
+
// Request metadata
|
|
107
|
+
string request_id = 12;
|
|
108
|
+
string client_id = 13;
|
|
109
|
+
string character_id = 14;
|
|
110
|
+
string user_id = 15;
|
|
111
|
+
google.protobuf.Duration timeout = 16;
|
|
112
|
+
|
|
113
|
+
// Generation options
|
|
114
|
+
bool enable_cache = 17;
|
|
115
|
+
bool enable_semantic_cache = 18;
|
|
116
|
+
bool retry_on_failure = 19;
|
|
117
|
+
int32 max_retries = 20;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
message GenerateTextResponse {
|
|
121
|
+
GenerationResult result = 1;
|
|
122
|
+
ModelInfo used_model = 2;
|
|
123
|
+
UsageStats usage = 3;
|
|
124
|
+
google.protobuf.Duration processing_time = 4;
|
|
125
|
+
bool cached = 5;
|
|
126
|
+
bool semantic_cache_hit = 6;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
message GenerateTextStreamResponse {
|
|
130
|
+
string token = 1;
|
|
131
|
+
int32 token_index = 2;
|
|
132
|
+
ModelInfo used_model = 3;
|
|
133
|
+
GenerationError error = 4;
|
|
134
|
+
bool completed = 5;
|
|
135
|
+
UsageStats usage = 6;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ----- IMAGE GENERATION (NULL IMPLEMENTATION) -----
|
|
139
|
+
|
|
140
|
+
message GenerateImageRequest {
|
|
141
|
+
string prompt = 1;
|
|
142
|
+
GenerationContext context = 2;
|
|
143
|
+
ModelSelection model_selection = 3;
|
|
144
|
+
|
|
145
|
+
// Image parameters
|
|
146
|
+
int32 width = 4;
|
|
147
|
+
int32 height = 5;
|
|
148
|
+
string style = 6;
|
|
149
|
+
int32 num_images = 7;
|
|
150
|
+
string negative_prompt = 8;
|
|
151
|
+
|
|
152
|
+
// Request metadata
|
|
153
|
+
string request_id = 9;
|
|
154
|
+
string client_id = 10;
|
|
155
|
+
string character_id = 11;
|
|
156
|
+
string user_id = 12;
|
|
157
|
+
google.protobuf.Duration timeout = 13;
|
|
158
|
+
|
|
159
|
+
// Generation options
|
|
160
|
+
bool enable_cache = 14;
|
|
161
|
+
bool retry_on_failure = 15;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
message GenerateImageResponse {
|
|
165
|
+
// NULL IMPLEMENTATION - Coming Soon
|
|
166
|
+
GenerationError error = 1; // Returns "NOT_IMPLEMENTED" with message
|
|
167
|
+
string message = 2; // "Image generation coming soon"
|
|
168
|
+
GenerationResult result = 3;
|
|
169
|
+
ModelInfo used_model = 4;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ----- AUDIO GENERATION (NULL IMPLEMENTATION) -----
|
|
173
|
+
|
|
174
|
+
message GenerateAudioRequest {
|
|
175
|
+
string text = 1;
|
|
176
|
+
GenerationContext context = 2;
|
|
177
|
+
ModelSelection model_selection = 3;
|
|
178
|
+
|
|
179
|
+
// Audio parameters
|
|
180
|
+
string voice = 4;
|
|
181
|
+
float speed = 5;
|
|
182
|
+
float pitch = 6;
|
|
183
|
+
string format = 7;
|
|
184
|
+
|
|
185
|
+
// Request metadata
|
|
186
|
+
string request_id = 8;
|
|
187
|
+
string client_id = 9;
|
|
188
|
+
string character_id = 10;
|
|
189
|
+
string user_id = 11;
|
|
190
|
+
google.protobuf.Duration timeout = 12;
|
|
191
|
+
|
|
192
|
+
// Generation options
|
|
193
|
+
bool enable_cache = 13;
|
|
194
|
+
bool retry_on_failure = 14;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
message GenerateAudioResponse {
|
|
198
|
+
// NULL IMPLEMENTATION - Coming Soon
|
|
199
|
+
GenerationError error = 1; // Returns "NOT_IMPLEMENTED" with message
|
|
200
|
+
string message = 2; // "Audio generation coming soon"
|
|
201
|
+
GenerationResult result = 3;
|
|
202
|
+
ModelInfo used_model = 4;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ----- VIDEO GENERATION (NULL IMPLEMENTATION) -----
|
|
206
|
+
|
|
207
|
+
message GenerateVideoRequest {
|
|
208
|
+
string prompt = 1;
|
|
209
|
+
GenerationContext context = 2;
|
|
210
|
+
ModelSelection model_selection = 3;
|
|
211
|
+
|
|
212
|
+
// Video parameters
|
|
213
|
+
int32 duration = 4;
|
|
214
|
+
int32 width = 5;
|
|
215
|
+
int32 height = 6;
|
|
216
|
+
int32 fps = 7;
|
|
217
|
+
string style = 8;
|
|
218
|
+
|
|
219
|
+
// Request metadata
|
|
220
|
+
string request_id = 9;
|
|
221
|
+
string client_id = 10;
|
|
222
|
+
string character_id = 11;
|
|
223
|
+
string user_id = 12;
|
|
224
|
+
google.protobuf.Duration timeout = 13;
|
|
225
|
+
|
|
226
|
+
// Generation options
|
|
227
|
+
bool enable_cache = 14;
|
|
228
|
+
bool retry_on_failure = 15;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
message GenerateVideoResponse {
|
|
232
|
+
// NULL IMPLEMENTATION - Coming Soon
|
|
233
|
+
GenerationError error = 1; // Returns "NOT_IMPLEMENTED" with message
|
|
234
|
+
string message = 2; // "Video generation coming soon"
|
|
235
|
+
GenerationResult result = 3;
|
|
236
|
+
ModelInfo used_model = 4;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ===================================================================
|
|
240
|
+
// CONTEXT & SUPPORTING MESSAGES
|
|
241
|
+
// ===================================================================
|
|
242
|
+
|
|
243
|
+
message GenerationContext {
|
|
244
|
+
// Character context
|
|
245
|
+
string character_id = 1;
|
|
246
|
+
CharacterPersonality personality = 2;
|
|
247
|
+
|
|
248
|
+
// Conversation history (for context)
|
|
249
|
+
repeated Message conversation_history = 3;
|
|
250
|
+
|
|
251
|
+
// User preferences
|
|
252
|
+
map<string, string> user_preferences = 4;
|
|
253
|
+
|
|
254
|
+
// Session information
|
|
255
|
+
string session_id = 5;
|
|
256
|
+
google.protobuf.Timestamp session_start = 6;
|
|
257
|
+
|
|
258
|
+
// Custom metadata
|
|
259
|
+
map<string, string> metadata = 7;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
message CharacterPersonality {
|
|
263
|
+
string name = 1;
|
|
264
|
+
string description = 2;
|
|
265
|
+
repeated string traits = 3;
|
|
266
|
+
string background = 4;
|
|
267
|
+
repeated string interests = 5;
|
|
268
|
+
string speaking_style = 6;
|
|
269
|
+
map<string, string> custom_fields = 7;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
message Message {
|
|
273
|
+
string role = 1; // "user", "assistant", "system"
|
|
274
|
+
string content = 2;
|
|
275
|
+
google.protobuf.Timestamp timestamp = 3;
|
|
276
|
+
map<string, string> metadata = 4;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ===================================================================
|
|
280
|
+
// RESULT & ERROR MESSAGES
|
|
281
|
+
// ===================================================================
|
|
282
|
+
|
|
283
|
+
message GenerationResult {
|
|
284
|
+
string result = 1; // Main result (text, URL, etc.)
|
|
285
|
+
repeated string additional_data = 2; // Extra information
|
|
286
|
+
map<string, string> metadata = 3; // Generation metadata
|
|
287
|
+
string content_type = 4; // "text", "image_url", "audio_url", etc.
|
|
288
|
+
google.protobuf.Timestamp generated_at = 5;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
message ModelInfo {
|
|
292
|
+
string model_id = 1;
|
|
293
|
+
string model_name = 2;
|
|
294
|
+
string provider_id = 3;
|
|
295
|
+
string provider_name = 4;
|
|
296
|
+
GenerationType generation_type = 5;
|
|
297
|
+
map<string, string> configuration = 6;
|
|
298
|
+
string version = 7;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
message UsageStats {
|
|
302
|
+
int32 input_tokens = 1;
|
|
303
|
+
int32 output_tokens = 2;
|
|
304
|
+
int32 total_tokens = 3;
|
|
305
|
+
double cost_estimate = 4;
|
|
306
|
+
int32 requests_made = 5;
|
|
307
|
+
int32 cache_hits = 6;
|
|
308
|
+
int32 semantic_cache_hits = 7;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
message GenerationError {
|
|
312
|
+
ErrorCode error_code = 1;
|
|
313
|
+
string error_message = 2;
|
|
314
|
+
string error_details = 3;
|
|
315
|
+
bool retryable = 4;
|
|
316
|
+
int32 retry_after_seconds = 5;
|
|
317
|
+
ErrorCategory category = 6;
|
|
318
|
+
|
|
319
|
+
// Context about the error
|
|
320
|
+
string provider_id = 7;
|
|
321
|
+
string model_id = 8;
|
|
322
|
+
string request_id = 9;
|
|
323
|
+
|
|
324
|
+
// Debug information
|
|
325
|
+
map<string, string> debug_info = 10;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
enum ErrorCode {
|
|
329
|
+
ERROR_CODE_UNSPECIFIED = 0;
|
|
330
|
+
ERROR_CODE_SUCCESS = 1;
|
|
331
|
+
ERROR_CODE_INVALID_REQUEST = 2;
|
|
332
|
+
ERROR_CODE_AUTHENTICATION_FAILED = 3;
|
|
333
|
+
ERROR_CODE_ACCESS_DENIED = 4;
|
|
334
|
+
ERROR_CODE_MODEL_NOT_FOUND = 5;
|
|
335
|
+
ERROR_CODE_PROVIDER_UNAVAILABLE = 6;
|
|
336
|
+
ERROR_CODE_RATE_LIMIT_EXCEEDED = 7;
|
|
337
|
+
ERROR_CODE_QUOTA_EXCEEDED = 8;
|
|
338
|
+
ERROR_CODE_PROMPT_TOO_LONG = 9;
|
|
339
|
+
ERROR_CODE_PARAMETER_ERROR = 10;
|
|
340
|
+
ERROR_CODE_GENERATION_FAILED = 11;
|
|
341
|
+
ERROR_CODE_TIMEOUT = 12;
|
|
342
|
+
ERROR_CODE_INTERNAL_ERROR = 13;
|
|
343
|
+
ERROR_CODE_SERVICE_UNAVAILABLE = 14;
|
|
344
|
+
ERROR_CODE_NOT_IMPLEMENTED = 15;
|
|
345
|
+
ERROR_CODE_DATABASE_ERROR = 16;
|
|
346
|
+
ERROR_CODE_CACHE_ERROR = 17;
|
|
347
|
+
ERROR_CODE_VECTOR_DB_ERROR = 18;
|
|
348
|
+
ERROR_CODE_PROVIDER_TIMEOUT = 19;
|
|
349
|
+
ERROR_CODE_PROVIDER_RATE_LIMIT = 20;
|
|
350
|
+
ERROR_CODE_PROVIDER_AUTH_ERROR = 21;
|
|
351
|
+
ERROR_CODE_PROVIDER_QUOTA_EXCEEDED = 22;
|
|
352
|
+
ERROR_CODE_PROVIDER_INVALID_REQUEST = 23;
|
|
353
|
+
ERROR_CODE_PROVIDER_GENERATION_FAILED = 24;
|
|
354
|
+
ERROR_CODE_MAINTENANCE_MODE = 25;
|
|
355
|
+
ERROR_CODE_DEPRECATED_MODEL = 26;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
enum ErrorCategory {
|
|
359
|
+
ERROR_CATEGORY_UNSPECIFIED = 0;
|
|
360
|
+
ERROR_CATEGORY_CLIENT = 1; // Client error (4xx)
|
|
361
|
+
ERROR_CATEGORY_SERVER = 2; // Server error (5xx)
|
|
362
|
+
ERROR_CATEGORY_PROVIDER = 3; // Provider error
|
|
363
|
+
ERROR_CATEGORY_RATE_LIMIT = 4; // Rate limiting
|
|
364
|
+
ERROR_CATEGORY_AUTHENTICATION = 5; // Auth related
|
|
365
|
+
ERROR_CATEGORY_AUTHORIZATION = 6; // Permission related
|
|
366
|
+
ERROR_CATEGORY_VALIDATION = 7; // Input validation
|
|
367
|
+
ERROR_CATEGORY_QUOTA = 8; // Quota/limit exceeded
|
|
368
|
+
ERROR_CATEGORY_NOT_FOUND = 9; // Resource not found
|
|
369
|
+
ERROR_CATEGORY_NOT_IMPLEMENTED = 10; // Feature not implemented
|
|
370
|
+
ERROR_CATEGORY_MAINTENANCE = 11; // Service maintenance
|
|
371
|
+
ERROR_CATEGORY_TEMPORARY = 12; // Temporary error, retry later
|
|
372
|
+
ERROR_CATEGORY_PERMANENT = 13; // Permanent error, no retry
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ===================================================================
|
|
376
|
+
// ADMINISTRATIVE MESSAGES
|
|
377
|
+
// ===================================================================
|
|
378
|
+
|
|
379
|
+
message HealthCheckRequest {
|
|
380
|
+
bool include_details = 1;
|
|
381
|
+
bool check_providers = 2;
|
|
382
|
+
bool check_cache = 3;
|
|
383
|
+
bool check_database = 4;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
message HealthCheckResponse {
|
|
387
|
+
ServiceStatus status = 1;
|
|
388
|
+
string message = 2;
|
|
389
|
+
google.protobuf.Timestamp timestamp = 3;
|
|
390
|
+
repeated ProviderHealth provider_health = 4;
|
|
391
|
+
SystemMetrics metrics = 5;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
message ServiceStatus {
|
|
395
|
+
StatusCode status = 1;
|
|
396
|
+
int32 uptime_seconds = 2;
|
|
397
|
+
int32 total_requests = 3;
|
|
398
|
+
int32 active_requests = 4;
|
|
399
|
+
double cpu_usage = 5;
|
|
400
|
+
double memory_usage = 6;
|
|
401
|
+
int32 cache_hit_rate = 7;
|
|
402
|
+
int32 semantic_cache_hit_rate = 8;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
enum StatusCode {
|
|
406
|
+
STATUS_CODE_HEALTHY = 0;
|
|
407
|
+
STATUS_CODE_DEGRADED = 1;
|
|
408
|
+
STATUS_CODE_UNHEALTHY = 2;
|
|
409
|
+
STATUS_CODE_MAINTENANCE = 3;
|
|
410
|
+
STATUS_CODE_UNKNOWN = 4;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
message ProviderHealth {
|
|
414
|
+
string provider_id = 1;
|
|
415
|
+
string provider_name = 2;
|
|
416
|
+
bool is_healthy = 3;
|
|
417
|
+
int32 response_time_ms = 4;
|
|
418
|
+
repeated GenerationType supported_types = 5;
|
|
419
|
+
string error_message = 6;
|
|
420
|
+
google.protobuf.Timestamp last_check = 7;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
message SystemMetrics {
|
|
424
|
+
int32 total_providers = 1;
|
|
425
|
+
int32 active_providers = 2;
|
|
426
|
+
int32 total_models = 3;
|
|
427
|
+
int32 active_models = 4;
|
|
428
|
+
int32 total_presets = 5;
|
|
429
|
+
int32 active_clients = 6;
|
|
430
|
+
int32 cache_size = 7;
|
|
431
|
+
int32 vector_cache_size = 8;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
message ListModelsRequest {
|
|
435
|
+
GenerationType type = 1;
|
|
436
|
+
bool active_only = 2;
|
|
437
|
+
string provider_id = 3;
|
|
438
|
+
bool include_inactive = 4;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
message ListModelsResponse {
|
|
442
|
+
repeated ModelInfo models = 1;
|
|
443
|
+
int32 total_count = 2;
|
|
444
|
+
int32 active_count = 3;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
message ListPresetsRequest {
|
|
448
|
+
GenerationType type = 1;
|
|
449
|
+
string tier = 2; // "free", "basic", "premium", "unlimited"
|
|
450
|
+
bool active_only = 3;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
message ModelPreset {
|
|
454
|
+
string preset_id = 1;
|
|
455
|
+
string name = 2;
|
|
456
|
+
string description = 3;
|
|
457
|
+
string tier = 4;
|
|
458
|
+
GenerationType type = 5;
|
|
459
|
+
string model_id = 6;
|
|
460
|
+
ModelInfo model_info = 7;
|
|
461
|
+
map<string, string> parameters = 8;
|
|
462
|
+
bool is_default = 9;
|
|
463
|
+
bool is_active = 10;
|
|
464
|
+
map<string, string> features = 11;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
message ListPresetsResponse {
|
|
468
|
+
repeated ModelPreset presets = 1;
|
|
469
|
+
int32 total_count = 2;
|
|
470
|
+
int32 active_count = 3;
|
|
471
|
+
}
|
|
472
|
+
|