jvserve 2.0.12__py3-none-any.whl → 2.0.15__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.
Potentially problematic release.
This version of jvserve might be problematic. Click here for more details.
- jvserve/__init__.py +1 -1
- jvserve/cli.py +15 -6
- jvserve/lib/agent_interface.py +210 -53
- {jvserve-2.0.12.dist-info → jvserve-2.0.15.dist-info}/METADATA +1 -1
- jvserve-2.0.15.dist-info/RECORD +13 -0
- {jvserve-2.0.12.dist-info → jvserve-2.0.15.dist-info}/WHEEL +1 -1
- jvserve-2.0.12.dist-info/RECORD +0 -13
- {jvserve-2.0.12.dist-info → jvserve-2.0.15.dist-info}/entry_points.txt +0 -0
- {jvserve-2.0.12.dist-info → jvserve-2.0.15.dist-info}/licenses/LICENSE +0 -0
- {jvserve-2.0.12.dist-info → jvserve-2.0.15.dist-info}/top_level.txt +0 -0
jvserve/__init__.py
CHANGED
jvserve/cli.py
CHANGED
|
@@ -7,14 +7,13 @@ from contextlib import asynccontextmanager
|
|
|
7
7
|
from typing import AsyncIterator, Optional
|
|
8
8
|
|
|
9
9
|
from dotenv import load_dotenv
|
|
10
|
-
from fastapi.responses import FileResponse, StreamingResponse
|
|
10
|
+
from fastapi.responses import FileResponse, Response, StreamingResponse
|
|
11
11
|
from jac_cloud.jaseci.security import authenticator
|
|
12
12
|
from jac_cloud.plugin.jaseci import NodeAnchor
|
|
13
13
|
from jaclang.cli.cmdreg import cmd_registry
|
|
14
14
|
from jaclang.plugin.default import hookimpl
|
|
15
15
|
from jaclang.runtimelib.context import ExecutionContext
|
|
16
16
|
from jaclang.runtimelib.machine import JacMachine
|
|
17
|
-
from requests import Response
|
|
18
17
|
from uvicorn import run as _run
|
|
19
18
|
|
|
20
19
|
from jvserve.lib.agent_interface import AgentInterface
|
|
@@ -216,8 +215,7 @@ class JacCmd:
|
|
|
216
215
|
) -> None:
|
|
217
216
|
"""Launch the file proxy server for remote files."""
|
|
218
217
|
# load FastAPI
|
|
219
|
-
from
|
|
220
|
-
from fastapi import FastAPI, HTTPException
|
|
218
|
+
from fastapi import FastAPI
|
|
221
219
|
from fastapi.middleware.cors import CORSMiddleware
|
|
222
220
|
|
|
223
221
|
# Setup custom routes
|
|
@@ -238,21 +236,32 @@ class JacCmd:
|
|
|
238
236
|
@app.get("/files/{file_path:path}", response_model=None)
|
|
239
237
|
async def serve_file(
|
|
240
238
|
file_path: str,
|
|
241
|
-
) -> Response:
|
|
239
|
+
) -> FileResponse | StreamingResponse | Response:
|
|
240
|
+
descriptor_path = os.environ["JIVAS_DESCRIPTOR_ROOT_PATH"]
|
|
241
|
+
if descriptor_path and descriptor_path in file_path:
|
|
242
|
+
return Response(status_code=403)
|
|
243
|
+
|
|
242
244
|
return serve_proxied_file(file_path)
|
|
243
245
|
|
|
244
246
|
@app.get("/f/{file_id:path}", response_model=None)
|
|
245
247
|
async def get_proxied_file(
|
|
246
248
|
file_id: str,
|
|
247
|
-
) -> Response:
|
|
249
|
+
) -> FileResponse | StreamingResponse | Response:
|
|
250
|
+
from bson import ObjectId
|
|
251
|
+
from fastapi import HTTPException
|
|
252
|
+
|
|
248
253
|
params = file_id.split("/")
|
|
249
254
|
object_id = params[0]
|
|
250
255
|
|
|
251
256
|
# mongo db collection
|
|
252
257
|
collection = NodeAnchor.Collection.get_collection("url_proxies")
|
|
253
258
|
file_details = collection.find_one({"_id": ObjectId(object_id)})
|
|
259
|
+
descriptor_path = os.environ["JIVAS_DESCRIPTOR_ROOT_PATH"]
|
|
254
260
|
|
|
255
261
|
if file_details:
|
|
262
|
+
if descriptor_path and descriptor_path in file_details["path"]:
|
|
263
|
+
return Response(status_code=403)
|
|
264
|
+
|
|
256
265
|
return serve_proxied_file(file_details["path"])
|
|
257
266
|
|
|
258
267
|
raise HTTPException(status_code=404, detail="File not found")
|
jvserve/lib/agent_interface.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Agent Interface class and methods for interaction with Jivas."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
@@ -158,77 +159,211 @@ class AgentInterface:
|
|
|
158
159
|
|
|
159
160
|
return response
|
|
160
161
|
|
|
162
|
+
@staticmethod
|
|
163
|
+
def get_action_data(agent_id: str, action_label: str) -> dict:
|
|
164
|
+
"""Retrieves the data for a specific action of an agent."""
|
|
165
|
+
|
|
166
|
+
action_data = {}
|
|
167
|
+
ctx = AgentInterface.load_context()
|
|
168
|
+
|
|
169
|
+
if not ctx:
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
# TODO : raise error in the event agent id is invalid
|
|
173
|
+
AgentInterface.LOGGER.debug(
|
|
174
|
+
f"attempting to interact with agent {agent_id} with user root {ctx.root}..."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
actions = _Jac.spawn_call(
|
|
179
|
+
ctx.entry_node.architype,
|
|
180
|
+
AgentInterface.spawn_walker(
|
|
181
|
+
walker_name="list_actions",
|
|
182
|
+
attributes={"agent_id": agent_id},
|
|
183
|
+
module_name="agent.action.list_actions",
|
|
184
|
+
),
|
|
185
|
+
).actions
|
|
186
|
+
|
|
187
|
+
if actions:
|
|
188
|
+
for action in actions:
|
|
189
|
+
if action.get("label") == action_label:
|
|
190
|
+
action_data = action
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
AgentInterface.EXPIRATION = None
|
|
195
|
+
AgentInterface.LOGGER.error(
|
|
196
|
+
f"an exception occurred: {e}, {traceback.format_exc()}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
ctx.close()
|
|
200
|
+
return action_data
|
|
201
|
+
|
|
161
202
|
@staticmethod
|
|
162
203
|
async def action_walker_exec(
|
|
163
|
-
agent_id: str = Form(
|
|
164
|
-
|
|
165
|
-
walker: str = Form(
|
|
204
|
+
agent_id: Optional[str] = Form(None), # noqa: B008
|
|
205
|
+
action: Optional[str] = Form(None), # noqa: B008
|
|
206
|
+
walker: Optional[str] = Form(None), # noqa: B008
|
|
166
207
|
args: Optional[str] = Form(None), # noqa: B008
|
|
167
208
|
attachments: List[UploadFile] = File(default_factory=list), # noqa: B008
|
|
168
209
|
) -> JSONResponse:
|
|
169
|
-
"""
|
|
210
|
+
"""
|
|
211
|
+
Execute a named walker exposed by an action within context.
|
|
212
|
+
Capable of handling JSON or file data depending on request.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
agent_id: ID of the agent
|
|
216
|
+
action: Name of the action
|
|
217
|
+
walker: Name of the walker to execute
|
|
218
|
+
args: JSON string of additional arguments
|
|
219
|
+
attachments: List of uploaded files
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
JSONResponse: Response containing walker output or error message
|
|
223
|
+
"""
|
|
224
|
+
ctx = None
|
|
225
|
+
try:
|
|
226
|
+
# Validate required parameters
|
|
227
|
+
if walker is None or agent_id is None or action is None:
|
|
228
|
+
AgentInterface.LOGGER.error("Missing required parameters")
|
|
229
|
+
return JSONResponse(
|
|
230
|
+
status_code=400, # 400 (Bad Request)
|
|
231
|
+
content={"error": "Missing required parameters"},
|
|
232
|
+
)
|
|
170
233
|
|
|
171
|
-
|
|
234
|
+
# Get action data to resolve module
|
|
235
|
+
if agent_id is None or action is None:
|
|
236
|
+
AgentInterface.LOGGER.error("agent_id and action must not be None")
|
|
237
|
+
return JSONResponse(
|
|
238
|
+
status_code=400,
|
|
239
|
+
content={"error": "agent_id and action must not be None"},
|
|
240
|
+
)
|
|
172
241
|
|
|
173
|
-
|
|
242
|
+
action_data = AgentInterface.get_action_data(agent_id, action)
|
|
243
|
+
if not action_data:
|
|
244
|
+
AgentInterface.LOGGER.error(
|
|
245
|
+
f"Action {action} not found for agent {agent_id}"
|
|
246
|
+
)
|
|
247
|
+
return JSONResponse(
|
|
248
|
+
status_code=404,
|
|
249
|
+
content={"error": "Action not found"},
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
module_root = (
|
|
253
|
+
action_data.get("_package", {}).get("config", {}).get("module_root", "")
|
|
254
|
+
)
|
|
255
|
+
if not module_root:
|
|
256
|
+
AgentInterface.LOGGER.error(
|
|
257
|
+
f"Module not found for action {action} of agent {agent_id}"
|
|
258
|
+
)
|
|
259
|
+
return JSONResponse(
|
|
260
|
+
status_code=404,
|
|
261
|
+
content={"error": "Module not found"},
|
|
262
|
+
)
|
|
174
263
|
|
|
175
|
-
#
|
|
264
|
+
# Load execution context
|
|
265
|
+
ctx = await AgentInterface.load_context_async()
|
|
266
|
+
if not ctx:
|
|
267
|
+
AgentInterface.LOGGER.error(f"Unable to execute {walker}")
|
|
268
|
+
return JSONResponse(
|
|
269
|
+
status_code=500,
|
|
270
|
+
content={"error": "Failed to load execution context"},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Prepare attributes
|
|
176
274
|
attributes: Dict[str, Any] = {"agent_id": agent_id}
|
|
177
275
|
|
|
178
|
-
#
|
|
276
|
+
# Parse additional arguments if provided
|
|
179
277
|
if args:
|
|
180
|
-
|
|
278
|
+
try:
|
|
279
|
+
attributes.update(json.loads(args))
|
|
280
|
+
except json.JSONDecodeError as e:
|
|
281
|
+
AgentInterface.LOGGER.error(f"Invalid JSON in args: {e}")
|
|
282
|
+
return JSONResponse(
|
|
283
|
+
status_code=400,
|
|
284
|
+
content={"error": "Invalid JSON in arguments"},
|
|
285
|
+
)
|
|
181
286
|
|
|
182
|
-
#
|
|
287
|
+
# Process uploaded files
|
|
183
288
|
if attachments:
|
|
184
289
|
attributes["files"] = []
|
|
185
290
|
for file in attachments:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
291
|
+
try:
|
|
292
|
+
attributes["files"].append(
|
|
293
|
+
{
|
|
294
|
+
"name": file.filename,
|
|
295
|
+
"type": file.content_type,
|
|
296
|
+
"content": await file.read(),
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
AgentInterface.LOGGER.error(
|
|
301
|
+
f"Failed to process file {file.filename}: {e}"
|
|
302
|
+
)
|
|
303
|
+
continue # Skip problematic files or return error if critical
|
|
193
304
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
305
|
+
# Execute the walker
|
|
306
|
+
walker_response = _Jac.spawn_call(
|
|
307
|
+
ctx.entry_node.architype,
|
|
308
|
+
AgentInterface.spawn_walker(
|
|
309
|
+
walker_name=walker,
|
|
310
|
+
attributes=attributes,
|
|
311
|
+
module_name=f"{module_root}.{walker}",
|
|
312
|
+
),
|
|
313
|
+
).response
|
|
199
314
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
315
|
+
# Handle different response types appropriately
|
|
316
|
+
try:
|
|
317
|
+
# If it's already a proper Response object, return as-is
|
|
318
|
+
if isinstance(walker_response, requests.Response):
|
|
319
|
+
return walker_response
|
|
205
320
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
module = f"{module_root}.{walker}"
|
|
321
|
+
# If it's a Pydantic model or similar complex object with dict representation
|
|
322
|
+
if hasattr(walker_response, "dict"):
|
|
323
|
+
return JSONResponse(status_code=200, content=walker_response.dict())
|
|
210
324
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
325
|
+
# If it's a list of complex objects
|
|
326
|
+
if (
|
|
327
|
+
isinstance(walker_response, list)
|
|
328
|
+
and len(walker_response) > 0
|
|
329
|
+
and hasattr(walker_response[0], "dict")
|
|
330
|
+
):
|
|
331
|
+
return JSONResponse(
|
|
332
|
+
status_code=200,
|
|
333
|
+
content=[item.dict() for item in walker_response],
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# For other JSON-serializable types
|
|
337
|
+
try:
|
|
338
|
+
return JSONResponse(status_code=200, content=walker_response)
|
|
339
|
+
except TypeError:
|
|
340
|
+
# Fallback to string representation if not directly JSON-serializable
|
|
341
|
+
return JSONResponse(
|
|
342
|
+
status_code=200, content={"result": str(walker_response)}
|
|
343
|
+
)
|
|
220
344
|
|
|
221
345
|
except Exception as e:
|
|
222
|
-
AgentInterface.
|
|
223
|
-
|
|
224
|
-
|
|
346
|
+
AgentInterface.LOGGER.error(f"Failed to format walker response: {e}")
|
|
347
|
+
return JSONResponse(
|
|
348
|
+
status_code=500,
|
|
349
|
+
content={"error": "Failed to format response", "details": str(e)},
|
|
225
350
|
)
|
|
226
|
-
else:
|
|
227
|
-
AgentInterface.LOGGER.error(f"unable to execute {walker}")
|
|
228
351
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
352
|
+
except Exception as e:
|
|
353
|
+
AgentInterface.EXPIRATION = None
|
|
354
|
+
AgentInterface.LOGGER.error(
|
|
355
|
+
f"Exception occurred: {str(e)}\n{traceback.format_exc()}"
|
|
356
|
+
)
|
|
357
|
+
return JSONResponse(
|
|
358
|
+
status_code=500,
|
|
359
|
+
content={"error": "Internal server error", "details": str(e)},
|
|
360
|
+
)
|
|
361
|
+
finally:
|
|
362
|
+
if ctx:
|
|
363
|
+
try:
|
|
364
|
+
ctx.close()
|
|
365
|
+
except Exception as e:
|
|
366
|
+
AgentInterface.LOGGER.error(f"Error closing context: {str(e)}")
|
|
232
367
|
|
|
233
368
|
class InteractPayload(BaseModel):
|
|
234
369
|
"""Payload for interacting with the agent."""
|
|
@@ -243,7 +378,7 @@ class AgentInterface:
|
|
|
243
378
|
streaming: Optional[bool] = None
|
|
244
379
|
|
|
245
380
|
@staticmethod
|
|
246
|
-
def interact(payload: InteractPayload) -> dict:
|
|
381
|
+
def interact(payload: InteractPayload, request: Request) -> dict:
|
|
247
382
|
"""Interact with the agent."""
|
|
248
383
|
response = None
|
|
249
384
|
ctx = AgentInterface.load_context()
|
|
@@ -291,7 +426,7 @@ class AgentInterface:
|
|
|
291
426
|
interaction_node = response.interaction_node
|
|
292
427
|
|
|
293
428
|
async def generate(
|
|
294
|
-
generator: Iterator,
|
|
429
|
+
generator: Iterator, request: Request
|
|
295
430
|
) -> AsyncGenerator[str, None]:
|
|
296
431
|
"""
|
|
297
432
|
Asynchronously yield data chunks from a response generator in Server-Sent Events (SSE) format.
|
|
@@ -304,11 +439,11 @@ class AgentInterface:
|
|
|
304
439
|
"""
|
|
305
440
|
full_text = ""
|
|
306
441
|
total_tokens = 0
|
|
442
|
+
|
|
307
443
|
try:
|
|
308
444
|
for chunk in generator:
|
|
309
445
|
full_text += chunk.content
|
|
310
446
|
total_tokens += 1 # each chunk is a token, let's tally
|
|
311
|
-
await sleep(0.025)
|
|
312
447
|
yield (
|
|
313
448
|
"data: "
|
|
314
449
|
+ json.dumps(
|
|
@@ -324,6 +459,7 @@ class AgentInterface:
|
|
|
324
459
|
)
|
|
325
460
|
+ "\n\n"
|
|
326
461
|
)
|
|
462
|
+
await sleep(0.025)
|
|
327
463
|
# Update the interaction node with the fully generated text
|
|
328
464
|
actx = await AgentInterface.load_context_async()
|
|
329
465
|
try:
|
|
@@ -336,7 +472,7 @@ class AgentInterface:
|
|
|
336
472
|
attributes={
|
|
337
473
|
"interaction_data": interaction_node.export(),
|
|
338
474
|
},
|
|
339
|
-
module_name="jivas.agent.
|
|
475
|
+
module_name="jivas.agent.memory.update_interaction",
|
|
340
476
|
),
|
|
341
477
|
)
|
|
342
478
|
finally:
|
|
@@ -347,9 +483,30 @@ class AgentInterface:
|
|
|
347
483
|
AgentInterface.LOGGER.error(
|
|
348
484
|
f"Exception in streaming generator: {e}, {traceback.format_exc()}"
|
|
349
485
|
)
|
|
486
|
+
except asyncio.CancelledError:
|
|
487
|
+
AgentInterface.LOGGER.error(
|
|
488
|
+
"Client disconnected. Aborting stream."
|
|
489
|
+
)
|
|
490
|
+
actx = await AgentInterface.load_context_async()
|
|
491
|
+
try:
|
|
492
|
+
interaction_node.set_text_message(message=full_text)
|
|
493
|
+
interaction_node.add_tokens(total_tokens)
|
|
494
|
+
_Jac.spawn_call(
|
|
495
|
+
NodeAnchor.ref(interaction_node.id).architype,
|
|
496
|
+
AgentInterface.spawn_walker(
|
|
497
|
+
walker_name="update_interaction",
|
|
498
|
+
attributes={
|
|
499
|
+
"interaction_data": interaction_node.export(),
|
|
500
|
+
},
|
|
501
|
+
module_name="jivas.agent.memory.update_interaction",
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
finally:
|
|
505
|
+
if actx:
|
|
506
|
+
actx.close()
|
|
350
507
|
|
|
351
508
|
return StreamingResponse(
|
|
352
|
-
generate(response.generator),
|
|
509
|
+
generate(response.generator, request),
|
|
353
510
|
media_type="text/event-stream",
|
|
354
511
|
)
|
|
355
512
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
jvserve/__init__.py,sha256=S9zx3emgI3a17M2NmUFp3sdzrZ9SDFwDKYAUR_TjDxE,191
|
|
2
|
+
jvserve/cli.py,sha256=NJD4ehtKnhOvsz-vrFh9lz3P4bnxqz-uqTr8QItljZc,8897
|
|
3
|
+
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
+
jvserve/lib/agent_interface.py,sha256=d2trRXgLjUzciGSRwYD2Rl-TPnyJVpUKrgG1iU7CGyw,37768
|
|
5
|
+
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
+
jvserve/lib/file_interface.py,sha256=sqwTmBnZsVFQUGt7mE1EWA-jHnZlRtBAFeb4Rb_QgQ8,6079
|
|
7
|
+
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
8
|
+
jvserve-2.0.15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
+
jvserve-2.0.15.dist-info/METADATA,sha256=eXjQb1zag7Si-czz5V4wX60-6wr9E2eyz3yIETUF9i0,4791
|
|
10
|
+
jvserve-2.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
jvserve-2.0.15.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
12
|
+
jvserve-2.0.15.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
13
|
+
jvserve-2.0.15.dist-info/RECORD,,
|
jvserve-2.0.12.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
jvserve/__init__.py,sha256=hT0e0Ny1xLqK83tJ8AFjLfpMvrs5MB9of01udMpC4Cw,191
|
|
2
|
-
jvserve/cli.py,sha256=uWUfujG-YX-jXiGWeMo66d4p3f1Aix7zfNTr7xwUqqk,8380
|
|
3
|
-
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
-
jvserve/lib/agent_interface.py,sha256=vGJQ9A_9kWfa6YoW7BiidZO6Huf-0XSlunMLMNGqgws,31053
|
|
5
|
-
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
-
jvserve/lib/file_interface.py,sha256=sqwTmBnZsVFQUGt7mE1EWA-jHnZlRtBAFeb4Rb_QgQ8,6079
|
|
7
|
-
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
8
|
-
jvserve-2.0.12.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
-
jvserve-2.0.12.dist-info/METADATA,sha256=QySsRhK1sCoAVIfVvH_a29JrXShLRLId_kmqBM1pGo8,4791
|
|
10
|
-
jvserve-2.0.12.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
11
|
-
jvserve-2.0.12.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
12
|
-
jvserve-2.0.12.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
13
|
-
jvserve-2.0.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|