@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.
Files changed (117) hide show
  1. package/README.md +353 -0
  2. package/npm/bin/kagent-serve.js +6 -0
  3. package/npm/bin/kagent.js +6 -0
  4. package/npm/lib/App.js +524 -0
  5. package/npm/lib/app-state.js +224 -0
  6. package/npm/lib/approval-choice.js +25 -0
  7. package/npm/lib/commands.js +59 -0
  8. package/npm/lib/editor.js +188 -0
  9. package/npm/lib/ink-runner.js +41 -0
  10. package/npm/lib/kagent-home.js +39 -0
  11. package/npm/lib/launcher.js +221 -0
  12. package/npm/lib/protocol.js +33 -0
  13. package/npm/lib/provider-setup.js +139 -0
  14. package/npm/lib/python-runner.js +892 -0
  15. package/npm/lib/runtime-client.js +390 -0
  16. package/npm/lib/terminal-input.js +127 -0
  17. package/npm/lib/terminal-text.js +19 -0
  18. package/npm/lib/terminal-width.js +14 -0
  19. package/npm/lib/transcript.js +227 -0
  20. package/npm/lib/ui-components.js +247 -0
  21. package/npm/lib/update-manager.js +334 -0
  22. package/package.json +39 -0
  23. package/pyproject.toml +55 -0
  24. package/src/kagent/__init__.py +90 -0
  25. package/src/kagent/cli/__init__.py +5 -0
  26. package/src/kagent/cli/__main__.py +6 -0
  27. package/src/kagent/cli/commands.py +112 -0
  28. package/src/kagent/cli/conversation.py +127 -0
  29. package/src/kagent/cli/interactive.py +841 -0
  30. package/src/kagent/cli/main.py +685 -0
  31. package/src/kagent/cli/memory.py +460 -0
  32. package/src/kagent/cli/pending_approval.py +169 -0
  33. package/src/kagent/cli/provider.py +27 -0
  34. package/src/kagent/cli/session_commands.py +401 -0
  35. package/src/kagent/cli/stdio_runtime.py +784 -0
  36. package/src/kagent/cli/trace.py +67 -0
  37. package/src/kagent/cli/ui.py +931 -0
  38. package/src/kagent/core/__init__.py +0 -0
  39. package/src/kagent/core/agent.py +296 -0
  40. package/src/kagent/core/faults.py +11 -0
  41. package/src/kagent/core/invariants.py +47 -0
  42. package/src/kagent/core/normalization.py +81 -0
  43. package/src/kagent/core/planning.py +48 -0
  44. package/src/kagent/core/state.py +73 -0
  45. package/src/kagent/core/summary.py +64 -0
  46. package/src/kagent/core/tools.py +196 -0
  47. package/src/kagent/core/trace.py +57 -0
  48. package/src/kagent/eval/__init__.py +11 -0
  49. package/src/kagent/eval/cases.py +229 -0
  50. package/src/kagent/eval/evaluator.py +216 -0
  51. package/src/kagent/integrations/__init__.py +3 -0
  52. package/src/kagent/integrations/audit.py +95 -0
  53. package/src/kagent/integrations/backends.py +131 -0
  54. package/src/kagent/integrations/memory.py +301 -0
  55. package/src/kagent/ops/__init__.py +0 -0
  56. package/src/kagent/ops/batch.py +156 -0
  57. package/src/kagent/ops/doctor.py +255 -0
  58. package/src/kagent/ops/metrics.py +214 -0
  59. package/src/kagent/ops/release_evidence.py +877 -0
  60. package/src/kagent/ops/release_manifest.py +142 -0
  61. package/src/kagent/ops/trace_replay.py +285 -0
  62. package/src/kagent/providers/__init__.py +7 -0
  63. package/src/kagent/providers/embeddings.py +187 -0
  64. package/src/kagent/providers/llm.py +770 -0
  65. package/src/kagent/py.typed +1 -0
  66. package/src/kagent/runtime/__init__.py +28 -0
  67. package/src/kagent/runtime/action_graph.py +543 -0
  68. package/src/kagent/runtime/agent.py +2089 -0
  69. package/src/kagent/runtime/approval.py +64 -0
  70. package/src/kagent/runtime/cancellation.py +64 -0
  71. package/src/kagent/runtime/checkpoint_state.py +146 -0
  72. package/src/kagent/runtime/checkpoint_storage.py +270 -0
  73. package/src/kagent/runtime/context.py +65 -0
  74. package/src/kagent/runtime/file_transaction.py +195 -0
  75. package/src/kagent/runtime/hooks.py +74 -0
  76. package/src/kagent/runtime/metadata.py +116 -0
  77. package/src/kagent/runtime/patch_checkpoints.py +385 -0
  78. package/src/kagent/runtime/policy.py +50 -0
  79. package/src/kagent/runtime/presentation.py +205 -0
  80. package/src/kagent/runtime/redaction.py +130 -0
  81. package/src/kagent/runtime/sandbox.py +331 -0
  82. package/src/kagent/runtime/skills.py +66 -0
  83. package/src/kagent/runtime/steering.py +56 -0
  84. package/src/kagent/runtime/steps.py +255 -0
  85. package/src/kagent/runtime/task_state.py +40 -0
  86. package/src/kagent/runtime/tools.py +3532 -0
  87. package/src/kagent/runtime/types.py +240 -0
  88. package/src/kagent/runtime/workspace.py +597 -0
  89. package/src/kagent/service/__init__.py +26 -0
  90. package/src/kagent/service/__main__.py +6 -0
  91. package/src/kagent/service/active_runs.py +178 -0
  92. package/src/kagent/service/cli.py +737 -0
  93. package/src/kagent/service/contract.py +2571 -0
  94. package/src/kagent/service/errors.py +71 -0
  95. package/src/kagent/service/idempotency.py +584 -0
  96. package/src/kagent/service/router.py +884 -0
  97. package/src/kagent/service/run.py +150 -0
  98. package/src/kagent/service/runtime.py +1915 -0
  99. package/src/kagent/service/runtime_approval.py +20 -0
  100. package/src/kagent/service/runtime_cancel.py +192 -0
  101. package/src/kagent/service/runtime_lifecycle.py +229 -0
  102. package/src/kagent/service/runtime_metadata.py +21 -0
  103. package/src/kagent/service/runtime_policy.py +115 -0
  104. package/src/kagent/service/runtime_recovery.py +573 -0
  105. package/src/kagent/service/runtime_resume.py +382 -0
  106. package/src/kagent/service/runtime_resume_claim.py +116 -0
  107. package/src/kagent/service/runtime_run.py +350 -0
  108. package/src/kagent/service/runtime_status.py +2007 -0
  109. package/src/kagent/service/safety.py +139 -0
  110. package/src/kagent/service/server.py +114 -0
  111. package/src/kagent/service/status.py +233 -0
  112. package/src/kagent/service/trace_store.py +322 -0
  113. package/src/kagent/service/transport.py +24 -0
  114. package/src/kagent/utils/__init__.py +0 -0
  115. package/src/kagent/utils/config_validation.py +21 -0
  116. package/src/kagent/utils/json_output.py +23 -0
  117. package/src/kagent/utils/paths.py +473 -0
@@ -0,0 +1,350 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from concurrent.futures import TimeoutError
5
+ from typing import Any, Dict, Tuple
6
+ from uuid import uuid4
7
+
8
+ from kagent.integrations.audit import KafkaRestAuditHook, KafkaRestProgressEventSink
9
+ from kagent.providers.llm import (
10
+ FakeLLMProvider,
11
+ LLMProviderConfig,
12
+ OpenAICompatibleProvider,
13
+ SequentialFakeLLMProvider,
14
+ )
15
+ from kagent.runtime import RuntimeCancellationToken, run_runtime_agent
16
+ from kagent.runtime.policy import RuntimePolicy
17
+ from kagent.service import errors as service_errors
18
+ from kagent.service.active_runs import ActiveRunRegistry, ExecutionSlotLease
19
+ from kagent.service.errors import failure_payload
20
+ from kagent.service.run import run_with_timeout
21
+ from kagent.service.runtime import ServiceConfig
22
+ from kagent.service.runtime_approval import (
23
+ validate_approved_action_ids,
24
+ )
25
+ from kagent.service.runtime_lifecycle import (
26
+ persist_cancelled_runtime_trace,
27
+ persist_failed_runtime_trace,
28
+ persist_runtime_worker_result,
29
+ persisted_runtime_cancellation_probe,
30
+ running_runtime_trace,
31
+ )
32
+ from kagent.service.runtime_metadata import (
33
+ validate_runtime_metadata,
34
+ validate_runtime_tags,
35
+ )
36
+ from kagent.service.trace_store import persist_trace
37
+ from kagent.utils.json_output import json_ready
38
+
39
+
40
+ def execute_runtime_run_request(
41
+ body: bytes,
42
+ _service_config: ServiceConfig,
43
+ auth_subject: str = "",
44
+ *,
45
+ active_run_registry: ActiveRunRegistry | None = None,
46
+ execution_slot_lease: ExecutionSlotLease | None = None,
47
+ ) -> Tuple[int, Dict[str, Any]]:
48
+ try:
49
+ payload = json.loads(body.decode("utf-8"))
50
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
51
+ return 400, failure_payload(service_errors.INVALID_JSON, f"invalid JSON: {exc}")
52
+ if not isinstance(payload, dict):
53
+ return 400, failure_payload(
54
+ service_errors.INVALID_REQUEST_BODY,
55
+ "request body must be a JSON object",
56
+ )
57
+ goal = str(payload.get("goal", ""))
58
+ if not goal.strip():
59
+ return 400, failure_payload(service_errors.MISSING_GOAL, "goal is required")
60
+ if len(goal) > _service_config.max_goal_chars:
61
+ return 413, failure_payload(
62
+ service_errors.GOAL_TOO_LARGE,
63
+ "goal exceeds max_goal_chars",
64
+ )
65
+ max_iterations = payload.get("max_iterations", 1)
66
+ if (
67
+ not isinstance(max_iterations, int)
68
+ or isinstance(max_iterations, bool)
69
+ or max_iterations < 1
70
+ ):
71
+ return 400, failure_payload(
72
+ service_errors.INVALID_REQUEST_BODY,
73
+ "max_iterations must be an integer greater than or equal to 1",
74
+ )
75
+ if max_iterations > _service_config.runtime_max_iterations:
76
+ return 400, failure_payload(
77
+ service_errors.INVALID_REQUEST_BODY,
78
+ "max_iterations exceeds runtime_max_iterations",
79
+ )
80
+ approved_action_ids_payload = payload.get("approved_action_ids", [])
81
+ approved_action_ids, approval_error = validate_approved_action_ids(
82
+ approved_action_ids_payload
83
+ )
84
+ if approval_error:
85
+ return 400, failure_payload(
86
+ service_errors.INVALID_REQUEST_BODY,
87
+ approval_error,
88
+ )
89
+ metadata, metadata_error = validate_runtime_metadata(payload.get("metadata"))
90
+ if metadata_error:
91
+ return 400, failure_payload(
92
+ service_errors.INVALID_REQUEST_BODY,
93
+ metadata_error,
94
+ )
95
+ tags, tags_error = validate_runtime_tags(payload.get("tags"))
96
+ if tags_error:
97
+ return 400, failure_payload(
98
+ service_errors.INVALID_REQUEST_BODY,
99
+ tags_error,
100
+ )
101
+ plan_payload = payload.get("plan")
102
+ plan_sequence_payload = payload.get("plan_sequence")
103
+ if plan_payload is not None and plan_sequence_payload is not None:
104
+ return 400, failure_payload(
105
+ service_errors.INVALID_REQUEST_BODY,
106
+ "plan and plan_sequence are mutually exclusive",
107
+ )
108
+ if approved_action_ids and plan_payload is None and plan_sequence_payload is None:
109
+ return 400, failure_payload(
110
+ service_errors.INVALID_REQUEST_BODY,
111
+ "approved_action_ids require plan or plan_sequence; use /runtime/resume "
112
+ "to approve live provider actions",
113
+ )
114
+ planned_action_ids = _planned_action_ids(plan_payload, plan_sequence_payload)
115
+ if approved_action_ids and planned_action_ids is not None:
116
+ missing_approved_ids = [
117
+ action_id
118
+ for action_id in approved_action_ids
119
+ if action_id not in planned_action_ids
120
+ ]
121
+ if missing_approved_ids:
122
+ return 400, failure_payload(
123
+ service_errors.INVALID_REQUEST_BODY,
124
+ "approved_action_ids must reference planned action ids: "
125
+ + ", ".join(missing_approved_ids),
126
+ )
127
+ if plan_payload is not None:
128
+ if not isinstance(plan_payload, dict):
129
+ return 400, failure_payload(
130
+ service_errors.INVALID_REQUEST_BODY,
131
+ "plan must be a JSON object",
132
+ )
133
+ provider = FakeLLMProvider(json.dumps(plan_payload, sort_keys=True))
134
+ elif plan_sequence_payload is not None:
135
+ if not isinstance(plan_sequence_payload, list) or not plan_sequence_payload:
136
+ return 400, failure_payload(
137
+ service_errors.INVALID_REQUEST_BODY,
138
+ "plan_sequence must be a non-empty array of JSON objects",
139
+ )
140
+ if any(not isinstance(item, dict) for item in plan_sequence_payload):
141
+ return 400, failure_payload(
142
+ service_errors.INVALID_REQUEST_BODY,
143
+ "plan_sequence must be a non-empty array of JSON objects",
144
+ )
145
+ provider = SequentialFakeLLMProvider(
146
+ [json.dumps(item, sort_keys=True) for item in plan_sequence_payload]
147
+ )
148
+ else:
149
+ try:
150
+ provider = OpenAICompatibleProvider(LLMProviderConfig.from_sources())
151
+ except ValueError as exc:
152
+ return 400, failure_payload(service_errors.INVALID_AGENT_CONFIG, str(exc))
153
+ allowed_tools = _service_config.runtime_allowed_tools_for_subject(auth_subject)
154
+ policy = (
155
+ RuntimePolicy(allowed_tools=set(allowed_tools))
156
+ if allowed_tools is not None
157
+ else RuntimePolicy()
158
+ )
159
+ run_id = str(uuid4())
160
+ cancellation_token = RuntimeCancellationToken(
161
+ external_cancellation_probe=(
162
+ lambda: persisted_runtime_cancellation_probe(
163
+ run_id=run_id,
164
+ trace_dir=_service_config.trace_dir,
165
+ )
166
+ if _service_config.trace_dir
167
+ else None
168
+ )
169
+ )
170
+ registry = active_run_registry or ActiveRunRegistry()
171
+ trace_path = ""
172
+ if _service_config.trace_dir:
173
+ initial_trace = running_runtime_trace(
174
+ run_id=run_id,
175
+ goal=goal,
176
+ max_iterations=max_iterations,
177
+ auth_subject=auth_subject,
178
+ runtime_instance_id=registry.instance_id,
179
+ )
180
+ try:
181
+ trace_path = persist_trace(initial_trace, _service_config.trace_dir)
182
+ except OSError as exc:
183
+ return 500, failure_payload(
184
+ service_errors.TRACE_PERSISTENCE_FAILED,
185
+ f"could not persist trace: {exc}",
186
+ )
187
+ release_worker_slot = (
188
+ execution_slot_lease.transfer() if execution_slot_lease is not None else None
189
+ )
190
+ try:
191
+ registry.register(
192
+ run_id,
193
+ auth_subject,
194
+ cancellation_token,
195
+ release=release_worker_slot,
196
+ )
197
+ except Exception:
198
+ if release_worker_slot is not None:
199
+ release_worker_slot()
200
+ raise
201
+
202
+ def execute_runtime_worker() -> Dict[str, Any]:
203
+ try:
204
+ result = run_runtime_agent(
205
+ goal,
206
+ provider=provider,
207
+ run_id=run_id,
208
+ cancellation_token=cancellation_token,
209
+ policy=policy,
210
+ max_iterations=max_iterations,
211
+ approved_action_ids=set(approved_action_ids),
212
+ metadata=metadata,
213
+ tags=tags,
214
+ event_sink=_runtime_event_sink(_service_config),
215
+ hooks=_runtime_hooks(_service_config),
216
+ runtime_workspace_dir=_service_config.runtime_workspace_dir,
217
+ redis_url=_service_config.redis_url,
218
+ milvus_url=_service_config.milvus_url,
219
+ embedding_base_url=_service_config.embedding_base_url,
220
+ embedding_api_key=_service_config.embedding_api_key,
221
+ embedding_model=_service_config.embedding_model,
222
+ embedding_timeout_seconds=_service_config.embedding_timeout_seconds,
223
+ embedding_max_retries=_service_config.embedding_max_retries,
224
+ embedding_retry_backoff_seconds=(
225
+ _service_config.embedding_retry_backoff_seconds
226
+ ),
227
+ external_backend_timeout_seconds=(
228
+ _service_config.external_backend_timeout_seconds
229
+ ),
230
+ )
231
+ result["run_id"] = run_id
232
+ if auth_subject:
233
+ result["auth_subject"] = auth_subject
234
+ if _service_config.trace_dir:
235
+ result = persist_runtime_worker_result(
236
+ run_id=run_id,
237
+ trace_dir=_service_config.trace_dir,
238
+ result=result,
239
+ persist_trace_fn=persist_trace,
240
+ )
241
+ return result
242
+ except Exception as exc:
243
+ if _service_config.trace_dir:
244
+ try:
245
+ persist_failed_runtime_trace(
246
+ run_id=run_id,
247
+ trace_dir=_service_config.trace_dir,
248
+ error_code=service_errors.AGENT_RUN_FAILED,
249
+ error=f"agent run failed: {exc}",
250
+ )
251
+ except (OSError, ValueError):
252
+ pass
253
+ raise
254
+
255
+ def cancel_timed_out_worker() -> None:
256
+ active_run = registry.mark_timed_out(
257
+ run_id,
258
+ reason="runtime run timed out",
259
+ )
260
+ if active_run is None or not _service_config.trace_dir:
261
+ return
262
+ try:
263
+ persist_cancelled_runtime_trace(
264
+ run_id=run_id,
265
+ trace_dir=_service_config.trace_dir,
266
+ active_run=active_run,
267
+ error_code=service_errors.AGENT_RUN_TIMEOUT,
268
+ error="agent run timed out",
269
+ )
270
+ except (OSError, ValueError):
271
+ return
272
+
273
+ try:
274
+ result = run_with_timeout(
275
+ execute_runtime_worker,
276
+ timeout_seconds=_service_config.run_timeout_seconds,
277
+ on_timeout=cancel_timed_out_worker,
278
+ on_complete=lambda: registry.complete(run_id),
279
+ )
280
+ except TimeoutError:
281
+ timeout_payload = failure_payload(
282
+ service_errors.AGENT_RUN_TIMEOUT,
283
+ "agent run timed out",
284
+ )
285
+ timeout_payload["run_id"] = run_id
286
+ if trace_path:
287
+ timeout_payload["trace_path"] = trace_path
288
+ return 504, timeout_payload
289
+ except Exception:
290
+ failure = failure_payload(service_errors.AGENT_RUN_FAILED, "agent run failed")
291
+ failure["run_id"] = run_id
292
+ if trace_path:
293
+ failure["trace_path"] = trace_path
294
+ return 500, failure
295
+ return 200, json_ready(result)
296
+
297
+
298
+ def _runtime_hooks(config: ServiceConfig) -> list[Any]:
299
+ hooks: list[Any] = []
300
+ if config.kafka_audit_url:
301
+ hooks.append(
302
+ KafkaRestAuditHook(
303
+ url=config.kafka_audit_url,
304
+ topic=config.kafka_audit_topic,
305
+ timeout_seconds=config.external_backend_timeout_seconds,
306
+ )
307
+ )
308
+ return hooks
309
+
310
+
311
+ def _runtime_event_sink(config: ServiceConfig):
312
+ if not config.kafka_audit_url:
313
+ return None
314
+ return KafkaRestProgressEventSink(
315
+ url=config.kafka_audit_url,
316
+ topic=config.kafka_audit_topic,
317
+ timeout_seconds=config.external_backend_timeout_seconds,
318
+ fail_closed=True,
319
+ )
320
+
321
+
322
+ def _planned_action_ids(
323
+ plan_payload: Any,
324
+ plan_sequence_payload: Any,
325
+ ) -> set[str] | None:
326
+ plan_payloads = []
327
+ if isinstance(plan_payload, dict):
328
+ plan_payloads.append(plan_payload)
329
+ if isinstance(plan_sequence_payload, list):
330
+ plan_payloads.extend(
331
+ item for item in plan_sequence_payload if isinstance(item, dict)
332
+ )
333
+ if not plan_payloads:
334
+ return None
335
+ action_ids = set()
336
+ inspected_actions = False
337
+ for item in plan_payloads:
338
+ actions = item.get("actions")
339
+ if not isinstance(actions, list):
340
+ continue
341
+ inspected_actions = True
342
+ for action in actions:
343
+ if not isinstance(action, dict):
344
+ continue
345
+ action_id = action.get("id")
346
+ if isinstance(action_id, str) and action_id.strip() == action_id:
347
+ action_ids.add(action_id)
348
+ if not inspected_actions:
349
+ return None
350
+ return action_ids