minion-cli 1.0.0__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.
- minion/__init__.py +5 -0
- minion/__main__.py +3 -0
- minion/a2a/__init__.py +37 -0
- minion/a2a/card.py +69 -0
- minion/a2a/client.py +450 -0
- minion/a2a/config.py +95 -0
- minion/a2a/manager.py +116 -0
- minion/a2a/models.py +233 -0
- minion/a2a/server.py +454 -0
- minion/agents/__init__.py +89 -0
- minion/agents/builtin/coder.yaml +35 -0
- minion/agents/builtin/researcher.yaml +34 -0
- minion/agents/builtin/reviewer.yaml +33 -0
- minion/agents/builtin/tester.yaml +35 -0
- minion/agents/display.py +285 -0
- minion/agents/manifest.py +79 -0
- minion/agents/persist.py +28 -0
- minion/agents/registry.py +55 -0
- minion/agents/runner.py +253 -0
- minion/cli/__init__.py +3 -0
- minion/cli/_core.py +287 -0
- minion/cli/agents.py +60 -0
- minion/cli/config.py +39 -0
- minion/cli/doctor.py +82 -0
- minion/cli/mcp.py +54 -0
- minion/cli/memory.py +104 -0
- minion/cli/remote.py +88 -0
- minion/cli/skills.py +27 -0
- minion/compact/__init__.py +39 -0
- minion/compact/base.py +31 -0
- minion/compact/summary.py +113 -0
- minion/compact/truncate.py +58 -0
- minion/config/__init__.py +52 -0
- minion/config/file.py +422 -0
- minion/config/interactive.py +198 -0
- minion/config/model_catalog.py +165 -0
- minion/config/wizard.py +151 -0
- minion/context/__init__.py +11 -0
- minion/context/filetree.py +151 -0
- minion/context/manifest.py +248 -0
- minion/context/project.py +83 -0
- minion/context/prompts.py +106 -0
- minion/hooks/__init__.py +37 -0
- minion/hooks/builtin/__init__.py +3 -0
- minion/hooks/builtin/minion_md.py +53 -0
- minion/hooks/events.py +122 -0
- minion/hooks/handler.py +24 -0
- minion/hooks/handlers/__init__.py +3 -0
- minion/hooks/handlers/shell.py +76 -0
- minion/hooks/manifest.py +73 -0
- minion/hooks/persist.py +60 -0
- minion/hooks/registry.py +115 -0
- minion/hooks/result.py +14 -0
- minion/hooks/runner.py +119 -0
- minion/llm/__init__.py +4 -0
- minion/llm/anthropic.py +370 -0
- minion/llm/base.py +210 -0
- minion/llm/conversation.py +252 -0
- minion/llm/factory.py +78 -0
- minion/llm/openai.py +148 -0
- minion/llm/reflection.py +344 -0
- minion/llm/validate.py +70 -0
- minion/mcp/__init__.py +13 -0
- minion/mcp/config.py +124 -0
- minion/mcp/manager.py +766 -0
- minion/memory/__init__.py +13 -0
- minion/memory/config.py +42 -0
- minion/memory/embedder.py +68 -0
- minion/memory/extractor.py +231 -0
- minion/memory/injection.py +88 -0
- minion/memory/record.py +156 -0
- minion/memory/store.py +481 -0
- minion/memory/triggers.py +70 -0
- minion/memory/vector_store.py +123 -0
- minion/output/__init__.py +16 -0
- minion/output/base.py +202 -0
- minion/output/console.py +217 -0
- minion/output/diff.py +181 -0
- minion/output/display_utils.py +200 -0
- minion/output/formatter.py +154 -0
- minion/output/tui.py +199 -0
- minion/planner/__init__.py +10 -0
- minion/planner/creator.py +401 -0
- minion/planner/storage.py +54 -0
- minion/repl/__init__.py +22 -0
- minion/repl/agent_handlers.py +94 -0
- minion/repl/commands.py +659 -0
- minion/repl/config_cmd.py +271 -0
- minion/repl/init_md.py +70 -0
- minion/repl/input.py +168 -0
- minion/repl/mcp.py +188 -0
- minion/repl/session.py +1521 -0
- minion/repl/state.py +103 -0
- minion/runner/__init__.py +42 -0
- minion/runner/context.py +91 -0
- minion/runner/loop.py +616 -0
- minion/runner/parallel.py +318 -0
- minion/runner/session.py +172 -0
- minion/skills/__init__.py +12 -0
- minion/skills/builtin/commit.yaml +16 -0
- minion/skills/builtin/explain.yaml +27 -0
- minion/skills/builtin/refactor.yaml +33 -0
- minion/skills/builtin/review.yaml +28 -0
- minion/skills/builtin/test.yaml +24 -0
- minion/skills/manifest.py +80 -0
- minion/skills/persist.py +28 -0
- minion/skills/registry.py +76 -0
- minion/skills/runner.py +140 -0
- minion/theme/__init__.py +75 -0
- minion/theme/banner.py +297 -0
- minion/theme/console.py +31 -0
- minion/theme/palette.py +32 -0
- minion/theme/printers.py +295 -0
- minion/tools/__init__.py +1 -0
- minion/tools/confirmation.py +137 -0
- minion/tools/definitions.py +357 -0
- minion/tools/executor.py +903 -0
- minion/tools/implementations.py +549 -0
- minion/tools/outline.py +241 -0
- minion/tools/permissions.py +280 -0
- minion/tracing/__init__.py +12 -0
- minion/tracing/cli.py +161 -0
- minion/tracing/events.py +310 -0
- minion/tracing/server.py +85 -0
- minion/tracing/tracer.py +154 -0
- minion/tracing/ui.html +1166 -0
- minion/tui/__init__.py +46 -0
- minion/tui/agent_registry.py +96 -0
- minion/tui/app.py +1417 -0
- minion/tui/choice_panel.py +98 -0
- minion/tui/conversation.py +319 -0
- minion/tui/inspector.py +604 -0
- minion/tui/keys.py +20 -0
- minion/tui/messages.py +11 -0
- minion/tui/permission.py +188 -0
- minion/tui/render.py +141 -0
- minion/tui/screens/__init__.py +13 -0
- minion/tui/screens/agents_screen.py +2963 -0
- minion/tui/screens/base.py +373 -0
- minion/tui/screens/completion_setup.py +261 -0
- minion/tui/screens/config_panel.py +513 -0
- minion/tui/screens/help_screen.py +755 -0
- minion/tui/screens/hooks_screen.py +2367 -0
- minion/tui/screens/load_screen.py +528 -0
- minion/tui/screens/memories_screen.py +932 -0
- minion/tui/screens/model_config.py +930 -0
- minion/tui/screens/skills_screen.py +2682 -0
- minion/tui/setup_checklist.py +219 -0
- minion/tui/slots.py +180 -0
- minion/tui/status.py +144 -0
- minion/tui/terminal.py +92 -0
- minion/tui/theme.py +186 -0
- minion_cli-1.0.0.dist-info/METADATA +336 -0
- minion_cli-1.0.0.dist-info/RECORD +157 -0
- minion_cli-1.0.0.dist-info/WHEEL +4 -0
- minion_cli-1.0.0.dist-info/entry_points.txt +3 -0
- minion_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
minion/a2a/manager.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""A2A manager — connects to all configured remote agents, routes send_task() calls.
|
|
2
|
+
|
|
3
|
+
One A2AClient per configured agent. Emits Nefario trace events around the
|
|
4
|
+
task lifecycle. Gracefully handles unknown agent names and connection failures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from ..theme import console
|
|
14
|
+
from ..tracing import get_tracer
|
|
15
|
+
from .client import A2AClient, A2AError
|
|
16
|
+
from .config import A2AAgentConfig, load_a2a_config
|
|
17
|
+
from .models import AgentCard
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class A2AManager:
|
|
21
|
+
"""Routes named agent calls to the correct A2AClient.
|
|
22
|
+
|
|
23
|
+
Created once at REPL startup. Loaded from a2a.json config (user + project
|
|
24
|
+
tiers). Optionally fetches Agent Cards at startup for the /a2a list display.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
clients: dict[str, A2AClient],
|
|
30
|
+
cards: Optional[dict[str, Optional[AgentCard]]] = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._clients = clients
|
|
33
|
+
self._cards: dict[str, Optional[AgentCard]] = cards or {}
|
|
34
|
+
|
|
35
|
+
def agent_names(self) -> list[str]:
|
|
36
|
+
return list(self._clients.keys())
|
|
37
|
+
|
|
38
|
+
def has_agents(self) -> bool:
|
|
39
|
+
return bool(self._clients)
|
|
40
|
+
|
|
41
|
+
def agent_summary(self) -> list[dict]:
|
|
42
|
+
"""Return display info for each agent (name, url, card name/description)."""
|
|
43
|
+
result = []
|
|
44
|
+
for name, client in self._clients.items():
|
|
45
|
+
card = self._cards.get(name)
|
|
46
|
+
result.append({
|
|
47
|
+
"name": name,
|
|
48
|
+
"url": f"{client._scheme}://{client._netloc}",
|
|
49
|
+
"card_name": card.name if card else "(unreachable)",
|
|
50
|
+
"card_description": card.description if card else "",
|
|
51
|
+
})
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def send_task(self, agent_name: str, task_text: str) -> str:
|
|
55
|
+
"""Send a task to the named remote agent and return the result text.
|
|
56
|
+
|
|
57
|
+
Emits a2a_task_send, then a2a_task_complete or a2a_task_error.
|
|
58
|
+
Returns an error string (never raises) so the LLM sees the error as
|
|
59
|
+
a tool result rather than an uncaught exception.
|
|
60
|
+
"""
|
|
61
|
+
client = self._clients.get(agent_name)
|
|
62
|
+
if client is None:
|
|
63
|
+
available = ", ".join(self._clients) if self._clients else "(none configured)"
|
|
64
|
+
return (
|
|
65
|
+
f"Error: unknown A2A agent '{agent_name}'. "
|
|
66
|
+
f"Available: {available}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
remote_url = f"{client._scheme}://{client._netloc}"
|
|
70
|
+
get_tracer().emit(
|
|
71
|
+
"a2a_task_send",
|
|
72
|
+
agent_name=agent_name,
|
|
73
|
+
task=task_text,
|
|
74
|
+
remote_url=remote_url,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
start = time.monotonic()
|
|
78
|
+
try:
|
|
79
|
+
result = client.send_task(task_text)
|
|
80
|
+
latency_ms = int((time.monotonic() - start) * 1000)
|
|
81
|
+
get_tracer().emit(
|
|
82
|
+
"a2a_task_complete",
|
|
83
|
+
agent_name=agent_name,
|
|
84
|
+
task=task_text,
|
|
85
|
+
result=result[:500],
|
|
86
|
+
result_length=len(result),
|
|
87
|
+
latency_ms=latency_ms,
|
|
88
|
+
)
|
|
89
|
+
return result
|
|
90
|
+
except A2AError as e:
|
|
91
|
+
get_tracer().emit(
|
|
92
|
+
"a2a_task_error",
|
|
93
|
+
agent_name=agent_name,
|
|
94
|
+
task=task_text,
|
|
95
|
+
error=str(e),
|
|
96
|
+
)
|
|
97
|
+
return f"Error: {e}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_a2a_manager(cwd: Path | None = None) -> A2AManager:
|
|
101
|
+
"""Load A2A config and construct one A2AClient per configured agent.
|
|
102
|
+
|
|
103
|
+
Attempts to fetch each agent's Agent Card at startup so /a2a list can
|
|
104
|
+
show descriptions. Card fetch failures are silent — the agent is still
|
|
105
|
+
registered (it may be temporarily unreachable).
|
|
106
|
+
"""
|
|
107
|
+
configs = load_a2a_config(cwd)
|
|
108
|
+
clients: dict[str, A2AClient] = {}
|
|
109
|
+
cards: dict[str, Optional[AgentCard]] = {}
|
|
110
|
+
|
|
111
|
+
for name, cfg in configs.items():
|
|
112
|
+
client = A2AClient(name=cfg.name, url=cfg.url, timeout_seconds=cfg.timeout_seconds)
|
|
113
|
+
clients[name] = client
|
|
114
|
+
cards[name] = client.fetch_agent_card() # None if unreachable
|
|
115
|
+
|
|
116
|
+
return A2AManager(clients=clients, cards=cards)
|
minion/a2a/models.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""A2A protocol data model — Task, Artifact, AgentCard.
|
|
2
|
+
|
|
3
|
+
Spec-compliant implementation of the Agent-to-Agent (A2A) protocol:
|
|
4
|
+
- Artifacts use {parts: [{type, text}]} wire format
|
|
5
|
+
- Task status uses {state, timestamp} object
|
|
6
|
+
- AgentCard includes defaultInputModes/defaultOutputModes and full capabilities
|
|
7
|
+
- Message uses {role, parts: [{type, text}]} wire format
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import uuid
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _iso_now() -> str:
|
|
21
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _extract_text_from_message(msg) -> str:
|
|
25
|
+
"""Extract plain text from a spec Message object or bare string."""
|
|
26
|
+
if isinstance(msg, str):
|
|
27
|
+
return msg
|
|
28
|
+
if isinstance(msg, dict):
|
|
29
|
+
parts = msg.get("parts", [])
|
|
30
|
+
if parts:
|
|
31
|
+
texts = [p.get("text", "") for p in parts if p.get("type") == "text"]
|
|
32
|
+
return "\n".join(t for t in texts if t) or msg.get("text", "")
|
|
33
|
+
return msg.get("text", "")
|
|
34
|
+
return str(msg)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _make_message(text: str, role: str = "user") -> dict:
|
|
38
|
+
"""Build a spec-compliant Message object."""
|
|
39
|
+
return {
|
|
40
|
+
"role": role,
|
|
41
|
+
"parts": [{"type": "text", "text": text}],
|
|
42
|
+
"messageId": str(uuid.uuid4()),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TaskStatus(str, Enum):
|
|
47
|
+
SUBMITTED = "submitted"
|
|
48
|
+
WORKING = "working"
|
|
49
|
+
INPUT_REQUIRED = "input-required"
|
|
50
|
+
COMPLETED = "completed"
|
|
51
|
+
FAILED = "failed"
|
|
52
|
+
CANCELED = "canceled"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Artifact:
|
|
57
|
+
"""Text result produced by a remote agent for a task.
|
|
58
|
+
|
|
59
|
+
Wire format (spec): {"artifactId": "...", "parts": [{"type": "text", "text": "..."}]}
|
|
60
|
+
"""
|
|
61
|
+
text: str
|
|
62
|
+
artifact_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return {
|
|
66
|
+
"artifactId": self.artifact_id,
|
|
67
|
+
"parts": [{"type": "text", "text": self.text}],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_dict(cls, data: dict) -> "Artifact":
|
|
72
|
+
# Handle spec format: {parts: [{type, text}]}
|
|
73
|
+
parts = data.get("parts", [])
|
|
74
|
+
if parts:
|
|
75
|
+
text = "\n".join(
|
|
76
|
+
p.get("text", "") for p in parts if p.get("type") == "text"
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
# Fallback for older non-spec format
|
|
80
|
+
text = data.get("text", "")
|
|
81
|
+
return cls(text=text, artifact_id=data.get("artifactId", str(uuid.uuid4())))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Task:
|
|
86
|
+
"""Unit of work in the A2A protocol.
|
|
87
|
+
|
|
88
|
+
Wire format (spec): status is {state, timestamp}, not a bare string.
|
|
89
|
+
Lifecycle: submitted → working → completed / failed / canceled
|
|
90
|
+
contextId groups related tasks into a session (multi-turn conversation).
|
|
91
|
+
"""
|
|
92
|
+
id: str
|
|
93
|
+
status: TaskStatus
|
|
94
|
+
input_message: str
|
|
95
|
+
artifacts: list[Artifact] = field(default_factory=list)
|
|
96
|
+
error: Optional[str] = None
|
|
97
|
+
created_at: str = field(default_factory=_iso_now)
|
|
98
|
+
context_id: Optional[str] = None
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> dict:
|
|
101
|
+
d: dict = {
|
|
102
|
+
"id": self.id,
|
|
103
|
+
"status": {
|
|
104
|
+
"state": self.status.value,
|
|
105
|
+
"timestamp": _iso_now(),
|
|
106
|
+
},
|
|
107
|
+
"input": _make_message(self.input_message),
|
|
108
|
+
}
|
|
109
|
+
if self.context_id is not None:
|
|
110
|
+
d["contextId"] = self.context_id
|
|
111
|
+
if self.artifacts:
|
|
112
|
+
d["artifacts"] = [a.to_dict() for a in self.artifacts]
|
|
113
|
+
if self.error is not None:
|
|
114
|
+
d["error"] = self.error
|
|
115
|
+
return d
|
|
116
|
+
|
|
117
|
+
def status_event(self, final: bool = False) -> dict:
|
|
118
|
+
"""Build a spec TaskStatusUpdateEvent for SSE streaming."""
|
|
119
|
+
d: dict = {
|
|
120
|
+
"id": self.id,
|
|
121
|
+
"status": {
|
|
122
|
+
"state": self.status.value,
|
|
123
|
+
"timestamp": _iso_now(),
|
|
124
|
+
},
|
|
125
|
+
"final": final,
|
|
126
|
+
}
|
|
127
|
+
if self.context_id is not None:
|
|
128
|
+
d["contextId"] = self.context_id
|
|
129
|
+
return d
|
|
130
|
+
|
|
131
|
+
def artifact_event(self, artifact: Artifact, final: bool = True) -> dict:
|
|
132
|
+
"""Build a spec TaskArtifactUpdateEvent for SSE streaming."""
|
|
133
|
+
d: dict = {
|
|
134
|
+
"id": self.id,
|
|
135
|
+
"artifact": artifact.to_dict(),
|
|
136
|
+
"final": final,
|
|
137
|
+
}
|
|
138
|
+
if self.context_id is not None:
|
|
139
|
+
d["contextId"] = self.context_id
|
|
140
|
+
return d
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_dict(cls, data: dict) -> "Task":
|
|
144
|
+
# Parse status — handle both spec {state, timestamp} and legacy bare string
|
|
145
|
+
raw_status = data.get("status", "submitted")
|
|
146
|
+
if isinstance(raw_status, dict):
|
|
147
|
+
status_str = raw_status.get("state", "submitted")
|
|
148
|
+
else:
|
|
149
|
+
status_str = raw_status
|
|
150
|
+
try:
|
|
151
|
+
status = TaskStatus(status_str)
|
|
152
|
+
except ValueError:
|
|
153
|
+
status = TaskStatus.FAILED
|
|
154
|
+
|
|
155
|
+
artifacts = [Artifact.from_dict(a) for a in data.get("artifacts", [])]
|
|
156
|
+
|
|
157
|
+
# Parse input — handle both spec Message and legacy {message: str}
|
|
158
|
+
input_data = data.get("input", {})
|
|
159
|
+
if isinstance(input_data, dict):
|
|
160
|
+
input_message = _extract_text_from_message(input_data)
|
|
161
|
+
else:
|
|
162
|
+
input_message = str(input_data)
|
|
163
|
+
|
|
164
|
+
return cls(
|
|
165
|
+
id=data.get("id", ""),
|
|
166
|
+
status=status,
|
|
167
|
+
input_message=input_message,
|
|
168
|
+
artifacts=artifacts,
|
|
169
|
+
error=data.get("error"),
|
|
170
|
+
context_id=data.get("contextId"),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class AgentCard:
|
|
176
|
+
"""Capability advertisement for an A2A agent.
|
|
177
|
+
|
|
178
|
+
Served at /.well-known/agent.json.
|
|
179
|
+
"""
|
|
180
|
+
name: str
|
|
181
|
+
description: str
|
|
182
|
+
url: str
|
|
183
|
+
version: str
|
|
184
|
+
capabilities: dict = field(default_factory=lambda: {
|
|
185
|
+
"streaming": True,
|
|
186
|
+
"pushNotifications": False,
|
|
187
|
+
"stateTransitionHistory": False,
|
|
188
|
+
})
|
|
189
|
+
skills: list[dict] = field(default_factory=list)
|
|
190
|
+
default_input_modes: list[str] = field(default_factory=lambda: ["text"])
|
|
191
|
+
default_output_modes: list[str] = field(default_factory=lambda: ["text"])
|
|
192
|
+
# Optional spec fields — omitted from wire format when None
|
|
193
|
+
provider: Optional[dict] = None # {"organization": "...", "url": "..."}
|
|
194
|
+
authentication: Optional[dict] = None # {"schemes": ["Bearer"]}
|
|
195
|
+
|
|
196
|
+
def to_dict(self) -> dict:
|
|
197
|
+
d: dict = {
|
|
198
|
+
"name": self.name,
|
|
199
|
+
"description": self.description,
|
|
200
|
+
"url": self.url,
|
|
201
|
+
"version": self.version,
|
|
202
|
+
"capabilities": self.capabilities,
|
|
203
|
+
"defaultInputModes": self.default_input_modes,
|
|
204
|
+
"defaultOutputModes": self.default_output_modes,
|
|
205
|
+
"skills": self.skills,
|
|
206
|
+
}
|
|
207
|
+
if self.provider is not None:
|
|
208
|
+
d["provider"] = self.provider
|
|
209
|
+
if self.authentication is not None:
|
|
210
|
+
d["authentication"] = self.authentication
|
|
211
|
+
return d
|
|
212
|
+
|
|
213
|
+
def to_json(self) -> str:
|
|
214
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def from_dict(cls, data: dict) -> "AgentCard":
|
|
218
|
+
return cls(
|
|
219
|
+
name=data.get("name", ""),
|
|
220
|
+
description=data.get("description", ""),
|
|
221
|
+
url=data.get("url", ""),
|
|
222
|
+
version=data.get("version", ""),
|
|
223
|
+
capabilities=data.get("capabilities", {
|
|
224
|
+
"streaming": True,
|
|
225
|
+
"pushNotifications": False,
|
|
226
|
+
"stateTransitionHistory": False,
|
|
227
|
+
}),
|
|
228
|
+
skills=data.get("skills", []),
|
|
229
|
+
default_input_modes=data.get("defaultInputModes", ["text"]),
|
|
230
|
+
default_output_modes=data.get("defaultOutputModes", ["text"]),
|
|
231
|
+
provider=data.get("provider"),
|
|
232
|
+
authentication=data.get("authentication"),
|
|
233
|
+
)
|