jaf-py 2.5.4__py3-none-any.whl → 2.5.5__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.
- jaf/__init__.py +1 -1
- jaf/core/engine.py +158 -108
- jaf/core/tracing.py +1 -1
- jaf/core/types.py +6 -0
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/METADATA +2 -2
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/RECORD +10 -10
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.4.dist-info → jaf_py-2.5.5.dist-info}/top_level.txt +0 -0
jaf/__init__.py
CHANGED
|
@@ -191,7 +191,7 @@ def generate_run_id() -> RunId:
|
|
|
191
191
|
"""Generate a new run ID."""
|
|
192
192
|
return create_run_id(str(uuid.uuid4()))
|
|
193
193
|
|
|
194
|
-
__version__ = "2.5.
|
|
194
|
+
__version__ = "2.5.5"
|
|
195
195
|
__all__ = [
|
|
196
196
|
# Core types and functions
|
|
197
197
|
"TraceId", "RunId", "ValidationResult", "Message", "ModelConfig",
|
jaf/core/engine.py
CHANGED
|
@@ -527,6 +527,17 @@ async def _run_internal(
|
|
|
527
527
|
"gpt-4o"
|
|
528
528
|
)
|
|
529
529
|
|
|
530
|
+
# Apply before_llm_call callback if provided
|
|
531
|
+
if config.before_llm_call:
|
|
532
|
+
if asyncio.iscoroutinefunction(config.before_llm_call):
|
|
533
|
+
state = await config.before_llm_call(state, current_agent)
|
|
534
|
+
else:
|
|
535
|
+
result = config.before_llm_call(state, current_agent)
|
|
536
|
+
if asyncio.iscoroutine(result):
|
|
537
|
+
state = await result
|
|
538
|
+
else:
|
|
539
|
+
state = result
|
|
540
|
+
|
|
530
541
|
# Emit LLM call start event
|
|
531
542
|
if config.on_event:
|
|
532
543
|
config.on_event(LLMCallStartEvent(data=to_event_data(LLMCallStartEventData(
|
|
@@ -538,121 +549,157 @@ async def _run_internal(
|
|
|
538
549
|
messages=state.messages
|
|
539
550
|
))))
|
|
540
551
|
|
|
541
|
-
#
|
|
552
|
+
# Retry logic for empty LLM responses
|
|
542
553
|
llm_response: Dict[str, Any]
|
|
543
554
|
assistant_event_streamed = False
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
})
|
|
569
|
-
target = partial_tool_calls[idx]
|
|
570
|
-
# id
|
|
571
|
-
tc_id = getattr(tcd, "id", None)
|
|
572
|
-
if tc_id:
|
|
573
|
-
target["id"] = tc_id
|
|
574
|
-
# function fields
|
|
575
|
-
fn = getattr(tcd, "function", None)
|
|
576
|
-
if fn is not None:
|
|
577
|
-
fn_name = getattr(fn, "name", None)
|
|
578
|
-
if fn_name:
|
|
579
|
-
target["function"]["name"] = fn_name
|
|
580
|
-
args_delta = getattr(fn, "arguments_delta", None)
|
|
581
|
-
if args_delta:
|
|
582
|
-
target["function"]["arguments"] += args_delta
|
|
583
|
-
|
|
584
|
-
# Emit partial assistant message when something changed
|
|
585
|
-
if delta_text or tcd is not None:
|
|
586
|
-
assistant_event_streamed = True
|
|
587
|
-
# Normalize tool_calls for message
|
|
588
|
-
message_tool_calls = None
|
|
589
|
-
if len(partial_tool_calls) > 0:
|
|
590
|
-
message_tool_calls = []
|
|
591
|
-
for i, tc in enumerate(partial_tool_calls):
|
|
592
|
-
arguments = tc["function"]["arguments"]
|
|
593
|
-
if isinstance(arguments, str):
|
|
594
|
-
arguments = _normalize_tool_call_arguments(arguments)
|
|
595
|
-
message_tool_calls.append({
|
|
596
|
-
"id": tc["id"] or f"call_{i}",
|
|
555
|
+
|
|
556
|
+
for retry_attempt in range(config.max_empty_response_retries + 1):
|
|
557
|
+
# Get completion from model provider, prefer streaming if available
|
|
558
|
+
get_stream = getattr(config.model_provider, "get_completion_stream", None)
|
|
559
|
+
if callable(get_stream):
|
|
560
|
+
try:
|
|
561
|
+
aggregated_text = ""
|
|
562
|
+
# Working array of partial tool calls
|
|
563
|
+
partial_tool_calls: List[Dict[str, Any]] = []
|
|
564
|
+
|
|
565
|
+
async for chunk in get_stream(state, current_agent, config): # type: ignore[arg-type]
|
|
566
|
+
# Text deltas
|
|
567
|
+
delta_text = getattr(chunk, "delta", None)
|
|
568
|
+
if delta_text:
|
|
569
|
+
aggregated_text += delta_text
|
|
570
|
+
|
|
571
|
+
# Tool call deltas
|
|
572
|
+
tcd = getattr(chunk, "tool_call_delta", None)
|
|
573
|
+
if tcd is not None:
|
|
574
|
+
idx = getattr(tcd, "index", 0) or 0
|
|
575
|
+
# Ensure slot exists
|
|
576
|
+
while len(partial_tool_calls) <= idx:
|
|
577
|
+
partial_tool_calls.append({
|
|
578
|
+
"id": None,
|
|
597
579
|
"type": "function",
|
|
598
|
-
"function": {
|
|
599
|
-
"name": tc["function"]["name"] or "",
|
|
600
|
-
"arguments": arguments
|
|
601
|
-
}
|
|
580
|
+
"function": {"name": None, "arguments": ""}
|
|
602
581
|
})
|
|
582
|
+
target = partial_tool_calls[idx]
|
|
583
|
+
# id
|
|
584
|
+
tc_id = getattr(tcd, "id", None)
|
|
585
|
+
if tc_id:
|
|
586
|
+
target["id"] = tc_id
|
|
587
|
+
# function fields
|
|
588
|
+
fn = getattr(tcd, "function", None)
|
|
589
|
+
if fn is not None:
|
|
590
|
+
fn_name = getattr(fn, "name", None)
|
|
591
|
+
if fn_name:
|
|
592
|
+
target["function"]["name"] = fn_name
|
|
593
|
+
args_delta = getattr(fn, "arguments_delta", None)
|
|
594
|
+
if args_delta:
|
|
595
|
+
target["function"]["arguments"] += args_delta
|
|
596
|
+
|
|
597
|
+
# Emit partial assistant message when something changed
|
|
598
|
+
if delta_text or tcd is not None:
|
|
599
|
+
assistant_event_streamed = True
|
|
600
|
+
# Normalize tool_calls for message
|
|
601
|
+
message_tool_calls = None
|
|
602
|
+
if len(partial_tool_calls) > 0:
|
|
603
|
+
message_tool_calls = []
|
|
604
|
+
for i, tc in enumerate(partial_tool_calls):
|
|
605
|
+
arguments = tc["function"]["arguments"]
|
|
606
|
+
if isinstance(arguments, str):
|
|
607
|
+
arguments = _normalize_tool_call_arguments(arguments)
|
|
608
|
+
message_tool_calls.append({
|
|
609
|
+
"id": tc["id"] or f"call_{i}",
|
|
610
|
+
"type": "function",
|
|
611
|
+
"function": {
|
|
612
|
+
"name": tc["function"]["name"] or "",
|
|
613
|
+
"arguments": arguments
|
|
614
|
+
}
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
partial_msg = Message(
|
|
618
|
+
role=ContentRole.ASSISTANT,
|
|
619
|
+
content=aggregated_text or "",
|
|
620
|
+
tool_calls=None if not message_tool_calls else [
|
|
621
|
+
ToolCall(
|
|
622
|
+
id=mc["id"],
|
|
623
|
+
type="function",
|
|
624
|
+
function=ToolCallFunction(
|
|
625
|
+
name=mc["function"]["name"],
|
|
626
|
+
arguments=_normalize_tool_call_arguments(mc["function"]["arguments"])
|
|
627
|
+
),
|
|
628
|
+
) for mc in message_tool_calls
|
|
629
|
+
],
|
|
630
|
+
)
|
|
631
|
+
try:
|
|
632
|
+
if config.on_event:
|
|
633
|
+
config.on_event(AssistantMessageEvent(data=to_event_data(
|
|
634
|
+
AssistantMessageEventData(message=partial_msg)
|
|
635
|
+
)))
|
|
636
|
+
except Exception as _e:
|
|
637
|
+
# Do not fail the run on callback errors
|
|
638
|
+
pass
|
|
639
|
+
|
|
640
|
+
# Build final response object compatible with downstream logic
|
|
641
|
+
final_tool_calls = None
|
|
642
|
+
if len(partial_tool_calls) > 0:
|
|
643
|
+
final_tool_calls = []
|
|
644
|
+
for i, tc in enumerate(partial_tool_calls):
|
|
645
|
+
arguments = tc["function"]["arguments"]
|
|
646
|
+
if isinstance(arguments, str):
|
|
647
|
+
arguments = _normalize_tool_call_arguments(arguments)
|
|
648
|
+
final_tool_calls.append({
|
|
649
|
+
"id": tc["id"] or f"call_{i}",
|
|
650
|
+
"type": "function",
|
|
651
|
+
"function": {
|
|
652
|
+
"name": tc["function"]["name"] or "",
|
|
653
|
+
"arguments": arguments
|
|
654
|
+
}
|
|
655
|
+
})
|
|
603
656
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
content
|
|
607
|
-
tool_calls
|
|
608
|
-
|
|
609
|
-
id=mc["id"],
|
|
610
|
-
type="function",
|
|
611
|
-
function=ToolCallFunction(
|
|
612
|
-
name=mc["function"]["name"],
|
|
613
|
-
arguments=_normalize_tool_call_arguments(mc["function"]["arguments"])
|
|
614
|
-
),
|
|
615
|
-
) for mc in message_tool_calls
|
|
616
|
-
],
|
|
617
|
-
)
|
|
618
|
-
try:
|
|
619
|
-
if config.on_event:
|
|
620
|
-
config.on_event(AssistantMessageEvent(data=to_event_data(
|
|
621
|
-
AssistantMessageEventData(message=partial_msg)
|
|
622
|
-
)))
|
|
623
|
-
except Exception as _e:
|
|
624
|
-
# Do not fail the run on callback errors
|
|
625
|
-
pass
|
|
626
|
-
|
|
627
|
-
# Build final response object compatible with downstream logic
|
|
628
|
-
final_tool_calls = None
|
|
629
|
-
if len(partial_tool_calls) > 0:
|
|
630
|
-
final_tool_calls = []
|
|
631
|
-
for i, tc in enumerate(partial_tool_calls):
|
|
632
|
-
arguments = tc["function"]["arguments"]
|
|
633
|
-
if isinstance(arguments, str):
|
|
634
|
-
arguments = _normalize_tool_call_arguments(arguments)
|
|
635
|
-
final_tool_calls.append({
|
|
636
|
-
"id": tc["id"] or f"call_{i}",
|
|
637
|
-
"type": "function",
|
|
638
|
-
"function": {
|
|
639
|
-
"name": tc["function"]["name"] or "",
|
|
640
|
-
"arguments": arguments
|
|
641
|
-
}
|
|
642
|
-
})
|
|
643
|
-
|
|
644
|
-
llm_response = {
|
|
645
|
-
"message": {
|
|
646
|
-
"content": aggregated_text or None,
|
|
647
|
-
"tool_calls": final_tool_calls
|
|
657
|
+
llm_response = {
|
|
658
|
+
"message": {
|
|
659
|
+
"content": aggregated_text or None,
|
|
660
|
+
"tool_calls": final_tool_calls
|
|
661
|
+
}
|
|
648
662
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
663
|
+
except Exception:
|
|
664
|
+
# Fallback to non-streaming on error
|
|
665
|
+
assistant_event_streamed = False
|
|
666
|
+
llm_response = await config.model_provider.get_completion(state, current_agent, config)
|
|
667
|
+
else:
|
|
653
668
|
llm_response = await config.model_provider.get_completion(state, current_agent, config)
|
|
654
|
-
|
|
655
|
-
|
|
669
|
+
|
|
670
|
+
# Check if response has meaningful content
|
|
671
|
+
has_content = llm_response.get('message', {}).get('content')
|
|
672
|
+
has_tool_calls = llm_response.get('message', {}).get('tool_calls')
|
|
673
|
+
|
|
674
|
+
# If we got a valid response, break out of retry loop
|
|
675
|
+
if has_content or has_tool_calls:
|
|
676
|
+
break
|
|
677
|
+
|
|
678
|
+
# If this is not the last attempt, retry with exponential backoff
|
|
679
|
+
if retry_attempt < config.max_empty_response_retries:
|
|
680
|
+
delay = config.empty_response_retry_delay * (2 ** retry_attempt)
|
|
681
|
+
if config.log_empty_responses:
|
|
682
|
+
print(f"[JAF:ENGINE] Empty LLM response on attempt {retry_attempt + 1}/{config.max_empty_response_retries + 1}, retrying in {delay:.1f}s...")
|
|
683
|
+
print(f"[JAF:ENGINE] Response had message: {bool(llm_response.get('message'))}, content: {bool(has_content)}, tool_calls: {bool(has_tool_calls)}")
|
|
684
|
+
await asyncio.sleep(delay)
|
|
685
|
+
else:
|
|
686
|
+
# Last attempt failed, log detailed diagnostic info
|
|
687
|
+
if config.log_empty_responses:
|
|
688
|
+
print(f"[JAF:ENGINE] Empty LLM response after {config.max_empty_response_retries + 1} attempts")
|
|
689
|
+
print(f"[JAF:ENGINE] Agent: {current_agent.name}, Model: {model}")
|
|
690
|
+
print(f"[JAF:ENGINE] Message count: {len(state.messages)}, Turn: {state.turn_count}")
|
|
691
|
+
print(f"[JAF:ENGINE] Response structure: {json.dumps(llm_response, indent=2)[:1000]}")
|
|
692
|
+
|
|
693
|
+
# Apply after_llm_call callback if provided
|
|
694
|
+
if config.after_llm_call:
|
|
695
|
+
if asyncio.iscoroutinefunction(config.after_llm_call):
|
|
696
|
+
llm_response = await config.after_llm_call(state, llm_response)
|
|
697
|
+
else:
|
|
698
|
+
result = config.after_llm_call(state, llm_response)
|
|
699
|
+
if asyncio.iscoroutine(result):
|
|
700
|
+
llm_response = await result
|
|
701
|
+
else:
|
|
702
|
+
llm_response = result
|
|
656
703
|
|
|
657
704
|
# Emit LLM call end event
|
|
658
705
|
if config.on_event:
|
|
@@ -665,6 +712,9 @@ async def _run_internal(
|
|
|
665
712
|
|
|
666
713
|
# Check if response has message
|
|
667
714
|
if not llm_response.get('message'):
|
|
715
|
+
if config.log_empty_responses:
|
|
716
|
+
print(f"[JAF:ENGINE] ERROR: No message in LLM response")
|
|
717
|
+
print(f"[JAF:ENGINE] Response structure: {json.dumps(llm_response, indent=2)[:500]}")
|
|
668
718
|
return RunResult(
|
|
669
719
|
final_state=state,
|
|
670
720
|
outcome=ErrorOutcome(error=ModelBehaviorError(
|
jaf/core/tracing.py
CHANGED
jaf/core/types.py
CHANGED
|
@@ -892,6 +892,12 @@ class RunConfig(Generic[Ctx]):
|
|
|
892
892
|
default_fast_model: Optional[str] = None # Default model for fast operations like guardrails
|
|
893
893
|
default_tool_timeout: Optional[float] = 300.0 # Default timeout for tool execution in seconds
|
|
894
894
|
approval_storage: Optional['ApprovalStorage'] = None # Storage for approval decisions
|
|
895
|
+
before_llm_call: Optional[Callable[[RunState[Ctx], Agent[Ctx, Any]], Union[RunState[Ctx], Awaitable[RunState[Ctx]]]]] = None # Callback before LLM call - can modify context/messages
|
|
896
|
+
after_llm_call: Optional[Callable[[RunState[Ctx], ModelCompletionResponse], Union[ModelCompletionResponse, Awaitable[ModelCompletionResponse]]]] = None # Callback after LLM call - can process response
|
|
897
|
+
max_empty_response_retries: int = 3 # Maximum retries when LLM returns empty response
|
|
898
|
+
empty_response_retry_delay: float = 1.0 # Initial delay in seconds before retrying empty response (uses exponential backoff)
|
|
899
|
+
log_empty_responses: bool = True # Whether to log diagnostic info for empty responses
|
|
900
|
+
|
|
895
901
|
|
|
896
902
|
# Regeneration types for conversation management
|
|
897
903
|
@dataclass(frozen=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.5
|
|
4
4
|
Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
|
|
5
5
|
Author: JAF Contributors
|
|
6
6
|
Maintainer: JAF Contributors
|
|
@@ -82,7 +82,7 @@ Dynamic: license-file
|
|
|
82
82
|
|
|
83
83
|
<!--  -->
|
|
84
84
|
|
|
85
|
-
[](https://github.com/xynehq/jaf-py)
|
|
86
86
|
[](https://www.python.org/)
|
|
87
87
|
[](https://xynehq.github.io/jaf-py/)
|
|
88
88
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
jaf/__init__.py,sha256=
|
|
1
|
+
jaf/__init__.py,sha256=jzou4ny01tsRBZAb-ojUf4pjyu5LA32FRDt_QiHjSPs,8260
|
|
2
2
|
jaf/cli.py,sha256=Af4di_NZ7rZ4wFl0R4EZh611NgJ--TL03vNyZ2M1_FY,8477
|
|
3
3
|
jaf/exceptions.py,sha256=nl8JY355u7oTXB3PmC_LhnUaL8fzk2K4EaWM4fVpMPE,9196
|
|
4
4
|
jaf/a2a/__init__.py,sha256=p4YVthZH0ow1ZECqWTQ0aQl8JWySYZb25jlzZJ09na4,7662
|
|
@@ -42,7 +42,7 @@ jaf/core/__init__.py,sha256=1VHV2-a1oJXIWcg8n5G5g2cmjw2QXv7OezncNB59KLw,1988
|
|
|
42
42
|
jaf/core/agent_tool.py,sha256=tfLNaTIcOZ0dR9GBP1AHLPkLExm_dLbURnVIN4R84FQ,11806
|
|
43
43
|
jaf/core/analytics.py,sha256=zFHIWqWal0bbEFCmJDc4DKeM0Ja7b_D19PqVaBI12pA,23338
|
|
44
44
|
jaf/core/composition.py,sha256=IVxRO1Q9nK7JRH32qQ4p8WMIUu66BhqPNrlTNMGFVwE,26317
|
|
45
|
-
jaf/core/engine.py,sha256=
|
|
45
|
+
jaf/core/engine.py,sha256=gv2nnkiWbqD54ru7NE3-c__DjOgp4OtPSoY9ApaBZIc,61009
|
|
46
46
|
jaf/core/errors.py,sha256=5fwTNhkojKRQ4wZj3lZlgDnAsrYyjYOwXJkIr5EGNUc,5539
|
|
47
47
|
jaf/core/guardrails.py,sha256=nv7pQuCx7-9DDZrecWO1DsDqFoujL81FBDrafOsXgcI,26179
|
|
48
48
|
jaf/core/handoff.py,sha256=ttjOQ6CSl34J4T_1ejdmq78gZ-ve07_IQE_DAbz2bmo,6002
|
|
@@ -55,8 +55,8 @@ jaf/core/state.py,sha256=oNCVXPWLkqnBQObdQX10TcmZ0eOF3wKG6DtL3kF6ohw,9649
|
|
|
55
55
|
jaf/core/streaming.py,sha256=h_lYHQA9ee_D5QsDO9-Vhevgi7rFXPslPzd9605AJGo,17034
|
|
56
56
|
jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
|
|
57
57
|
jaf/core/tools.py,sha256=84N9A7QQ3xxcOs2eUUot3nmCnt5i7iZT9VwkuzuFBxQ,16274
|
|
58
|
-
jaf/core/tracing.py,sha256=
|
|
59
|
-
jaf/core/types.py,sha256=
|
|
58
|
+
jaf/core/tracing.py,sha256=GNOJ8cJ-1kIsrvx_WUEhihy6T-hYk6i5MFB_UVGAcwU,53367
|
|
59
|
+
jaf/core/types.py,sha256=xXkSV2ZE9oQea3JrU9XfWFvIHWUbZHHftJaZV-UAwHg,32860
|
|
60
60
|
jaf/core/workflows.py,sha256=Ul-82gzjIXtkhnSMSPv-8igikjkMtW1EBo9yrfodtvI,26294
|
|
61
61
|
jaf/memory/__init__.py,sha256=-L98xlvihurGAzF0DnXtkueDVvO_wV2XxxEwAWdAj50,1400
|
|
62
62
|
jaf/memory/approval_storage.py,sha256=HHZ_b57kIthdR53QE5XNSII9xy1Cg-1cFUCSAZ8A4Rk,11083
|
|
@@ -88,9 +88,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
|
|
|
88
88
|
jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
|
|
89
89
|
jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
|
|
90
90
|
jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
|
|
91
|
-
jaf_py-2.5.
|
|
92
|
-
jaf_py-2.5.
|
|
93
|
-
jaf_py-2.5.
|
|
94
|
-
jaf_py-2.5.
|
|
95
|
-
jaf_py-2.5.
|
|
96
|
-
jaf_py-2.5.
|
|
91
|
+
jaf_py-2.5.5.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
|
|
92
|
+
jaf_py-2.5.5.dist-info/METADATA,sha256=QavZuyRtYw55yL1NQOmYfLIMxLhTu_V5yZaHOKOY4gY,27743
|
|
93
|
+
jaf_py-2.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
94
|
+
jaf_py-2.5.5.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
|
|
95
|
+
jaf_py-2.5.5.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
|
|
96
|
+
jaf_py-2.5.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|