@openlucaskaka/kagent 0.1.7
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.
- package/README.md +353 -0
- package/npm/bin/kagent-serve.js +6 -0
- package/npm/bin/kagent.js +6 -0
- package/npm/lib/App.js +524 -0
- package/npm/lib/app-state.js +224 -0
- package/npm/lib/approval-choice.js +25 -0
- package/npm/lib/commands.js +59 -0
- package/npm/lib/editor.js +188 -0
- package/npm/lib/ink-runner.js +41 -0
- package/npm/lib/kagent-home.js +39 -0
- package/npm/lib/launcher.js +221 -0
- package/npm/lib/protocol.js +33 -0
- package/npm/lib/provider-setup.js +139 -0
- package/npm/lib/python-runner.js +892 -0
- package/npm/lib/runtime-client.js +390 -0
- package/npm/lib/terminal-input.js +127 -0
- package/npm/lib/terminal-text.js +19 -0
- package/npm/lib/terminal-width.js +14 -0
- package/npm/lib/transcript.js +227 -0
- package/npm/lib/ui-components.js +247 -0
- package/npm/lib/update-manager.js +334 -0
- package/package.json +39 -0
- package/pyproject.toml +55 -0
- package/src/kagent/__init__.py +90 -0
- package/src/kagent/cli/__init__.py +5 -0
- package/src/kagent/cli/__main__.py +6 -0
- package/src/kagent/cli/commands.py +112 -0
- package/src/kagent/cli/conversation.py +127 -0
- package/src/kagent/cli/interactive.py +841 -0
- package/src/kagent/cli/main.py +685 -0
- package/src/kagent/cli/memory.py +460 -0
- package/src/kagent/cli/pending_approval.py +169 -0
- package/src/kagent/cli/provider.py +27 -0
- package/src/kagent/cli/session_commands.py +401 -0
- package/src/kagent/cli/stdio_runtime.py +784 -0
- package/src/kagent/cli/trace.py +67 -0
- package/src/kagent/cli/ui.py +931 -0
- package/src/kagent/core/__init__.py +0 -0
- package/src/kagent/core/agent.py +296 -0
- package/src/kagent/core/faults.py +11 -0
- package/src/kagent/core/invariants.py +47 -0
- package/src/kagent/core/normalization.py +81 -0
- package/src/kagent/core/planning.py +48 -0
- package/src/kagent/core/state.py +73 -0
- package/src/kagent/core/summary.py +64 -0
- package/src/kagent/core/tools.py +196 -0
- package/src/kagent/core/trace.py +57 -0
- package/src/kagent/eval/__init__.py +11 -0
- package/src/kagent/eval/cases.py +229 -0
- package/src/kagent/eval/evaluator.py +216 -0
- package/src/kagent/integrations/__init__.py +3 -0
- package/src/kagent/integrations/audit.py +95 -0
- package/src/kagent/integrations/backends.py +131 -0
- package/src/kagent/integrations/memory.py +301 -0
- package/src/kagent/ops/__init__.py +0 -0
- package/src/kagent/ops/batch.py +156 -0
- package/src/kagent/ops/doctor.py +255 -0
- package/src/kagent/ops/metrics.py +214 -0
- package/src/kagent/ops/release_evidence.py +877 -0
- package/src/kagent/ops/release_manifest.py +142 -0
- package/src/kagent/ops/trace_replay.py +285 -0
- package/src/kagent/providers/__init__.py +7 -0
- package/src/kagent/providers/embeddings.py +187 -0
- package/src/kagent/providers/llm.py +770 -0
- package/src/kagent/py.typed +1 -0
- package/src/kagent/runtime/__init__.py +28 -0
- package/src/kagent/runtime/action_graph.py +543 -0
- package/src/kagent/runtime/agent.py +2089 -0
- package/src/kagent/runtime/approval.py +64 -0
- package/src/kagent/runtime/cancellation.py +64 -0
- package/src/kagent/runtime/checkpoint_state.py +146 -0
- package/src/kagent/runtime/checkpoint_storage.py +270 -0
- package/src/kagent/runtime/context.py +65 -0
- package/src/kagent/runtime/file_transaction.py +195 -0
- package/src/kagent/runtime/hooks.py +74 -0
- package/src/kagent/runtime/metadata.py +116 -0
- package/src/kagent/runtime/patch_checkpoints.py +385 -0
- package/src/kagent/runtime/policy.py +50 -0
- package/src/kagent/runtime/presentation.py +205 -0
- package/src/kagent/runtime/redaction.py +130 -0
- package/src/kagent/runtime/sandbox.py +331 -0
- package/src/kagent/runtime/skills.py +66 -0
- package/src/kagent/runtime/steering.py +56 -0
- package/src/kagent/runtime/steps.py +255 -0
- package/src/kagent/runtime/task_state.py +40 -0
- package/src/kagent/runtime/tools.py +3532 -0
- package/src/kagent/runtime/types.py +240 -0
- package/src/kagent/runtime/workspace.py +597 -0
- package/src/kagent/service/__init__.py +26 -0
- package/src/kagent/service/__main__.py +6 -0
- package/src/kagent/service/active_runs.py +178 -0
- package/src/kagent/service/cli.py +737 -0
- package/src/kagent/service/contract.py +2571 -0
- package/src/kagent/service/errors.py +71 -0
- package/src/kagent/service/idempotency.py +584 -0
- package/src/kagent/service/router.py +884 -0
- package/src/kagent/service/run.py +150 -0
- package/src/kagent/service/runtime.py +1915 -0
- package/src/kagent/service/runtime_approval.py +20 -0
- package/src/kagent/service/runtime_cancel.py +192 -0
- package/src/kagent/service/runtime_lifecycle.py +229 -0
- package/src/kagent/service/runtime_metadata.py +21 -0
- package/src/kagent/service/runtime_policy.py +115 -0
- package/src/kagent/service/runtime_recovery.py +573 -0
- package/src/kagent/service/runtime_resume.py +382 -0
- package/src/kagent/service/runtime_resume_claim.py +116 -0
- package/src/kagent/service/runtime_run.py +350 -0
- package/src/kagent/service/runtime_status.py +2007 -0
- package/src/kagent/service/safety.py +139 -0
- package/src/kagent/service/server.py +114 -0
- package/src/kagent/service/status.py +233 -0
- package/src/kagent/service/trace_store.py +322 -0
- package/src/kagent/service/transport.py +24 -0
- package/src/kagent/utils/__init__.py +0 -0
- package/src/kagent/utils/config_validation.py +21 -0
- package/src/kagent/utils/json_output.py +23 -0
- package/src/kagent/utils/paths.py +473 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
MAX_PLAN_ACTIONS = 50
|
|
8
|
+
MAX_ACTION_REASON_CHARS = 2000
|
|
9
|
+
MAX_PLAN_FINAL_ANSWER_CHARS = 20000
|
|
10
|
+
_PLAN_FIELDS = {"actions", "final_answer"}
|
|
11
|
+
_ACTION_FIELDS = {"id", "tool", "input", "reason", "depends_on"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AgentAction:
|
|
16
|
+
id: str
|
|
17
|
+
tool: str
|
|
18
|
+
input: Dict[str, Any]
|
|
19
|
+
reason: str = ""
|
|
20
|
+
depends_on: List[str] = field(default_factory=list)
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
23
|
+
payload = {
|
|
24
|
+
"id": self.id,
|
|
25
|
+
"tool": self.tool,
|
|
26
|
+
"input": self.input,
|
|
27
|
+
"reason": self.reason,
|
|
28
|
+
}
|
|
29
|
+
if self.depends_on:
|
|
30
|
+
payload["depends_on"] = self.depends_on
|
|
31
|
+
return payload
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class AgentPlan:
|
|
36
|
+
actions: List[AgentAction]
|
|
37
|
+
final_answer: str = ""
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
40
|
+
payload: Dict[str, Any] = {
|
|
41
|
+
"actions": [action.to_dict() for action in self.actions]
|
|
42
|
+
}
|
|
43
|
+
if self.final_answer:
|
|
44
|
+
payload["final_answer"] = self.final_answer
|
|
45
|
+
return payload
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class AgentObservation:
|
|
50
|
+
action_id: str
|
|
51
|
+
tool: str
|
|
52
|
+
status: str
|
|
53
|
+
output: Dict[str, Any]
|
|
54
|
+
error_code: str = ""
|
|
55
|
+
error: str = ""
|
|
56
|
+
started_at: str = ""
|
|
57
|
+
completed_at: str = ""
|
|
58
|
+
duration_seconds: str = ""
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
61
|
+
payload = {
|
|
62
|
+
"action_id": self.action_id,
|
|
63
|
+
"tool": self.tool,
|
|
64
|
+
"status": self.status,
|
|
65
|
+
"output": self.output,
|
|
66
|
+
}
|
|
67
|
+
if self.error_code:
|
|
68
|
+
payload["error_code"] = self.error_code
|
|
69
|
+
if self.error:
|
|
70
|
+
payload["error"] = self.error
|
|
71
|
+
if self.started_at:
|
|
72
|
+
payload["started_at"] = self.started_at
|
|
73
|
+
if self.completed_at:
|
|
74
|
+
payload["completed_at"] = self.completed_at
|
|
75
|
+
if self.duration_seconds:
|
|
76
|
+
payload["duration_seconds"] = self.duration_seconds
|
|
77
|
+
return payload
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_agent_plan(text: str) -> AgentPlan:
|
|
81
|
+
payload = _parse_plan_json_object(text)
|
|
82
|
+
if not isinstance(payload, dict):
|
|
83
|
+
raise ValueError("plan must be a JSON object")
|
|
84
|
+
_reject_unknown_fields(payload, _PLAN_FIELDS, "plan")
|
|
85
|
+
actions_payload = payload.get("actions")
|
|
86
|
+
if not isinstance(actions_payload, list):
|
|
87
|
+
raise ValueError("plan actions must be a list")
|
|
88
|
+
if len(actions_payload) > MAX_PLAN_ACTIONS:
|
|
89
|
+
raise ValueError(f"plan actions must contain at most {MAX_PLAN_ACTIONS} item(s)")
|
|
90
|
+
final_answer = payload.get("final_answer", "")
|
|
91
|
+
if not isinstance(final_answer, str):
|
|
92
|
+
raise ValueError("final_answer must be a string")
|
|
93
|
+
if len(final_answer) > MAX_PLAN_FINAL_ANSWER_CHARS:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"final_answer must contain at most {MAX_PLAN_FINAL_ANSWER_CHARS} character(s)"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
actions = []
|
|
99
|
+
seen_action_ids = set()
|
|
100
|
+
for index, action_payload in enumerate(actions_payload):
|
|
101
|
+
if not isinstance(action_payload, dict):
|
|
102
|
+
raise ValueError(f"action {index} must be an object")
|
|
103
|
+
_reject_unknown_fields(action_payload, _ACTION_FIELDS, "action")
|
|
104
|
+
action_id = action_payload.get("id")
|
|
105
|
+
if not isinstance(action_id, str) or not action_id.strip():
|
|
106
|
+
raise ValueError("action id is required")
|
|
107
|
+
if action_id != action_id.strip():
|
|
108
|
+
raise ValueError("action id must not contain surrounding whitespace")
|
|
109
|
+
if action_id in seen_action_ids:
|
|
110
|
+
raise ValueError(f"duplicate action id: {action_id}")
|
|
111
|
+
tool = action_payload.get("tool")
|
|
112
|
+
if not isinstance(tool, str) or not tool.strip():
|
|
113
|
+
raise ValueError("action tool is required")
|
|
114
|
+
if tool != tool.strip():
|
|
115
|
+
raise ValueError("action tool must not contain surrounding whitespace")
|
|
116
|
+
action_input = action_payload.get("input", {})
|
|
117
|
+
if not isinstance(action_input, dict):
|
|
118
|
+
raise ValueError("action input must be an object")
|
|
119
|
+
reason = action_payload.get("reason", "")
|
|
120
|
+
if not isinstance(reason, str):
|
|
121
|
+
raise ValueError("action reason must be a string")
|
|
122
|
+
if len(reason) > MAX_ACTION_REASON_CHARS:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"action reason must contain at most {MAX_ACTION_REASON_CHARS} character(s)"
|
|
125
|
+
)
|
|
126
|
+
depends_on = _parse_action_dependencies(
|
|
127
|
+
action_payload.get("depends_on", []),
|
|
128
|
+
seen_action_ids,
|
|
129
|
+
)
|
|
130
|
+
_validate_dependency_references(action_input, set(depends_on))
|
|
131
|
+
seen_action_ids.add(action_id)
|
|
132
|
+
actions.append(
|
|
133
|
+
AgentAction(
|
|
134
|
+
id=action_id,
|
|
135
|
+
tool=tool,
|
|
136
|
+
input=action_input,
|
|
137
|
+
reason=reason,
|
|
138
|
+
depends_on=depends_on,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return AgentPlan(actions=actions, final_answer=final_answer)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _parse_plan_json_object(text: str) -> Any:
|
|
145
|
+
try:
|
|
146
|
+
return json.loads(text)
|
|
147
|
+
except json.JSONDecodeError as exc:
|
|
148
|
+
strict_error = exc
|
|
149
|
+
decoder = json.JSONDecoder()
|
|
150
|
+
candidates = []
|
|
151
|
+
for index, character in enumerate(text):
|
|
152
|
+
if character != "{":
|
|
153
|
+
continue
|
|
154
|
+
try:
|
|
155
|
+
payload, _end = decoder.raw_decode(text, index)
|
|
156
|
+
except json.JSONDecodeError:
|
|
157
|
+
continue
|
|
158
|
+
if isinstance(payload, dict) and "actions" in payload:
|
|
159
|
+
candidates.append(payload)
|
|
160
|
+
if candidates:
|
|
161
|
+
return candidates[-1]
|
|
162
|
+
raise ValueError(f"plan JSON is invalid: {strict_error}") from strict_error
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _reject_unknown_fields(
|
|
166
|
+
payload: Dict[str, Any],
|
|
167
|
+
allowed_fields: set,
|
|
168
|
+
label: str,
|
|
169
|
+
) -> None:
|
|
170
|
+
for field_name in payload:
|
|
171
|
+
if field_name not in allowed_fields:
|
|
172
|
+
raise ValueError(f"{label} field is not allowed: {field_name}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _parse_action_dependencies(value: Any, seen_action_ids: set) -> List[str]:
|
|
176
|
+
if not isinstance(value, list):
|
|
177
|
+
raise ValueError("action depends_on must be a list")
|
|
178
|
+
dependencies = []
|
|
179
|
+
seen_dependencies = set()
|
|
180
|
+
for dependency in value:
|
|
181
|
+
if not isinstance(dependency, str):
|
|
182
|
+
raise ValueError("action dependency must be a string")
|
|
183
|
+
if not dependency.strip():
|
|
184
|
+
raise ValueError("action dependency is required")
|
|
185
|
+
if dependency != dependency.strip():
|
|
186
|
+
raise ValueError("action dependency must not contain surrounding whitespace")
|
|
187
|
+
if dependency not in seen_action_ids:
|
|
188
|
+
raise ValueError(f"unknown or later action dependency: {dependency}")
|
|
189
|
+
if dependency in seen_dependencies:
|
|
190
|
+
raise ValueError(f"duplicate action dependency: {dependency}")
|
|
191
|
+
seen_dependencies.add(dependency)
|
|
192
|
+
dependencies.append(dependency)
|
|
193
|
+
return dependencies
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _validate_dependency_references(
|
|
197
|
+
value: Any,
|
|
198
|
+
declared_dependencies: set[str],
|
|
199
|
+
*,
|
|
200
|
+
depth: int = 0,
|
|
201
|
+
reference_count: List[int] | None = None,
|
|
202
|
+
) -> None:
|
|
203
|
+
if depth > 20:
|
|
204
|
+
raise ValueError("dependency input nesting exceeds 20 levels")
|
|
205
|
+
active_reference_count = reference_count or [0]
|
|
206
|
+
if isinstance(value, list):
|
|
207
|
+
for item in value:
|
|
208
|
+
_validate_dependency_references(
|
|
209
|
+
item,
|
|
210
|
+
declared_dependencies,
|
|
211
|
+
depth=depth + 1,
|
|
212
|
+
reference_count=active_reference_count,
|
|
213
|
+
)
|
|
214
|
+
return
|
|
215
|
+
if not isinstance(value, dict):
|
|
216
|
+
return
|
|
217
|
+
if "$from_action" in value:
|
|
218
|
+
if set(value) != {"$from_action", "pointer"}:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"dependency reference must contain only $from_action and pointer"
|
|
221
|
+
)
|
|
222
|
+
active_reference_count[0] += 1
|
|
223
|
+
if active_reference_count[0] > 50:
|
|
224
|
+
raise ValueError("action input contains more than 50 dependency references")
|
|
225
|
+
action_id = value.get("$from_action")
|
|
226
|
+
pointer = value.get("pointer")
|
|
227
|
+
if not isinstance(action_id, str) or action_id not in declared_dependencies:
|
|
228
|
+
raise ValueError("dependency reference must name a declared dependency")
|
|
229
|
+
if not isinstance(pointer, str) or (pointer and not pointer.startswith("/")):
|
|
230
|
+
raise ValueError(
|
|
231
|
+
"dependency reference pointer must be empty or start with /"
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
for item in value.values():
|
|
235
|
+
_validate_dependency_references(
|
|
236
|
+
item,
|
|
237
|
+
declared_dependencies,
|
|
238
|
+
depth=depth + 1,
|
|
239
|
+
reference_count=active_reference_count,
|
|
240
|
+
)
|