jac-scale 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

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.
@@ -0,0 +1,349 @@
1
+ impl JacScaleUserManager.postinit -> None {
2
+ super.postinit();
3
+ # Create SSO accounts table for tracking linked external identities
4
+ self._ensure_connection();
5
+ self._conn.execute(
6
+ """
7
+ CREATE TABLE IF NOT EXISTS sso_accounts (
8
+ user_id TEXT NOT NULL,
9
+ platform TEXT NOT NULL,
10
+ external_id TEXT NOT NULL,
11
+ email TEXT,
12
+ linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
13
+ PRIMARY KEY (platform, external_id),
14
+ FOREIGN KEY (user_id) REFERENCES users(username) ON DELETE CASCADE
15
+ )
16
+ """
17
+ );
18
+ # Create index for faster lookups by user_id
19
+ self._conn.execute(
20
+ "CREATE INDEX IF NOT EXISTS idx_sso_accounts_user_id ON sso_accounts(user_id)"
21
+ );
22
+ self._conn.commit();
23
+ # Load SSO config
24
+ sso_config = get_scale_config().get_sso_config();
25
+ for platform in Platforms {
26
+ key = platform.lower();
27
+ platform_config = sso_config.get(key, {});
28
+
29
+ client_id = platform_config.get('client_id', '');
30
+ client_secret = platform_config.get('client_secret', '');
31
+
32
+ if not client_id or not client_secret {
33
+ continue;
34
+ }
35
+
36
+ self.SUPPORTED_PLATFORMS[platform.value] = {
37
+ "client_id": client_id,
38
+ "client_secret": client_secret
39
+ };
40
+ }
41
+ }
42
+
43
+ impl JacScaleUserManager.create_jwt_token(username: str) -> str {
44
+ now = datetime.now(UTC);
45
+ payload: dict[(str, Any)] = {
46
+ 'username': username,
47
+ 'exp': (now + timedelta(days=JWT_EXP_DELTA_DAYS)),
48
+ 'iat': now.timestamp()
49
+ };
50
+ return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM);
51
+ }
52
+
53
+ impl JacScaleUserManager.validate_jwt_token(token: str) -> (str | None) {
54
+ try {
55
+ decoded = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM]);
56
+ return decoded['username'];
57
+ } except Exception {
58
+ return None;
59
+ }
60
+ }
61
+
62
+ impl JacScaleUserManager.refresh_jwt_token(token: str) -> (str | None) {
63
+ try {
64
+ decoded = jwt.decode(
65
+ token, JWT_SECRET, algorithms=[JWT_ALGORITHM], options={"verify_exp": True}
66
+ );
67
+ username = decoded.get('username');
68
+
69
+ if not username {
70
+ return None;
71
+ }
72
+
73
+ return self.create_jwt_token(username);
74
+ } except Exception {
75
+ return None;
76
+ }
77
+ }
78
+
79
+ impl JacScaleUserManager.validate_token(token: str) -> (str | None) {
80
+ return self.validate_jwt_token(token);
81
+ }
82
+
83
+ impl JacScaleUserManager.get_sso(platform: str, operation: str) -> (SSOProvider | None) {
84
+ if (platform not in self.SUPPORTED_PLATFORMS) {
85
+ return None;
86
+ }
87
+ credentials = self.SUPPORTED_PLATFORMS[platform];
88
+ redirect_uri = f"{SSO_HOST}/{platform}/{operation}/callback";
89
+ if (platform == Platforms.GOOGLE.value) {
90
+ import from jac_scale.google_sso_provider { GoogleSSOProvider }
91
+ return GoogleSSOProvider(
92
+ client_id=credentials['client_id'],
93
+ client_secret=credentials['client_secret'],
94
+ redirect_uri=redirect_uri,
95
+ allow_insecure_http=True
96
+ );
97
+ }
98
+ return None;
99
+ }
100
+
101
+ impl JacScaleUserManager.sso_initiate(
102
+ platform: str, operation: str
103
+ ) -> (Response | TransportResponse) {
104
+ import from jaclang.runtimelib.server { JsonValue }
105
+ if (platform not in [p.value for p in Platforms]) {
106
+ return TransportResponse.fail(
107
+ code='INVALID_PLATFORM',
108
+ message=f"Invalid platform '{platform}'. Supported platforms: {', '.join(
109
+ [p.value for p in Platforms]
110
+ )}",
111
+ meta=Meta(extra={'http_status': 400})
112
+ );
113
+ }
114
+ if (platform not in self.SUPPORTED_PLATFORMS) {
115
+ return TransportResponse.fail(
116
+ code='SSO_NOT_CONFIGURED',
117
+ message=f"SSO for platform '{platform}' is not configured. Please set SSO_{platform.upper()}_CLIENT_ID and SSO_{platform.upper()}_CLIENT_SECRET environment variables.",
118
+ meta=Meta(extra={'http_status': 501})
119
+ );
120
+ }
121
+ if (operation not in [o.value for o in Operations]) {
122
+ return TransportResponse.fail(
123
+ code='INVALID_OPERATION',
124
+ message=f"Invalid operation '{operation}'. Must be 'login' or 'register'",
125
+ meta=Meta(extra={'http_status': 400})
126
+ );
127
+ }
128
+ sso = self.get_sso(platform, operation);
129
+ if not sso {
130
+ return TransportResponse.fail(
131
+ code='SSO_INIT_FAILED',
132
+ message=f"Failed to initialize SSO for platform '{platform}'",
133
+ meta=Meta(extra={'http_status': 500})
134
+ );
135
+ }
136
+ return await sso.initiate_auth(operation);
137
+ }
138
+
139
+ impl JacScaleUserManager.sso_callback(
140
+ request: Request, platform: str, operation: str
141
+ ) -> TransportResponse {
142
+ import from jaclang.runtimelib.server { JsonValue }
143
+ if (platform not in [p.value for p in Platforms]) {
144
+ return TransportResponse.fail(
145
+ code='INVALID_PLATFORM',
146
+ message=f"Invalid platform '{platform}'. Supported platforms: {', '.join(
147
+ [p.value for p in Platforms]
148
+ )}",
149
+ meta=Meta(extra={'http_status': 400})
150
+ );
151
+ }
152
+ if (platform not in self.SUPPORTED_PLATFORMS) {
153
+ return TransportResponse.fail(
154
+ code='SSO_NOT_CONFIGURED',
155
+ message=f"SSO for platform '{platform}' is not configured. Please set SSO_{platform.upper()}_CLIENT_ID and SSO_{platform.upper()}_CLIENT_SECRET environment variables.",
156
+ meta=Meta(extra={'http_status': 501})
157
+ );
158
+ }
159
+ if (operation not in [o.value for o in Operations]) {
160
+ return TransportResponse.fail(
161
+ code='INVALID_OPERATION',
162
+ message=f"Invalid operation '{operation}'. Must be 'login' or 'register'",
163
+ meta=Meta(extra={'http_status': 400})
164
+ );
165
+ }
166
+ sso = self.get_sso(platform, operation);
167
+ if not sso {
168
+ return TransportResponse.fail(
169
+ code='SSO_INIT_FAILED',
170
+ message=f"Failed to initialize SSO for platform '{platform}'",
171
+ meta=Meta(extra={'http_status': 500})
172
+ );
173
+ }
174
+ try {
175
+ user_info = await sso.handle_callback(request);
176
+ email = user_info.email;
177
+ if not email {
178
+ return TransportResponse.fail(
179
+ code='EMAIL_MISSING',
180
+ message=f"Email not provided by {platform}",
181
+ meta=Meta(extra={'http_status': 400})
182
+ );
183
+ }
184
+ if (operation == Operations.LOGIN.value) {
185
+ user = self.get_user(email);
186
+ if not user {
187
+ return TransportResponse.fail(
188
+ code='USER_NOT_FOUND',
189
+ message='User not found. Please register first.',
190
+ meta=Meta(extra={'http_status': 404})
191
+ );
192
+ }
193
+ token = self.create_jwt_token(email);
194
+ return TransportResponse.success(
195
+ data={
196
+ 'message': 'Login successful',
197
+ 'email': email,
198
+ 'token': token,
199
+ 'platform': platform,
200
+ 'user': dict[(str, JsonValue)](user)
201
+ },
202
+ meta=Meta(extra={'http_status': 200})
203
+ );
204
+ } elif (operation == Operations.REGISTER.value) {
205
+ existing_user = self.get_user(email);
206
+ if existing_user {
207
+ return TransportResponse.fail(
208
+ code='USER_EXISTS',
209
+ message='User already exists. Please login instead.',
210
+ meta=Meta(extra={'http_status': 400})
211
+ );
212
+ }
213
+ random_password = generate_random_password();
214
+ result = self.create_user(email, random_password);
215
+ if ('error' in result) {
216
+ return TransportResponse.fail(
217
+ code='USER_CREATION_FAILED',
218
+ message=result.get('error', 'User creation failed'),
219
+ meta=Meta(extra={'http_status': 400})
220
+ );
221
+ }
222
+ token = self.create_jwt_token(email);
223
+ result['token'] = token;
224
+ result['platform'] = platform;
225
+ return TransportResponse.success(
226
+ data=result, meta=Meta(extra={'http_status': 201})
227
+ );
228
+ }
229
+ } except Exception as e {
230
+ return TransportResponse.fail(
231
+ code='AUTHENTICATION_FAILED',
232
+ message=f"Authentication failed: {str(e)}",
233
+ meta=Meta(extra={'http_status': 500})
234
+ );
235
+ }
236
+ return TransportResponse.fail(
237
+ code='UNKNOWN_ERROR',
238
+ message='An unknown error occurred',
239
+ meta=Meta(extra={'http_status': 500})
240
+ );
241
+ }
242
+
243
+ # SSO Account Linking Methods
244
+ """Link an SSO account to a user.
245
+
246
+ This allows users to have multiple SSO providers linked to their account.
247
+ """
248
+ impl JacScaleUserManager.link_sso_account(
249
+ user_id: str, platform: str, external_id: str, email: str
250
+ ) -> dict[str, str] {
251
+ self._ensure_connection();
252
+ # Check if this SSO account is already linked to another user
253
+ cursor = self._conn.execute(
254
+ "SELECT user_id FROM sso_accounts WHERE platform = ? AND external_id = ?",
255
+ (platform, external_id)
256
+ );
257
+ existing = cursor.fetchone();
258
+ if existing {
259
+ if existing[0] == user_id {
260
+ return {'message': 'SSO account already linked to this user'};
261
+ }
262
+ return {'error': 'This SSO account is already linked to another user'};
263
+ }
264
+ # Link the SSO account
265
+ self._conn.execute(
266
+ """
267
+ INSERT INTO sso_accounts (user_id, platform, external_id, email)
268
+ VALUES (?, ?, ?, ?)
269
+ """,
270
+ (user_id, platform, external_id, email)
271
+ );
272
+ self._conn.commit();
273
+ return {
274
+ 'message': 'SSO account linked successfully',
275
+ 'user_id': user_id,
276
+ 'platform': platform
277
+ };
278
+ }
279
+
280
+ """Unlink an SSO account from a user."""
281
+ impl JacScaleUserManager.unlink_sso_account(
282
+ user_id: str, platform: str
283
+ ) -> dict[str, str] {
284
+ self._ensure_connection();
285
+ cursor = self._conn.execute(
286
+ "DELETE FROM sso_accounts WHERE user_id = ? AND platform = ?",
287
+ (user_id, platform)
288
+ );
289
+ self._conn.commit();
290
+ if cursor.rowcount == 0 {
291
+ return {'error': 'SSO account not found'};
292
+ }
293
+ return {
294
+ 'message': 'SSO account unlinked successfully',
295
+ 'user_id': user_id,
296
+ 'platform': platform
297
+ };
298
+ }
299
+
300
+ """Get all SSO accounts linked to a user."""
301
+ impl JacScaleUserManager.get_sso_accounts(user_id: str) -> list[dict[str, str]] {
302
+ self._ensure_connection();
303
+ cursor = self._conn.execute(
304
+ """
305
+ SELECT platform, external_id, email, linked_at
306
+ FROM sso_accounts
307
+ WHERE user_id = ?
308
+ ORDER BY linked_at DESC
309
+ """,
310
+ (user_id, )
311
+ );
312
+ accounts = [];
313
+ for row in cursor.fetchall() {
314
+ accounts.append(
315
+ {
316
+ 'platform': row[0],
317
+ 'external_id': row[1],
318
+ 'email': row[2],
319
+ 'linked_at': row[3]
320
+ }
321
+ );
322
+ }
323
+ return accounts;
324
+ }
325
+
326
+ """Find a user by their SSO account credentials.
327
+
328
+ This is used during SSO login to find the user associated with
329
+ an external SSO identity.
330
+ """
331
+ impl JacScaleUserManager.get_user_by_sso(
332
+ platform: str, external_id: str
333
+ ) -> (dict[str, str] | None) {
334
+ self._ensure_connection();
335
+ cursor = self._conn.execute(
336
+ """
337
+ SELECT sa.user_id, u.token, u.root_id
338
+ FROM sso_accounts sa
339
+ JOIN users u ON sa.user_id = u.username
340
+ WHERE sa.platform = ? AND sa.external_id = ?
341
+ """,
342
+ (platform, external_id)
343
+ );
344
+ row = cursor.fetchone();
345
+ if not row {
346
+ return None;
347
+ }
348
+ return {'email': row[0], 'token': row[1], 'root_id': row[2]};
349
+ }
@@ -74,7 +74,9 @@ MongoDB persistence backend - implements PersistentMemory for durable L3 storage
74
74
  Replaces SqliteMemory when MongoDB is available.
75
75
  """
76
76
  obj MongoBackend(PersistentMemory) {
77
- has client: (MongoClient | None) = None,
77
+ has client: MongoClient | None = None,
78
+ db: Any by postinit,
79
+ collection: Any by postinit,
78
80
  db_name: str = 'jac_db',
79
81
  collection_name: str = 'anchors',
80
82
  mongo_url: str = _db_config['mongodb_uri'];
jac_scale/plugin.jac CHANGED
@@ -4,6 +4,7 @@ import pathlib;
4
4
  import from dotenv { load_dotenv }
5
5
  import from jaclang.cli.registry { get_registry }
6
6
  import from jaclang.cli.command { Arg, ArgKind, CommandPriority, HookContext }
7
+ import from jaclang.cli.console { console }
7
8
  import from jaclang.pycore.runtime { hookimpl, plugin_manager }
8
9
  import from jaclang.runtimelib.context { ExecutionContext }
9
10
  import from jaclang.runtimelib.server { JacAPIServer as JacServer }
@@ -15,6 +16,9 @@ import from .factories.deployment_factory { DeploymentTargetFactory }
15
16
  import from .factories.registry_factory { ImageRegistryFactory }
16
17
  import from .factories.utility_factory { UtilityFactory }
17
18
  import from .abstractions.config.app_config { AppConfig }
19
+ import from .user_manager { JacScaleUserManager }
20
+ import from jaclang.runtimelib.server { UserManager }
21
+ import from jaclang.runtimelib.storage { Storage }
18
22
 
19
23
  """Pre-hook for jac start command to handle --scale flag."""
20
24
  def _scale_pre_hook(context: HookContext) -> None {
@@ -23,6 +27,7 @@ def _scale_pre_hook(context: HookContext) -> None {
23
27
  # Handle deployment instead of local server
24
28
  filename = context.get_arg("filename");
25
29
  build = context.get_arg("build", False);
30
+ experimental = context.get_arg("experimental", False);
26
31
  target = context.get_arg("target", "kubernetes");
27
32
  registry = context.get_arg("registry", "dockerhub");
28
33
  if not os.path.exists(filename) {
@@ -57,15 +62,27 @@ def _scale_pre_hook(context: HookContext) -> None {
57
62
  }
58
63
  # Create app config
59
64
  app_config = AppConfig(
60
- code_folder=code_folder, file_name=base_file_path, build=build
65
+ code_folder=code_folder,
66
+ file_name=base_file_path,
67
+ build=build,
68
+ experimental=experimental
61
69
  );
70
+ if experimental {
71
+ console.print(
72
+ "Installing Jaseci packages from repository (experimental mode)..."
73
+ );
74
+ } else {
75
+ console.print("Installing Jaseci packages from PyPI...");
76
+ }
62
77
  # Deploy
63
78
  result = deployment_target.deploy(app_config);
64
79
  if not result.success {
65
80
  raise RuntimeError(result.message or "Deployment failed") ;
66
81
  }
67
82
  if result.service_url {
68
- print(f"Deployment complete! Service available at: {result.service_url}");
83
+ console.print(
84
+ f"Deployment complete! Service available at: {result.service_url}"
85
+ );
69
86
  }
70
87
  # Cancel normal start execution since we handled it
71
88
  context.set_data("cancel_execution", True);
@@ -99,6 +116,13 @@ class JacCmd {
99
116
  help="Build and push Docker image (with --scale)",
100
117
  short="b"
101
118
  ),
119
+ Arg.create(
120
+ "experimental",
121
+ typ=bool,
122
+ default=False,
123
+ help="Use experimental mode (install from repo instead of PyPI)",
124
+ short="e"
125
+ ),
102
126
  Arg.create(
103
127
  "target",
104
128
  typ=str,
@@ -166,7 +190,9 @@ class JacCmd {
166
190
  app_name = os.getenv('APP_NAME') or target_config.get('app_name', 'jaseci');
167
191
  deployment_target.destroy(app_name);
168
192
 
169
- print(f"Successfully destroyed deployment '{app_name}' from {target}");
193
+ console.print(
194
+ f"Successfully destroyed deployment '{app_name}' from {target}"
195
+ );
170
196
  return 0;
171
197
  }
172
198
  }
@@ -197,6 +223,23 @@ class JacScalePlugin {
197
223
  static def get_api_server_class -> type {
198
224
  return JacAPIServer;
199
225
  }
226
+
227
+ """Provide jac-scale's UserManager."""
228
+ @hookimpl
229
+ static def get_user_manager(base_path: str) -> UserManager {
230
+ return JacScaleUserManager(base_path=base_path);
231
+ }
232
+
233
+ """Provide jac-scale's storage backend.
234
+
235
+ This overrides the core store() to use jac-scale's StorageFactory,
236
+ which supports cloud backends (S3, GCS, Azure) via configuration.
237
+ """
238
+ @hookimpl
239
+ static def store(base_path: str = "./storage", create_dirs: bool = True) -> Storage {
240
+ import from .factories.storage_factory { StorageFactory }
241
+ return StorageFactory.get_default(base_path, create_dirs);
242
+ }
200
243
  }
201
244
 
202
245
  # Pluggy's varnames() puts parameters with defaults into kwargnames, not argnames.
@@ -149,6 +149,33 @@ class JacScalePluginConfig {
149
149
  "type": "bool",
150
150
  "default": True,
151
151
  "description": "Enable Redis deployment in Kubernetes"
152
+ },
153
+ "plugin_versions": {
154
+ "type": "dict",
155
+ "default": {},
156
+ "description": "Package versions for PyPI installation (default mode). Use 'latest' or specific version.",
157
+ "nested": {
158
+ "jaclang": {
159
+ "type": "string",
160
+ "default": "latest",
161
+ "description": "jaclang package version"
162
+ },
163
+ "jac_scale": {
164
+ "type": "string",
165
+ "default": "latest",
166
+ "description": "jac-scale package version"
167
+ },
168
+ "jac_client": {
169
+ "type": "string",
170
+ "default": "latest",
171
+ "description": "jac-client package version"
172
+ },
173
+ "jac_byllm": {
174
+ "type": "string",
175
+ "default": "latest",
176
+ "description": "jac-byllm package version (use 'none' to skip)"
177
+ }
178
+ }
152
179
  }
153
180
  }
154
181
  },
jac_scale/serve.jac CHANGED
@@ -24,6 +24,9 @@ import from enum { StrEnum }
24
24
  import from fastapi_sso.sso.google { GoogleSSO }
25
25
  import from jac_scale.utils { generate_random_password }
26
26
  import from jac_scale.config_loader { get_scale_config }
27
+ import from typing { AsyncGenerator }
28
+ import from inspect { isgenerator }
29
+ import from fastapi.responses { StreamingResponse }
27
30
 
28
31
  # Load configuration from jac.toml with env var overrides
29
32
  glob _jwt_config = get_scale_config().get_jwt_config(),
@@ -43,9 +46,6 @@ obj JacAPIServer(JServer) {
43
46
  has _hmr_pending: bool = False,
44
47
  _hot_reloader: Any | None = None;
45
48
 
46
- static def create_jwt_token(username: str) -> str;
47
- static def validate_jwt_token(token: str) -> (str | None);
48
- static def refresh_jwt_token(token: str) -> (str | None);
49
49
  def postinit -> None;
50
50
  def login(username: str, password: str) -> TransportResponse;
51
51
  def register_login_endpoint -> None;
@@ -66,15 +66,6 @@ obj JacAPIServer(JServer) {
66
66
  def refresh_token(token: (str | None) = None) -> TransportResponse;
67
67
  def register_create_user_endpoint -> None;
68
68
  def register_refresh_token_endpoint -> None;
69
- def get_sso(platform: str, operation: str) -> (GoogleSSO | None);
70
- async def sso_initiate(
71
- platform: str, operation: str
72
- ) -> (Response | TransportResponse);
73
-
74
- async def sso_callback(
75
- request: Request, platform: str, operation: str
76
- ) -> TransportResponse;
77
-
78
69
  def register_sso_endpoints -> None;
79
70
  def create_walker_callback(
80
71
  walker_name: str, has_node_param: bool = False
@@ -0,0 +1,72 @@
1
+ """SSO Provider Abstraction for jac-scale.
2
+
3
+ This module defines the abstract interface for SSO providers, enabling
4
+ easy addition of new authentication vendors (Google, Microsoft, GitHub, SAML, etc.).
5
+ """
6
+
7
+ import from typing { Any }
8
+ import from fastapi { Request, Response }
9
+ import from dataclasses { dataclass }
10
+
11
+ """Standardized user information from SSO providers."""
12
+ @dataclass
13
+ class SSOUserInfo {
14
+ has email: str,
15
+ external_id: str,
16
+ platform: str,
17
+ display_name: (str | None) = None;
18
+ }
19
+
20
+ """Abstract base class for SSO providers.
21
+
22
+ All SSO provider implementations must inherit from this class and implement
23
+ the required methods. This abstraction enables:
24
+
25
+ 1. Consistent interface across all SSO vendors
26
+ 2. Easy addition of new providers (just implement this interface)
27
+ 3. Testability through mock implementations
28
+ 4. Standardized user information format
29
+
30
+ Example:
31
+ To add a new SSO provider (e.g., Microsoft):
32
+
33
+ ```jac
34
+ obj MicrosoftSSOProvider(SSOProvider) {
35
+ # Implement the three required methods
36
+ async def initiate_auth(operation: str) -> Response;
37
+ async def handle_callback(request: Request) -> SSOUserInfo;
38
+ def get_platform_name() -> str;
39
+ }
40
+ ```
41
+ """
42
+ obj SSOProvider {
43
+ """Initialize SSO authentication flow.
44
+
45
+ Args:
46
+ operation: The operation type ('login' or 'register')
47
+
48
+ Returns:
49
+ Response object (typically a redirect to the SSO provider's auth page)
50
+ """
51
+ async def initiate_auth(operation: str) -> Response;
52
+
53
+ """Handle the OAuth callback from the SSO provider.
54
+
55
+ Args:
56
+ request: The FastAPI request object containing the OAuth callback data
57
+
58
+ Returns:
59
+ SSOUserInfo: Standardized user information from the provider
60
+
61
+ Raises:
62
+ Exception: If authentication fails or user info cannot be retrieved
63
+ """
64
+ async def handle_callback(request: Request) -> SSOUserInfo;
65
+
66
+ """Get the platform identifier for this provider.
67
+
68
+ Returns:
69
+ str: Platform name (e.g., 'google', 'microsoft', 'github')
70
+ """
71
+ def get_platform_name -> str;
72
+ }
@@ -34,12 +34,12 @@ class KubernetesConfig(BaseConfig) {
34
34
  app_mount_path: str = '/app',
35
35
  code_mount_path: str = '/code',
36
36
  workspace_path: str = '/code/workspace',
37
- # Runtime environment (defaults to official Jaseci repo)
38
37
  jaseci_repo_url: str = 'https://github.com/jaseci-labs/jaseci.git',
39
38
  jaseci_branch: str = 'main',
40
39
  jaseci_commit: (str | None) = None,
41
40
  install_jaseci: bool = True,
42
- additional_packages: list[str] = [];
41
+ additional_packages: list[str] = [],
42
+ plugin_versions: dict[str, str] = {};
43
43
 
44
44
  def init(
45
45
  self: KubernetesConfig,
@@ -75,12 +75,8 @@ class KubernetesConfig(BaseConfig) {
75
75
  jaseci_branch: str = 'main',
76
76
  jaseci_commit: (str | None) = None,
77
77
  install_jaseci: bool = True,
78
- additional_packages: list[str] = []
79
- # Runtime configuration
80
- # Storage configuration
81
- # Timing configuration
82
- # Paths
83
- # Runtime environment
78
+ additional_packages: list[str] = [],
79
+ plugin_versions: dict[str, str] = {}
84
80
  ) -> None {
85
81
  self.app_name = app_name;
86
82
  self.namespace = namespace;
@@ -121,6 +117,7 @@ class KubernetesConfig(BaseConfig) {
121
117
  self.jaseci_commit = jaseci_commit;
122
118
  self.install_jaseci = install_jaseci;
123
119
  self.additional_packages = additional_packages;
120
+ self.plugin_versions = plugin_versions;
124
121
  }
125
122
 
126
123
  override def to_dict(self: KubernetesConfig) -> dict[str, Any] {
@@ -162,7 +159,8 @@ class KubernetesConfig(BaseConfig) {
162
159
  'jaseci_branch': self.jaseci_branch,
163
160
  'jaseci_commit': self.jaseci_commit,
164
161
  'install_jaseci': self.install_jaseci,
165
- 'additional_packages': self.additional_packages
162
+ 'additional_packages': self.additional_packages,
163
+ 'plugin_versions': self.plugin_versions
166
164
  }
167
165
  );
168
166
  return base;
@@ -204,12 +202,8 @@ class KubernetesConfig(BaseConfig) {
204
202
  jaseci_branch=config.get('jaseci_branch', 'main'),
205
203
  jaseci_commit=config.get('jaseci_commit'),
206
204
  install_jaseci=config.get('install_jaseci', True),
207
- additional_packages=config.get('additional_packages', [])
208
- # Runtime configuration
209
- # Storage configuration
210
- # Timing configuration
211
- # Paths
212
- # Runtime environment
205
+ additional_packages=config.get('additional_packages', []),
206
+ plugin_versions=config.get('plugin_versions', {})
213
207
  );
214
208
  }
215
209
  }