applied-cli 0.5.4__tar.gz → 0.5.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -4,6 +4,6 @@ from applied_cli import tools
4
4
  from applied_cli.client import AppliedClient
5
5
  from applied_cli.formatters import to_csv, to_json
6
6
 
7
- __version__ = "0.5.4"
7
+ __version__ = "0.5.5"
8
8
 
9
9
  __all__ = ["AppliedClient", "tools", "to_csv", "to_json", "__version__"]
@@ -390,3 +390,111 @@ class AppliedClient:
390
390
  params={"type": object_type, "id": object_id},
391
391
  )
392
392
  return data.get("spans", [])
393
+
394
+ # -------------------------------------------------------------------------
395
+ # Conversational Testing (via streaming /complete endpoint)
396
+ # -------------------------------------------------------------------------
397
+
398
+ async def send_message(
399
+ self,
400
+ agent_id: str,
401
+ message: str,
402
+ conversation_id: str | None = None,
403
+ contact_email: str | None = None,
404
+ contact_name: str | None = None,
405
+ contact_phone: str | None = None,
406
+ metadata: dict[str, Any] | None = None,
407
+ ) -> dict[str, Any]:
408
+ """Send a message to an agent and wait for the response.
409
+
410
+ This calls the /complete endpoint (streaming) and consumes the full
411
+ response. Use this for testing conversational flows.
412
+
413
+ Args:
414
+ agent_id: The agent UUID
415
+ message: The user message to send
416
+ conversation_id: Optional existing conversation to continue
417
+ contact_email: Optional contact email for context
418
+ contact_name: Optional contact name for context
419
+ contact_phone: Optional contact phone for context
420
+ metadata: Optional metadata dict for the conversation
421
+
422
+ Returns:
423
+ Dict with conversation_id, response_text, and status
424
+ """
425
+ headers = {
426
+ "Authorization": f"Bearer {self.token}",
427
+ "Content-Type": "application/json",
428
+ "Accept": "text/event-stream",
429
+ }
430
+ if self.shop_id:
431
+ headers["X-Shop-Id"] = self.shop_id
432
+
433
+ body: dict[str, Any] = {
434
+ "transcript": [
435
+ {
436
+ "role": "user",
437
+ "content": message,
438
+ "text": message,
439
+ "format": "markdown",
440
+ }
441
+ ],
442
+ }
443
+
444
+ if conversation_id:
445
+ body["conversation_id"] = conversation_id
446
+
447
+ # Build metadata with contact info
448
+ meta = metadata or {}
449
+ if contact_email:
450
+ meta["email"] = contact_email
451
+ if contact_name:
452
+ meta["name"] = contact_name
453
+ if contact_phone:
454
+ meta["phone"] = contact_phone
455
+ if meta:
456
+ body["metadata"] = meta
457
+
458
+ url = f"{self.base_url}/v1/agents/{agent_id}/complete/"
459
+
460
+ response_text = ""
461
+ result_conversation_id = conversation_id
462
+
463
+ async with httpx.AsyncClient(timeout=120.0) as client:
464
+ async with client.stream(
465
+ "POST", url, headers=headers, json=body
466
+ ) as response:
467
+ response.raise_for_status()
468
+
469
+ async for line in response.aiter_lines():
470
+ if not line:
471
+ continue
472
+
473
+ # Parse SSE format: "data: {...}"
474
+ if line.startswith("data: "):
475
+ data_str = line[6:] # Remove "data: " prefix
476
+ if data_str == "[DONE]":
477
+ break
478
+
479
+ try:
480
+ import json
481
+
482
+ data = json.loads(data_str)
483
+
484
+ # Extract conversation_id from first chunk
485
+ if not result_conversation_id:
486
+ result_conversation_id = data.get("conversation_id")
487
+
488
+ # Accumulate response content
489
+ if "content" in data:
490
+ response_text += data["content"]
491
+
492
+ except (json.JSONDecodeError, KeyError):
493
+ # Skip malformed chunks
494
+ pass
495
+
496
+ return {
497
+ "conversation_id": result_conversation_id,
498
+ "response": response_text,
499
+ "status": "success" if response_text else "empty",
500
+ }
@@ -4,6 +4,8 @@ These functions wrap the client methods with formatting logic,
4
4
  suitable for both MCP tools and CLI commands.
5
5
  """
6
6
 
7
+ import json
8
+
7
9
  from applied_cli.client import AppliedClient
8
10
  from applied_cli.formatters import select_fields, to_csv, to_json
9
11
 
@@ -742,10 +744,50 @@ async def flow_run_get(
742
744
  result += f" - {status_msg}"
743
745
  result += "\n"
744
746
 
745
- # Show executor output for executor_run spans
747
+ # executor_run: parse output JSON and show fields individually
746
748
  if name == "executor_run" and attrs.get("output"):
747
- output = str(attrs.get("output", ""))[:500]
748
- result += f" Output: {output}\n"
749
+ try:
750
+ output_data = json.loads(attrs["output"])
751
+ for k, v in output_data.items():
752
+ if k == "success":
753
+ continue
754
+ result += f" output.{k}: {str(v)[:300]}\n"
755
+ except (json.JSONDecodeError, TypeError):
756
+ result += f" output: {str(attrs['output'])[:500]}\n"
757
+
758
+ # completion: show the LLM text output
759
+ elif name == "completion" and attrs.get("content"):
760
+ result += f" completion: {str(attrs['content'])[:500]}\n"
761
+
762
+ # structured_completion: show parsed output
763
+ elif name == "structured_completion" and attrs.get("output"):
764
+ try:
765
+ out = json.loads(attrs["output"])
766
+ result += f" output: {json.dumps(out, indent=None)[:400]}\n"
767
+ except (json.JSONDecodeError, TypeError):
768
+ result += f" output: {str(attrs['output'])[:400]}\n"
769
+
770
+ # branch: show routing decision and comparison breakdown
771
+ elif name == "branch":
772
+ selected = attrs.get("selected.path", "")
773
+ if selected:
774
+ path_label = (
775
+ "default (no match)"
776
+ if selected == "default"
777
+ else f"branch {selected}"
778
+ )
779
+ result += f" selected path: {path_label}\n"
780
+ # Show comparison evaluations
781
+ idx = 1
782
+ while True:
783
+ op = attrs.get(f"comparison.{idx}.operator")
784
+ if not op:
785
+ break
786
+ lhs = attrs.get(f"comparison.{idx}.lhs", "?")
787
+ rhs = attrs.get(f"comparison.{idx}.rhs", "?")
788
+ res = attrs.get(f"comparison.{idx}.result", "?")
789
+ result += f" comparison {idx}: {lhs} {op} {rhs} => {res}\n"
790
+ idx += 1
749
791
 
750
792
  # Actions
751
793
  actions = run.get("actions", [])
@@ -845,6 +887,66 @@ async def conversation_spans(
845
887
  return result
846
888
 
847
889
 
890
+ async def send_message(
891
+ client: AppliedClient,
892
+ agent_id: str,
893
+ message: str,
894
+ conversation_id: str | None = None,
895
+ contact_email: str | None = None,
896
+ contact_name: str | None = None,
897
+ contact_phone: str | None = None,
898
+ ) -> str:
899
+ """
900
+ Send a message to an agent and get the response.
901
+
902
+ Use this to test conversational flows. After sending, you can query
903
+ the conversation messages with conversation_get for full details.
904
+
905
+ Args:
906
+ client: Authenticated AppliedClient
907
+ agent_id: The agent UUID to send the message to
908
+ message: The user message to send
909
+ conversation_id: Optional - continue an existing conversation
910
+ contact_email: Optional contact email (for flows that need it)
911
+ contact_name: Optional contact name
912
+ contact_phone: Optional contact phone
913
+
914
+ Returns:
915
+ Conversation ID, response preview, and status
916
+ """
917
+ try:
918
+ result = await client.send_message(
919
+ agent_id=agent_id,
920
+ message=message,
921
+ conversation_id=conversation_id,
922
+ contact_email=contact_email,
923
+ contact_name=contact_name,
924
+ contact_phone=contact_phone,
925
+ )
926
+
927
+ output = "# Message Sent\n"
928
+ output += f"conversation_id: {result.get('conversation_id')}\n"
929
+ output += f"status: {result.get('status')}\n"
930
+
931
+ response = result.get("response", "")
932
+ if response:
933
+ # Truncate long responses for preview
934
+ preview = response[:500] + "..." if len(response) > 500 else response
935
+ output += f"\n## Response Preview\n{preview}\n"
936
+
937
+ output += "\n## Next Steps\n"
938
+ output += (
939
+ "- Use `conversation_get` with the conversation_id to see full messages\n"
940
+ )
941
+ output += "- Use `flow_run_list` with conversation_id to see flow runs\n"
942
+ output += "- Use `conversation_spans` for detailed execution trace\n"
943
+
944
+ return output
945
+
946
+ except Exception as e:
947
+ return f"Error sending message: {e}"
948
+
949
+
848
950
  async def executor_list(
849
951
  client: AppliedClient,
850
952
  output_format: str = "csv",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: applied-cli
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: CLI and shared client library for Applied Labs AI support agents
5
5
  Author: Applied Labs
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "applied-cli"
3
- version = "0.5.4"
3
+ version = "0.5.6"
4
4
  description = "CLI and shared client library for Applied Labs AI support agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
File without changes
File without changes