@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,685 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import contextlib
|
|
5
|
+
import getpass
|
|
6
|
+
import io
|
|
7
|
+
import sys
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from kagent.cli.interactive import (
|
|
12
|
+
run_runtime_interactive as _run_runtime_interactive,
|
|
13
|
+
)
|
|
14
|
+
from kagent.cli.memory import default_runtime_session_memory_path
|
|
15
|
+
from kagent.cli.provider import RuntimeProviderConfigError, runtime_provider_config_message
|
|
16
|
+
from kagent.cli.trace import (
|
|
17
|
+
persist_runtime_cli_trace,
|
|
18
|
+
)
|
|
19
|
+
from kagent.cli.trace import (
|
|
20
|
+
persist_runtime_cli_trace_or_raise as _persist_runtime_cli_trace_or_raise,
|
|
21
|
+
)
|
|
22
|
+
from kagent.cli.ui import runtime_setup_message, runtime_ui_color_enabled
|
|
23
|
+
from kagent.runtime.metadata import (
|
|
24
|
+
validate_runtime_metadata,
|
|
25
|
+
validate_runtime_tags,
|
|
26
|
+
)
|
|
27
|
+
from kagent.utils.json_output import format_and_write_json, json_ready
|
|
28
|
+
|
|
29
|
+
DEFAULT_RUNTIME_MAX_ITERATIONS = 3
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> None:
|
|
33
|
+
warnings.filterwarnings("ignore")
|
|
34
|
+
|
|
35
|
+
parser = argparse.ArgumentParser(description="Run the kagent.")
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"goal",
|
|
38
|
+
nargs="?",
|
|
39
|
+
help="Goal for the agent, for example: 'calculate 2 + 3'",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument("--max-steps", type=int, default=None)
|
|
42
|
+
parser.add_argument("--max-retries", type=int, default=None)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--runtime",
|
|
45
|
+
action="store_true",
|
|
46
|
+
help="Run the plan-act-observe runtime.",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--deterministic",
|
|
50
|
+
"--legacy-graph",
|
|
51
|
+
action="store_true",
|
|
52
|
+
help="Run the deterministic regression graph instead of the default runtime.",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--interactive",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Start the kagent terminal agent that reads goals from stdin.",
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--interactive-json",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="Print full JSON traces in interactive runtime sessions.",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--session-memory",
|
|
66
|
+
default="",
|
|
67
|
+
metavar="PATH",
|
|
68
|
+
help=(
|
|
69
|
+
"Persist interactive runtime session memory to PATH. "
|
|
70
|
+
"Only valid with --runtime --interactive."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--max-iterations",
|
|
75
|
+
type=int,
|
|
76
|
+
default=None,
|
|
77
|
+
help="Maximum runtime planner iterations.",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--runtime-plan",
|
|
81
|
+
default="",
|
|
82
|
+
metavar="JSON",
|
|
83
|
+
help="Use an inline strict runtime plan JSON payload instead of an LLM provider.",
|
|
84
|
+
)
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"--tag",
|
|
87
|
+
action="append",
|
|
88
|
+
default=[],
|
|
89
|
+
metavar="TAG",
|
|
90
|
+
help="Attach a non-secret runtime tag; may be repeated.",
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--metadata",
|
|
94
|
+
action="append",
|
|
95
|
+
default=[],
|
|
96
|
+
metavar="KEY=VALUE",
|
|
97
|
+
help="Attach non-secret runtime metadata; may be repeated.",
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--trace-dir",
|
|
101
|
+
default="",
|
|
102
|
+
metavar="PATH",
|
|
103
|
+
help="Persist one-shot runtime traces to PATH using run_id-based filenames.",
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--list-tools",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="Print the registered deterministic tool names as JSON.",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--verbose",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="Print richer metadata for discovery commands that support it.",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--list-faults",
|
|
117
|
+
action="store_true",
|
|
118
|
+
help="Print supported fault injection names as JSON.",
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--graph",
|
|
122
|
+
action="store_true",
|
|
123
|
+
help="Print the graph topology as JSON.",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--version",
|
|
127
|
+
action="store_true",
|
|
128
|
+
help="Print the package version as JSON.",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--configure",
|
|
132
|
+
action="store_true",
|
|
133
|
+
help="Configure the local OpenAI-compatible runtime provider.",
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
"--inject-wrong-answer",
|
|
137
|
+
action="append",
|
|
138
|
+
default=[],
|
|
139
|
+
metavar="STEP",
|
|
140
|
+
help="Force one wrong answer for STEP to demonstrate reflection and retry.",
|
|
141
|
+
)
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"--inject-fault",
|
|
144
|
+
action="append",
|
|
145
|
+
default=[],
|
|
146
|
+
metavar="STEP=FAULT",
|
|
147
|
+
help="Force a named fault for STEP, such as STEP=empty-answer.",
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--summary",
|
|
151
|
+
action="store_true",
|
|
152
|
+
help="Print a compact run summary instead of the full trace.",
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--plan",
|
|
156
|
+
action="store_true",
|
|
157
|
+
help="Print planner output and validation without executing the graph.",
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"--fail-on-agent-failure",
|
|
161
|
+
action="store_true",
|
|
162
|
+
help="Exit with code 1 when an executed agent run returns failed status.",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--output",
|
|
166
|
+
default="",
|
|
167
|
+
metavar="PATH",
|
|
168
|
+
help="Write the JSON payload to PATH as well as stdout.",
|
|
169
|
+
)
|
|
170
|
+
args = parser.parse_args()
|
|
171
|
+
_apply_default_cli_mode(args)
|
|
172
|
+
|
|
173
|
+
if args.max_steps is not None and args.max_steps < 1:
|
|
174
|
+
parser.error("--max-steps must be at least 1")
|
|
175
|
+
if args.max_retries is not None and args.max_retries < 0:
|
|
176
|
+
parser.error("--max-retries must be non-negative")
|
|
177
|
+
if args.max_iterations is not None and args.max_iterations < 1:
|
|
178
|
+
parser.error("--max-iterations must be at least 1")
|
|
179
|
+
if args.deterministic and args.runtime_plan:
|
|
180
|
+
parser.error("--deterministic cannot be combined with --runtime-plan")
|
|
181
|
+
if args.deterministic:
|
|
182
|
+
args.runtime = False
|
|
183
|
+
if args.deterministic and args.interactive:
|
|
184
|
+
parser.error("--deterministic cannot be combined with --interactive")
|
|
185
|
+
if args.runtime and args.plan:
|
|
186
|
+
parser.error("--plan is not supported with --runtime")
|
|
187
|
+
if args.interactive_json and not args.interactive:
|
|
188
|
+
parser.error("--interactive-json requires --interactive")
|
|
189
|
+
if args.configure and args.goal is not None:
|
|
190
|
+
parser.error("--configure cannot be combined with a goal")
|
|
191
|
+
if args.session_memory and not args.interactive:
|
|
192
|
+
parser.error("--session-memory requires --interactive")
|
|
193
|
+
if args.interactive and args.output:
|
|
194
|
+
parser.error("--output is not supported with --interactive")
|
|
195
|
+
if args.trace_dir and not args.runtime:
|
|
196
|
+
parser.error("--trace-dir requires --runtime")
|
|
197
|
+
if (args.tag or args.metadata) and not args.runtime:
|
|
198
|
+
parser.error("--tag and --metadata require --runtime")
|
|
199
|
+
runtime_metadata, runtime_tags = _runtime_labels_from_args(
|
|
200
|
+
args.metadata,
|
|
201
|
+
args.tag,
|
|
202
|
+
parser,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if args.goal is not None:
|
|
206
|
+
try:
|
|
207
|
+
fault_plan = _build_fault_plan(args.inject_wrong_answer, args.inject_fault)
|
|
208
|
+
except ValueError as exc:
|
|
209
|
+
parser.error(str(exc))
|
|
210
|
+
else:
|
|
211
|
+
fault_plan = {}
|
|
212
|
+
|
|
213
|
+
warning_sink = io.StringIO()
|
|
214
|
+
config_error = ""
|
|
215
|
+
result = {}
|
|
216
|
+
with contextlib.redirect_stderr(warning_sink), warnings.catch_warnings():
|
|
217
|
+
warnings.simplefilter("ignore")
|
|
218
|
+
from kagent import __version__
|
|
219
|
+
from kagent.core.agent import (
|
|
220
|
+
AgentConfig,
|
|
221
|
+
agent_topology,
|
|
222
|
+
preview_plan,
|
|
223
|
+
run_agent,
|
|
224
|
+
)
|
|
225
|
+
from kagent.core.faults import SUPPORTED_FAULTS
|
|
226
|
+
from kagent.core.summary import summarize_run
|
|
227
|
+
from kagent.core.tools import (
|
|
228
|
+
registered_tool_metadata,
|
|
229
|
+
registered_tool_names,
|
|
230
|
+
)
|
|
231
|
+
from kagent.providers.llm import (
|
|
232
|
+
DEFAULT_LLM_MODEL,
|
|
233
|
+
FakeLLMProvider,
|
|
234
|
+
LLMProviderConfig,
|
|
235
|
+
build_llm_provider,
|
|
236
|
+
default_provider_config_path,
|
|
237
|
+
save_provider_config,
|
|
238
|
+
)
|
|
239
|
+
from kagent.runtime import run_runtime_agent, runtime_topology
|
|
240
|
+
from kagent.runtime.tools import (
|
|
241
|
+
registered_runtime_tool_metadata,
|
|
242
|
+
)
|
|
243
|
+
from kagent.service.trace_store import persist_trace
|
|
244
|
+
|
|
245
|
+
if args.list_tools:
|
|
246
|
+
if args.runtime:
|
|
247
|
+
runtime_tools = registered_runtime_tool_metadata()
|
|
248
|
+
tools = (
|
|
249
|
+
runtime_tools
|
|
250
|
+
if args.verbose
|
|
251
|
+
else [tool["name"] for tool in runtime_tools]
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
tools = registered_tool_metadata() if args.verbose else registered_tool_names()
|
|
255
|
+
_emit_json_payload({"tools": tools}, args.output, parser)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if args.list_faults:
|
|
259
|
+
_emit_json_payload({"faults": sorted(SUPPORTED_FAULTS)}, args.output, parser)
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
if args.graph:
|
|
263
|
+
topology = runtime_topology() if args.runtime else agent_topology()
|
|
264
|
+
_emit_json_payload(topology, args.output, parser)
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
if args.version:
|
|
268
|
+
_emit_json_payload({"version": __version__}, args.output, parser)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
if args.configure:
|
|
272
|
+
_configure_runtime_provider_interactively(
|
|
273
|
+
LLMProviderConfig,
|
|
274
|
+
default_model=DEFAULT_LLM_MODEL,
|
|
275
|
+
default_config_path=default_provider_config_path,
|
|
276
|
+
save_config=save_provider_config,
|
|
277
|
+
)
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
if args.interactive:
|
|
281
|
+
try:
|
|
282
|
+
session_memory_path = _session_memory_path_from_args(
|
|
283
|
+
args,
|
|
284
|
+
interactive_tty=sys.stdin.isatty(),
|
|
285
|
+
)
|
|
286
|
+
provider = (
|
|
287
|
+
_runtime_provider_from_args(
|
|
288
|
+
args,
|
|
289
|
+
FakeLLMProvider,
|
|
290
|
+
build_llm_provider,
|
|
291
|
+
LLMProviderConfig,
|
|
292
|
+
interactive_setup=sys.stdin.isatty(),
|
|
293
|
+
default_model=DEFAULT_LLM_MODEL,
|
|
294
|
+
default_config_path=default_provider_config_path,
|
|
295
|
+
save_config=save_provider_config,
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
_run_runtime_interactive(
|
|
299
|
+
provider=provider,
|
|
300
|
+
run_runtime_agent=run_runtime_agent,
|
|
301
|
+
max_iterations=args.max_iterations or DEFAULT_RUNTIME_MAX_ITERATIONS,
|
|
302
|
+
fail_on_agent_failure=args.fail_on_agent_failure,
|
|
303
|
+
full_trace_output=args.interactive_json,
|
|
304
|
+
metadata=runtime_metadata,
|
|
305
|
+
tags=runtime_tags,
|
|
306
|
+
trace_dir=args.trace_dir,
|
|
307
|
+
persist_trace=persist_trace,
|
|
308
|
+
session_memory_path=session_memory_path,
|
|
309
|
+
)
|
|
310
|
+
return
|
|
311
|
+
except RuntimeProviderConfigError as exc:
|
|
312
|
+
_exit_runtime_provider_config_error(str(exc))
|
|
313
|
+
except ValueError as exc:
|
|
314
|
+
config_error = str(exc)
|
|
315
|
+
except OSError as exc:
|
|
316
|
+
config_error = f"could not use session memory: {exc}"
|
|
317
|
+
elif args.goal is None:
|
|
318
|
+
parser.error(
|
|
319
|
+
"goal is required unless --interactive, --list-tools, "
|
|
320
|
+
"--list-faults, --graph, or --version is used"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
elif args.runtime:
|
|
324
|
+
try:
|
|
325
|
+
provider = (
|
|
326
|
+
_runtime_provider_from_args(
|
|
327
|
+
args,
|
|
328
|
+
FakeLLMProvider,
|
|
329
|
+
build_llm_provider,
|
|
330
|
+
LLMProviderConfig,
|
|
331
|
+
interactive_setup=sys.stdin.isatty(),
|
|
332
|
+
default_model=DEFAULT_LLM_MODEL,
|
|
333
|
+
default_config_path=default_provider_config_path,
|
|
334
|
+
save_config=save_provider_config,
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
result = run_runtime_agent(
|
|
338
|
+
args.goal,
|
|
339
|
+
provider=provider,
|
|
340
|
+
max_iterations=args.max_iterations or DEFAULT_RUNTIME_MAX_ITERATIONS,
|
|
341
|
+
metadata=runtime_metadata,
|
|
342
|
+
tags=runtime_tags,
|
|
343
|
+
)
|
|
344
|
+
if args.trace_dir:
|
|
345
|
+
_persist_runtime_cli_trace_or_raise(result, args.trace_dir, persist_trace)
|
|
346
|
+
except RuntimeProviderConfigError as exc:
|
|
347
|
+
_exit_runtime_provider_config_error(str(exc))
|
|
348
|
+
except ValueError as exc:
|
|
349
|
+
config_error = str(exc)
|
|
350
|
+
except OSError as exc:
|
|
351
|
+
config_error = f"could not persist --trace-dir trace: {exc}"
|
|
352
|
+
else:
|
|
353
|
+
try:
|
|
354
|
+
config = _config_from_args(AgentConfig, args.max_steps, args.max_retries)
|
|
355
|
+
except ValueError as exc:
|
|
356
|
+
config_error = str(exc)
|
|
357
|
+
else:
|
|
358
|
+
if args.plan:
|
|
359
|
+
result = preview_plan(args.goal, config=config)
|
|
360
|
+
else:
|
|
361
|
+
result = run_agent(
|
|
362
|
+
args.goal,
|
|
363
|
+
config=config,
|
|
364
|
+
fault_plan=fault_plan,
|
|
365
|
+
)
|
|
366
|
+
if args.summary and not args.plan:
|
|
367
|
+
result = summarize_run(result)
|
|
368
|
+
if config_error:
|
|
369
|
+
parser.error(config_error)
|
|
370
|
+
payload = json_ready(result)
|
|
371
|
+
_emit_json_payload(payload, args.output, parser)
|
|
372
|
+
if args.fail_on_agent_failure and not args.plan and payload.get("status") == "failed":
|
|
373
|
+
raise SystemExit(1)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _runtime_provider_from_args(
|
|
377
|
+
args: argparse.Namespace,
|
|
378
|
+
FakeLLMProvider,
|
|
379
|
+
build_llm_provider,
|
|
380
|
+
LLMProviderConfig,
|
|
381
|
+
*,
|
|
382
|
+
interactive_setup: bool = False,
|
|
383
|
+
default_model: str = "qwen3.5-122b-a10b",
|
|
384
|
+
default_config_path=None,
|
|
385
|
+
save_config=None,
|
|
386
|
+
):
|
|
387
|
+
from kagent.providers.llm import missing_provider_config_fields
|
|
388
|
+
|
|
389
|
+
if args.runtime_plan:
|
|
390
|
+
return FakeLLMProvider(args.runtime_plan)
|
|
391
|
+
config = LLMProviderConfig.from_sources()
|
|
392
|
+
missing = missing_provider_config_fields(config)
|
|
393
|
+
if missing:
|
|
394
|
+
if interactive_setup and default_config_path is not None and save_config is not None:
|
|
395
|
+
config = _configure_runtime_provider_interactively(
|
|
396
|
+
LLMProviderConfig,
|
|
397
|
+
default_model=default_model,
|
|
398
|
+
default_config_path=default_config_path,
|
|
399
|
+
save_config=save_config,
|
|
400
|
+
)
|
|
401
|
+
missing = missing_provider_config_fields(config)
|
|
402
|
+
if not missing:
|
|
403
|
+
return build_llm_provider(config)
|
|
404
|
+
raise RuntimeProviderConfigError(runtime_provider_config_message(missing))
|
|
405
|
+
return build_llm_provider(config)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _configure_runtime_provider_interactively(
|
|
409
|
+
LLMProviderConfig,
|
|
410
|
+
*,
|
|
411
|
+
default_model: str,
|
|
412
|
+
default_config_path,
|
|
413
|
+
save_config,
|
|
414
|
+
input_fn=input,
|
|
415
|
+
secret_input_fn=getpass.getpass,
|
|
416
|
+
) -> object:
|
|
417
|
+
prompt_stream = sys.__stderr__ or sys.stderr
|
|
418
|
+
config_path = default_config_path()
|
|
419
|
+
print(
|
|
420
|
+
runtime_setup_message(
|
|
421
|
+
config_path=config_path,
|
|
422
|
+
color=runtime_ui_color_enabled(),
|
|
423
|
+
),
|
|
424
|
+
file=prompt_stream,
|
|
425
|
+
)
|
|
426
|
+
provider_option = _select_provider_for_setup(
|
|
427
|
+
default_model=default_model,
|
|
428
|
+
input_fn=input_fn,
|
|
429
|
+
prompt_stream=prompt_stream,
|
|
430
|
+
)
|
|
431
|
+
provider = provider_option["provider"]
|
|
432
|
+
default_base_url = str(provider_option["base_url"])
|
|
433
|
+
default_provider_model = str(provider_option["model"])
|
|
434
|
+
base_url_prompt = (
|
|
435
|
+
f"Base URL [{default_base_url}]: " if default_base_url else "Base URL: "
|
|
436
|
+
)
|
|
437
|
+
base_url = input_fn(base_url_prompt).strip() or default_base_url
|
|
438
|
+
model = (
|
|
439
|
+
input_fn(f"Model [{default_provider_model}]: ").strip()
|
|
440
|
+
or default_provider_model
|
|
441
|
+
)
|
|
442
|
+
api_key = secret_input_fn("API key: ").strip()
|
|
443
|
+
config = LLMProviderConfig(
|
|
444
|
+
provider=provider,
|
|
445
|
+
base_url=base_url,
|
|
446
|
+
api_key=api_key,
|
|
447
|
+
model=model,
|
|
448
|
+
)
|
|
449
|
+
from kagent.providers.llm import validate_provider_setup_config
|
|
450
|
+
|
|
451
|
+
validate_provider_setup_config(config)
|
|
452
|
+
saved_path = save_config(config)
|
|
453
|
+
print(f"kagent provider config saved to {saved_path}", file=prompt_stream)
|
|
454
|
+
return config
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _select_provider_for_setup(
|
|
458
|
+
*,
|
|
459
|
+
default_model: str,
|
|
460
|
+
input_fn,
|
|
461
|
+
prompt_stream,
|
|
462
|
+
) -> dict[str, object]:
|
|
463
|
+
options = _provider_setup_options(default_model)
|
|
464
|
+
if _can_use_arrow_provider_menu(input_fn, prompt_stream):
|
|
465
|
+
return _select_provider_with_arrow_keys(options, prompt_stream)
|
|
466
|
+
print("Select provider:", file=prompt_stream)
|
|
467
|
+
for index, option in enumerate(options, start=1):
|
|
468
|
+
print(
|
|
469
|
+
f" {index}. {option['label']} ({option['provider'].value})",
|
|
470
|
+
file=prompt_stream,
|
|
471
|
+
)
|
|
472
|
+
answer = input_fn("Provider [1]: ").strip()
|
|
473
|
+
if not answer:
|
|
474
|
+
return options[0]
|
|
475
|
+
try:
|
|
476
|
+
selected_index = int(answer)
|
|
477
|
+
except ValueError as exc:
|
|
478
|
+
raise ValueError("provider selection must be a number") from exc
|
|
479
|
+
if selected_index < 1 or selected_index > len(options):
|
|
480
|
+
raise ValueError("provider selection is out of range")
|
|
481
|
+
return options[selected_index - 1]
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _provider_setup_options(default_model: str) -> list[dict[str, object]]:
|
|
485
|
+
from kagent.providers.llm import provider_setup_options
|
|
486
|
+
|
|
487
|
+
return provider_setup_options(default_model)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _can_use_arrow_provider_menu(input_fn, prompt_stream) -> bool:
|
|
491
|
+
return (
|
|
492
|
+
input_fn is input
|
|
493
|
+
and sys.stdin.isatty()
|
|
494
|
+
and hasattr(prompt_stream, "isatty")
|
|
495
|
+
and prompt_stream.isatty()
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _select_provider_with_arrow_keys(
|
|
500
|
+
options: list[dict[str, object]],
|
|
501
|
+
prompt_stream,
|
|
502
|
+
) -> dict[str, object]:
|
|
503
|
+
import termios
|
|
504
|
+
import tty
|
|
505
|
+
|
|
506
|
+
input_stream = sys.stdin
|
|
507
|
+
fd = input_stream.fileno()
|
|
508
|
+
old_settings = termios.tcgetattr(fd)
|
|
509
|
+
selected = 0
|
|
510
|
+
print("Select provider with Up/Down, Enter to confirm:", file=prompt_stream)
|
|
511
|
+
|
|
512
|
+
def render() -> None:
|
|
513
|
+
print(f"\x1b[{len(options)}A", end="", file=prompt_stream)
|
|
514
|
+
for index, option in enumerate(options):
|
|
515
|
+
marker = ">" if index == selected else " "
|
|
516
|
+
print(
|
|
517
|
+
f"\x1b[2K\r {marker} {option['label']} ({option['provider'].value})",
|
|
518
|
+
file=prompt_stream,
|
|
519
|
+
)
|
|
520
|
+
prompt_stream.flush()
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
tty.setcbreak(fd)
|
|
524
|
+
print("\x1b[?25l", end="", file=prompt_stream)
|
|
525
|
+
for index, option in enumerate(options):
|
|
526
|
+
marker = ">" if index == selected else " "
|
|
527
|
+
print(
|
|
528
|
+
f" {marker} {option['label']} ({option['provider'].value})",
|
|
529
|
+
file=prompt_stream,
|
|
530
|
+
)
|
|
531
|
+
prompt_stream.flush()
|
|
532
|
+
while True:
|
|
533
|
+
char = input_stream.read(1)
|
|
534
|
+
if char in {"\r", "\n"}:
|
|
535
|
+
break
|
|
536
|
+
if char == "\x03":
|
|
537
|
+
raise KeyboardInterrupt
|
|
538
|
+
if char != "\x1b":
|
|
539
|
+
continue
|
|
540
|
+
sequence = input_stream.read(2)
|
|
541
|
+
if sequence == "[A":
|
|
542
|
+
selected = (selected - 1) % len(options)
|
|
543
|
+
render()
|
|
544
|
+
elif sequence == "[B":
|
|
545
|
+
selected = (selected + 1) % len(options)
|
|
546
|
+
render()
|
|
547
|
+
finally:
|
|
548
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
549
|
+
print("\x1b[?25h", end="", file=prompt_stream)
|
|
550
|
+
print(file=prompt_stream)
|
|
551
|
+
return options[selected]
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _exit_runtime_provider_config_error(message: str) -> None:
|
|
555
|
+
print(message, file=sys.__stderr__)
|
|
556
|
+
raise SystemExit(2)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _apply_default_cli_mode(args: argparse.Namespace) -> None:
|
|
560
|
+
if getattr(args, "configure", False):
|
|
561
|
+
return
|
|
562
|
+
if _uses_deterministic_graph(args):
|
|
563
|
+
args.deterministic = True
|
|
564
|
+
return
|
|
565
|
+
if args.runtime_plan:
|
|
566
|
+
args.runtime = True
|
|
567
|
+
if args.interactive:
|
|
568
|
+
args.runtime = True
|
|
569
|
+
if args.goal is not None and not _is_introspection_command(args):
|
|
570
|
+
args.runtime = True
|
|
571
|
+
if args.goal is None and not _is_introspection_command(args):
|
|
572
|
+
args.runtime = True
|
|
573
|
+
args.interactive = True
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _uses_deterministic_graph(args: argparse.Namespace) -> bool:
|
|
577
|
+
return bool(
|
|
578
|
+
args.deterministic
|
|
579
|
+
or args.plan
|
|
580
|
+
or args.summary
|
|
581
|
+
or args.max_steps is not None
|
|
582
|
+
or args.max_retries is not None
|
|
583
|
+
or args.inject_wrong_answer
|
|
584
|
+
or args.inject_fault
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _is_introspection_command(args: argparse.Namespace) -> bool:
|
|
589
|
+
return bool(
|
|
590
|
+
args.list_tools
|
|
591
|
+
or args.list_faults
|
|
592
|
+
or args.graph
|
|
593
|
+
or args.version
|
|
594
|
+
or getattr(args, "configure", False)
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _session_memory_path_from_args(
|
|
599
|
+
args: argparse.Namespace,
|
|
600
|
+
*,
|
|
601
|
+
interactive_tty: bool,
|
|
602
|
+
) -> str:
|
|
603
|
+
if args.session_memory:
|
|
604
|
+
return args.session_memory
|
|
605
|
+
if not interactive_tty:
|
|
606
|
+
return ""
|
|
607
|
+
return default_runtime_session_memory_path()
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _runtime_labels_from_args(
|
|
611
|
+
metadata_items: list[str],
|
|
612
|
+
tag_items: list[str],
|
|
613
|
+
parser: argparse.ArgumentParser,
|
|
614
|
+
) -> tuple[dict[str, str], list[str]]:
|
|
615
|
+
raw_metadata = {}
|
|
616
|
+
for item in metadata_items:
|
|
617
|
+
if "=" not in item:
|
|
618
|
+
parser.error("--metadata must use KEY=VALUE")
|
|
619
|
+
key, value = item.split("=", 1)
|
|
620
|
+
raw_metadata[key] = value
|
|
621
|
+
metadata, metadata_error = validate_runtime_metadata(raw_metadata or None)
|
|
622
|
+
if metadata_error:
|
|
623
|
+
parser.error(metadata_error)
|
|
624
|
+
tags, tags_error = validate_runtime_tags(tag_items or None)
|
|
625
|
+
if tags_error:
|
|
626
|
+
parser.error(tags_error)
|
|
627
|
+
return metadata, tags
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _persist_runtime_cli_trace(
|
|
631
|
+
result: dict,
|
|
632
|
+
trace_dir: str,
|
|
633
|
+
persist_trace: Any,
|
|
634
|
+
) -> None:
|
|
635
|
+
persist_runtime_cli_trace(result, trace_dir, persist_trace)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _emit_json_payload(
|
|
639
|
+
payload: Any,
|
|
640
|
+
output_path: str,
|
|
641
|
+
parser: argparse.ArgumentParser,
|
|
642
|
+
) -> None:
|
|
643
|
+
try:
|
|
644
|
+
json_payload = format_and_write_json(json_ready(payload), output_path)
|
|
645
|
+
except OSError as exc:
|
|
646
|
+
parser.error(f"could not write --output file: {exc}")
|
|
647
|
+
print(json_payload)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def _build_fault_plan(injected_steps: list, injected_faults: list) -> dict:
|
|
651
|
+
fault_plan = {}
|
|
652
|
+
for step in injected_steps:
|
|
653
|
+
_append_fault(fault_plan, step, "wrong-answer")
|
|
654
|
+
for item in injected_faults:
|
|
655
|
+
step, fault = _parse_fault(item)
|
|
656
|
+
_append_fault(fault_plan, step, fault)
|
|
657
|
+
return fault_plan
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _config_from_args(AgentConfig, max_steps, max_retries):
|
|
661
|
+
config = AgentConfig.from_env()
|
|
662
|
+
return AgentConfig(
|
|
663
|
+
max_steps=max_steps if max_steps is not None else config.max_steps,
|
|
664
|
+
max_retries=max_retries if max_retries is not None else config.max_retries,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _append_fault(fault_plan: dict, step: str, fault: str) -> None:
|
|
669
|
+
from kagent.core.faults import validate_faults
|
|
670
|
+
from kagent.core.normalization import normalize_goal
|
|
671
|
+
|
|
672
|
+
validate_faults([fault])
|
|
673
|
+
normalized_step = normalize_goal(step)
|
|
674
|
+
fault_plan.setdefault(normalized_step, []).append(fault)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _parse_fault(item: str) -> tuple:
|
|
678
|
+
if "=" not in item:
|
|
679
|
+
raise ValueError("--inject-fault must use STEP=FAULT")
|
|
680
|
+
step, fault = item.rsplit("=", 1)
|
|
681
|
+
return step, fault.strip()
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
if __name__ == "__main__":
|
|
685
|
+
main()
|