klyro 0.1.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/agent/anthropic-adapter.d.ts +75 -0
- package/dist/agent/anthropic-adapter.js +297 -0
- package/dist/agent/message.d.ts +40 -0
- package/dist/agent/message.js +21 -0
- package/dist/agent/observation.d.ts +47 -0
- package/dist/agent/observation.js +53 -0
- package/dist/agent/provider-adapter.d.ts +101 -0
- package/dist/agent/provider-adapter.js +254 -0
- package/dist/agent/registry.d.ts +42 -0
- package/dist/agent/registry.js +86 -0
- package/dist/agent/retry.d.ts +34 -0
- package/dist/agent/retry.js +91 -0
- package/dist/agent/runtime.d.ts +130 -0
- package/dist/agent/runtime.js +221 -0
- package/dist/agent/worker-spawner.d.ts +41 -0
- package/dist/agent/worker-spawner.js +76 -0
- package/dist/chat.d.ts +39 -0
- package/dist/chat.js +235 -0
- package/dist/chat.js.map +1 -0
- package/dist/cli/eval.d.ts +75 -0
- package/dist/cli/eval.js +190 -0
- package/dist/cli/repl.d.ts +15 -0
- package/dist/cli/repl.js +194 -0
- package/dist/cli/run.d.ts +67 -0
- package/dist/cli/run.js +198 -0
- package/dist/cli/slash/parser.d.ts +39 -0
- package/dist/cli/slash/parser.js +47 -0
- package/dist/context/level6.d.ts +45 -0
- package/dist/context/level6.js +231 -0
- package/dist/context/level7.d.ts +84 -0
- package/dist/context/level7.js +156 -0
- package/dist/context/project-map.d.ts +48 -0
- package/dist/context/project-map.js +438 -0
- package/dist/context/repo-map.d.ts +30 -0
- package/dist/context/repo-map.js +168 -0
- package/dist/context/selector.d.ts +22 -0
- package/dist/context/selector.js +37 -0
- package/dist/context/snippets.d.ts +17 -0
- package/dist/context/snippets.js +15 -0
- package/dist/context/tokenizer.d.ts +49 -0
- package/dist/context/tokenizer.js +97 -0
- package/dist/eval/harness.d.ts +48 -0
- package/dist/eval/harness.js +149 -0
- package/dist/eval/tasks.d.ts +6 -0
- package/dist/eval/tasks.js +98 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/audit.d.ts +75 -0
- package/dist/persistence/audit.js +15 -0
- package/dist/persistence/store.d.ts +67 -0
- package/dist/persistence/store.js +106 -0
- package/dist/policy/approval.d.ts +35 -0
- package/dist/policy/approval.js +54 -0
- package/dist/policy/engine.d.ts +76 -0
- package/dist/policy/engine.js +163 -0
- package/dist/policy/path-guard.d.ts +30 -0
- package/dist/policy/path-guard.js +100 -0
- package/dist/policy/secret-redactor.d.ts +19 -0
- package/dist/policy/secret-redactor.js +52 -0
- package/dist/providers.d.ts +23 -0
- package/dist/providers.js +83 -0
- package/dist/repl.d.ts +9 -0
- package/dist/repl.js +170 -0
- package/dist/repl.js.map +1 -0
- package/dist/tools/fs/edit-file.d.ts +48 -0
- package/dist/tools/fs/edit-file.js +84 -0
- package/dist/tools/fs/list-dir.d.ts +40 -0
- package/dist/tools/fs/list-dir.js +72 -0
- package/dist/tools/fs/read-file.d.ts +66 -0
- package/dist/tools/fs/read-file.js +75 -0
- package/dist/tools/fs/write-file.d.ts +24 -0
- package/dist/tools/fs/write-file.js +48 -0
- package/dist/tools/git/git-diff.d.ts +35 -0
- package/dist/tools/git/git-diff.js +68 -0
- package/dist/tools/git/git-status.d.ts +29 -0
- package/dist/tools/git/git-status.js +57 -0
- package/dist/tools/normalize.d.ts +31 -0
- package/dist/tools/normalize.js +88 -0
- package/dist/tools/registry.d.ts +23 -0
- package/dist/tools/registry.js +87 -0
- package/dist/tools/schema.d.ts +29 -0
- package/dist/tools/schema.js +183 -0
- package/dist/tools/search/dependencies.d.ts +44 -0
- package/dist/tools/search/dependencies.js +188 -0
- package/dist/tools/search/glob.d.ts +26 -0
- package/dist/tools/search/glob.js +88 -0
- package/dist/tools/search/grep.d.ts +52 -0
- package/dist/tools/search/grep.js +125 -0
- package/dist/tools/search/recent-files.d.ts +33 -0
- package/dist/tools/search/recent-files.js +100 -0
- package/dist/tools/search/search-files.d.ts +39 -0
- package/dist/tools/search/search-files.js +110 -0
- package/dist/tools/shell/shell-exec.d.ts +60 -0
- package/dist/tools/shell/shell-exec.js +133 -0
- package/dist/tools/types.d.ts +54 -0
- package/dist/tools/types.js +15 -0
- package/dist/tools/verify/run-verify.d.ts +60 -0
- package/dist/tools/verify/run-verify.js +115 -0
- package/dist/tui/app.d.ts +27 -0
- package/dist/tui/app.js +79 -0
- package/dist/tui/app.test.d.ts +1 -0
- package/dist/tui/app.test.js +77 -0
- package/dist/tui/status.d.ts +21 -0
- package/dist/tui/status.js +13 -0
- package/dist/tui/status.test.d.ts +1 -0
- package/dist/tui/status.test.js +39 -0
- package/dist/tui/transcript.d.ts +40 -0
- package/dist/tui/transcript.js +28 -0
- package/dist/tui/transcript.test.d.ts +1 -0
- package/dist/tui/transcript.test.js +71 -0
- package/dist/verification/detect.d.ts +29 -0
- package/dist/verification/detect.js +85 -0
- package/dist/verification/engine.d.ts +25 -0
- package/dist/verification/engine.js +57 -0
- package/package.json +58 -0
package/dist/chat.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot chat. POSTs to an OpenAI-compatible /v1/chat/completions endpoint
|
|
3
|
+
* and streams the response to stdout.
|
|
4
|
+
*
|
|
5
|
+
* Config (env):
|
|
6
|
+
* KLYRO_BASE_URL e.g. https://api.openai.com/v1
|
|
7
|
+
* KLYRO_API_KEY bearer token
|
|
8
|
+
* KLYRO_MODEL e.g. gpt-4o-mini
|
|
9
|
+
* KLYRO_TIMEOUT_MS request timeout in ms (default 60000)
|
|
10
|
+
*
|
|
11
|
+
* No abstractions, no registry, no retries. If you want those, layer them on later.
|
|
12
|
+
*/
|
|
13
|
+
/** Default request timeout: 60s. */
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
15
|
+
/** Max bytes of an error response body we will print. */
|
|
16
|
+
const MAX_ERROR_BODY_BYTES = 4_000;
|
|
17
|
+
/** Strip a trailing slash so we can append /chat/completions cleanly. */
|
|
18
|
+
export function normalizeBaseURL(url) {
|
|
19
|
+
return url.replace(/\/+$/, '');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate that the base URL is HTTPS (or localhost over HTTP for local LLMs).
|
|
23
|
+
* Refuses to send the bearer token over a plaintext remote connection.
|
|
24
|
+
*/
|
|
25
|
+
export function assertSafeBaseURL(url) {
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = new URL(url);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error(`KLYRO_BASE_URL is not a valid URL: ${url}`);
|
|
32
|
+
}
|
|
33
|
+
if (parsed.protocol === 'https:')
|
|
34
|
+
return;
|
|
35
|
+
if (parsed.protocol === 'http:') {
|
|
36
|
+
const host = parsed.hostname.toLowerCase();
|
|
37
|
+
if (host === 'localhost' || host === '127.0.0.1' || host === '::1')
|
|
38
|
+
return;
|
|
39
|
+
throw new Error(`Refusing to send KLYRO_API_KEY over plaintext HTTP to ${host}. ` +
|
|
40
|
+
`Use https:// or a localhost URL.`);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Unsupported KLYRO_BASE_URL protocol: ${parsed.protocol}`);
|
|
43
|
+
}
|
|
44
|
+
export async function chat(prompt, system, modelOverride, opts = {}) {
|
|
45
|
+
const baseURL = opts.baseURL ?? process.env.KLYRO_BASE_URL ?? 'https://api.openai.com/v1';
|
|
46
|
+
const apiKey = opts.apiKey ?? process.env.KLYRO_API_KEY;
|
|
47
|
+
const model = modelOverride ?? opts.model ?? process.env.KLYRO_MODEL ?? 'gpt-4o-mini';
|
|
48
|
+
const timeoutMs = opts.timeoutMs ?? (Number(process.env.KLYRO_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS);
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
console.error('klyro: KLYRO_API_KEY is not set. Set it in your environment.');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
assertSafeBaseURL(baseURL);
|
|
54
|
+
const ac = new AbortController();
|
|
55
|
+
const timeout = setTimeout(() => ac.abort(new Error(`request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
56
|
+
// If the caller passed their own signal, abort when they abort.
|
|
57
|
+
if (opts.signal) {
|
|
58
|
+
if (opts.signal.aborted)
|
|
59
|
+
ac.abort(opts.signal.reason);
|
|
60
|
+
else
|
|
61
|
+
opts.signal.addEventListener('abort', () => ac.abort(opts.signal?.reason), { once: true });
|
|
62
|
+
}
|
|
63
|
+
let res;
|
|
64
|
+
try {
|
|
65
|
+
res = await fetch(`${normalizeBaseURL(baseURL)}/chat/completions`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
Authorization: `Bearer ${apiKey}`,
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
model,
|
|
73
|
+
messages: [
|
|
74
|
+
{ role: 'system', content: system },
|
|
75
|
+
{ role: 'user', content: prompt },
|
|
76
|
+
],
|
|
77
|
+
stream: true,
|
|
78
|
+
}),
|
|
79
|
+
signal: ac.signal,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
85
|
+
console.error(`klyro: request failed: ${msg}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
const text = await readBoundedText(res.body, MAX_ERROR_BODY_BYTES);
|
|
91
|
+
console.error(`klyro: HTTP ${res.status} ${res.statusText}\n${text}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (!res.body) {
|
|
95
|
+
console.error('klyro: response had no body');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await streamToStdout(res.body, ac.signal);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (ac.signal.aborted) {
|
|
103
|
+
// Cancelled by us (timeout) or the caller. Quiet exit.
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
107
|
+
console.error(`\nklyro: stream error: ${msg}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Parse SSE frames and write text deltas to stdout. Each `data: ...` line is
|
|
113
|
+
* one chunk; `[DONE]` terminates the stream. Respects backpressure on stdout
|
|
114
|
+
* and the provided abort signal.
|
|
115
|
+
*/
|
|
116
|
+
export async function streamToStdout(body, signal) {
|
|
117
|
+
const reader = body.getReader();
|
|
118
|
+
const decoder = new TextDecoder('utf-8');
|
|
119
|
+
let buf = '';
|
|
120
|
+
process.stdout.write('\n');
|
|
121
|
+
try {
|
|
122
|
+
while (true) {
|
|
123
|
+
if (signal.aborted)
|
|
124
|
+
throw new Error('aborted');
|
|
125
|
+
const { value, done } = await reader.read();
|
|
126
|
+
if (done)
|
|
127
|
+
break;
|
|
128
|
+
buf += decoder.decode(value, { stream: true });
|
|
129
|
+
let idx;
|
|
130
|
+
while ((idx = buf.indexOf('\n')) !== -1) {
|
|
131
|
+
if (signal.aborted)
|
|
132
|
+
throw new Error('aborted');
|
|
133
|
+
const line = buf.slice(0, idx).replace(/\r$/, '');
|
|
134
|
+
buf = buf.slice(idx + 1);
|
|
135
|
+
if (!line.startsWith('data:'))
|
|
136
|
+
continue;
|
|
137
|
+
const data = line.slice(5).trim();
|
|
138
|
+
if (data === '[DONE]') {
|
|
139
|
+
process.stdout.write('\n');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(data);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const text = parsed.choices?.[0]?.delta?.content;
|
|
150
|
+
if (text) {
|
|
151
|
+
if (!await writeWithBackpressure(text)) {
|
|
152
|
+
// stdout closed (pipe severed). Stop reading.
|
|
153
|
+
try {
|
|
154
|
+
await reader.cancel();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
/* ignore */
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
process.stdout.write('\n');
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
try {
|
|
168
|
+
reader.releaseLock();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
/* already released */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Write to stdout and wait for the drain event if the buffer is full.
|
|
177
|
+
* Returns false if stdout has been closed (e.g. piped to `head`).
|
|
178
|
+
*/
|
|
179
|
+
function writeWithBackpressure(chunk) {
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
if (!process.stdout.write(chunk)) {
|
|
182
|
+
const onDrain = () => {
|
|
183
|
+
process.stdout.off('error', onError);
|
|
184
|
+
resolve(true);
|
|
185
|
+
};
|
|
186
|
+
const onError = () => {
|
|
187
|
+
process.stdout.off('drain', onDrain);
|
|
188
|
+
resolve(false);
|
|
189
|
+
};
|
|
190
|
+
process.stdout.once('drain', onDrain);
|
|
191
|
+
process.stdout.once('error', onError);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
resolve(true);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Read up to `max` bytes from a response body. Used for error responses where
|
|
200
|
+
* we want to surface the cause without risking OOM on a misbehaving server.
|
|
201
|
+
*/
|
|
202
|
+
export async function readBoundedText(body, max) {
|
|
203
|
+
if (!body)
|
|
204
|
+
return '';
|
|
205
|
+
const reader = body.getReader();
|
|
206
|
+
let received = 0;
|
|
207
|
+
const chunks = [];
|
|
208
|
+
try {
|
|
209
|
+
while (received < max) {
|
|
210
|
+
const { value, done } = await reader.read();
|
|
211
|
+
if (done)
|
|
212
|
+
break;
|
|
213
|
+
const remaining = max - received;
|
|
214
|
+
if (value.byteLength <= remaining) {
|
|
215
|
+
chunks.push(value);
|
|
216
|
+
received += value.byteLength;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
chunks.push(value.slice(0, remaining));
|
|
220
|
+
received += remaining;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
try {
|
|
226
|
+
reader.releaseLock();
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
/* ignore */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const decoder = new TextDecoder('utf-8');
|
|
233
|
+
const text = decoder.decode(Buffer.concat(chunks));
|
|
234
|
+
return received >= max ? `${text}\n[truncated]` : text;
|
|
235
|
+
}
|
package/dist/chat.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `klyro eval` — drive the agent runtime against scripted scenarios.
|
|
3
|
+
*
|
|
4
|
+
* Useful for:
|
|
5
|
+
* - regression testing the loop (status code, tool call count, etc.)
|
|
6
|
+
* - reproducing a buggy run from a saved transcript
|
|
7
|
+
* - smoke-testing provider adapters without hitting a real API
|
|
8
|
+
*
|
|
9
|
+
* Each scenario in the JSONL input is one object:
|
|
10
|
+
*
|
|
11
|
+
* {
|
|
12
|
+
* "name": "string-contains-shell",
|
|
13
|
+
* "task": "say hi and run shell_exec",
|
|
14
|
+
* "model": "mock",
|
|
15
|
+
* "maxSteps": 5,
|
|
16
|
+
* "scripted_events": [
|
|
17
|
+
* [["message_start"], ["text_delta", "Hello"], ["message_end", "stop"]],
|
|
18
|
+
* [["message_start"], ["tool_call_start", "c1", "shell_exec"],
|
|
19
|
+
* ["tool_call_delta", "c1", "{\"command\":\"echo hi\"}"],
|
|
20
|
+
* ["tool_call_end", "c1"], ["message_end", "tool_calls"]]
|
|
21
|
+
* ],
|
|
22
|
+
* "expect": {
|
|
23
|
+
* "status": "complete" | "max_steps" | "no_final" | "aborted",
|
|
24
|
+
* "textContains": "Hello",
|
|
25
|
+
* "toolCallsAtLeast": 1,
|
|
26
|
+
* "toolCallsAtMost": 3
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* `scripted_events` is an array of *steps*; each step is an array of
|
|
31
|
+
* [eventKind, ...args] tuples. The i-th step of the scenario is fed to
|
|
32
|
+
* the i-th call to the adapter.stream() generator.
|
|
33
|
+
*
|
|
34
|
+
* If `scripted_events` is omitted or empty, the scenario just runs to
|
|
35
|
+
* max_steps with no model output (useful for testing the harness
|
|
36
|
+
* itself).
|
|
37
|
+
*
|
|
38
|
+
* Output: prints each scenario's pass/fail as a line; if --output json
|
|
39
|
+
* is set, emits one JSON object per line. Exit code 0 if all pass, 1
|
|
40
|
+
* otherwise.
|
|
41
|
+
*/
|
|
42
|
+
import { type RunResult } from '../agent/runtime.js';
|
|
43
|
+
import type { ProviderAdapter } from '../agent/provider-adapter.js';
|
|
44
|
+
export interface EvalScenario {
|
|
45
|
+
name: string;
|
|
46
|
+
task: string;
|
|
47
|
+
model?: string;
|
|
48
|
+
maxSteps?: number;
|
|
49
|
+
maxTokens?: number;
|
|
50
|
+
/** Adapter events to script, as [eventKind, ...args] tuples. */
|
|
51
|
+
scripted_events?: Array<Array<unknown[]>>;
|
|
52
|
+
expect?: {
|
|
53
|
+
status?: RunResult['status'];
|
|
54
|
+
textContains?: string;
|
|
55
|
+
toolCallsAtLeast?: number;
|
|
56
|
+
toolCallsAtMost?: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export interface EvalResult {
|
|
60
|
+
name: string;
|
|
61
|
+
passed: boolean;
|
|
62
|
+
failures: string[];
|
|
63
|
+
status: RunResult['status'];
|
|
64
|
+
steps: number;
|
|
65
|
+
toolCalls: number;
|
|
66
|
+
text: string;
|
|
67
|
+
durationMs: number;
|
|
68
|
+
}
|
|
69
|
+
export interface RunEvalOptions {
|
|
70
|
+
inputPath: string;
|
|
71
|
+
output: 'human' | 'json' | 'silent';
|
|
72
|
+
}
|
|
73
|
+
export declare function runEval(opts: RunEvalOptions): Promise<number>;
|
|
74
|
+
export declare function scriptedAdapterFromSpec(spec: Array<Array<unknown[]>> | undefined): ProviderAdapter;
|
|
75
|
+
export declare function runScenario(sc: EvalScenario): Promise<EvalResult>;
|
package/dist/cli/eval.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `klyro eval` — drive the agent runtime against scripted scenarios.
|
|
3
|
+
*
|
|
4
|
+
* Useful for:
|
|
5
|
+
* - regression testing the loop (status code, tool call count, etc.)
|
|
6
|
+
* - reproducing a buggy run from a saved transcript
|
|
7
|
+
* - smoke-testing provider adapters without hitting a real API
|
|
8
|
+
*
|
|
9
|
+
* Each scenario in the JSONL input is one object:
|
|
10
|
+
*
|
|
11
|
+
* {
|
|
12
|
+
* "name": "string-contains-shell",
|
|
13
|
+
* "task": "say hi and run shell_exec",
|
|
14
|
+
* "model": "mock",
|
|
15
|
+
* "maxSteps": 5,
|
|
16
|
+
* "scripted_events": [
|
|
17
|
+
* [["message_start"], ["text_delta", "Hello"], ["message_end", "stop"]],
|
|
18
|
+
* [["message_start"], ["tool_call_start", "c1", "shell_exec"],
|
|
19
|
+
* ["tool_call_delta", "c1", "{\"command\":\"echo hi\"}"],
|
|
20
|
+
* ["tool_call_end", "c1"], ["message_end", "tool_calls"]]
|
|
21
|
+
* ],
|
|
22
|
+
* "expect": {
|
|
23
|
+
* "status": "complete" | "max_steps" | "no_final" | "aborted",
|
|
24
|
+
* "textContains": "Hello",
|
|
25
|
+
* "toolCallsAtLeast": 1,
|
|
26
|
+
* "toolCallsAtMost": 3
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* `scripted_events` is an array of *steps*; each step is an array of
|
|
31
|
+
* [eventKind, ...args] tuples. The i-th step of the scenario is fed to
|
|
32
|
+
* the i-th call to the adapter.stream() generator.
|
|
33
|
+
*
|
|
34
|
+
* If `scripted_events` is omitted or empty, the scenario just runs to
|
|
35
|
+
* max_steps with no model output (useful for testing the harness
|
|
36
|
+
* itself).
|
|
37
|
+
*
|
|
38
|
+
* Output: prints each scenario's pass/fail as a line; if --output json
|
|
39
|
+
* is set, emits one JSON object per line. Exit code 0 if all pass, 1
|
|
40
|
+
* otherwise.
|
|
41
|
+
*/
|
|
42
|
+
import * as fs from 'node:fs';
|
|
43
|
+
import * as readline from 'node:readline/promises';
|
|
44
|
+
import { stdin as input, stdout, stderr } from 'node:process';
|
|
45
|
+
import { run } from '../agent/runtime.js';
|
|
46
|
+
import { builtinRegistry } from '../tools/registry.js';
|
|
47
|
+
import { builtinRules, DEFAULT_POLICY_CONFIG, PolicyEngine } from '../policy/engine.js';
|
|
48
|
+
import { DenyAllApprovalPrompt } from '../policy/approval.js';
|
|
49
|
+
export async function runEval(opts) {
|
|
50
|
+
const scenarios = await readScenarios(opts.inputPath);
|
|
51
|
+
if (scenarios.length === 0) {
|
|
52
|
+
stderr.write('klyro eval: no scenarios in input\n');
|
|
53
|
+
return 2;
|
|
54
|
+
}
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const sc of scenarios) {
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
const r = await runScenario(sc);
|
|
59
|
+
r.durationMs = Date.now() - start;
|
|
60
|
+
results.push(r);
|
|
61
|
+
if (opts.output === 'json') {
|
|
62
|
+
stdout.write(JSON.stringify({ kind: 'eval_result', ...r }) + '\n');
|
|
63
|
+
}
|
|
64
|
+
else if (opts.output === 'human') {
|
|
65
|
+
const tag = r.passed ? 'PASS' : 'FAIL';
|
|
66
|
+
stdout.write(`[${tag}] ${r.name} (${r.durationMs}ms, ${r.steps} steps, ${r.toolCalls} tools)\n`);
|
|
67
|
+
for (const f of r.failures)
|
|
68
|
+
stdout.write(` - ${f}\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const passed = results.filter((r) => r.passed).length;
|
|
72
|
+
const failed = results.length - passed;
|
|
73
|
+
if (opts.output === 'json') {
|
|
74
|
+
stdout.write(JSON.stringify({ kind: 'eval_summary', total: results.length, passed, failed }) + '\n');
|
|
75
|
+
}
|
|
76
|
+
else if (opts.output === 'human') {
|
|
77
|
+
stdout.write(`\n${passed}/${results.length} passed\n`);
|
|
78
|
+
}
|
|
79
|
+
return failed === 0 ? 0 : 1;
|
|
80
|
+
}
|
|
81
|
+
async function readScenarios(path) {
|
|
82
|
+
const text = await readAll(path);
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const line of text.split('\n')) {
|
|
85
|
+
const trimmed = line.trim();
|
|
86
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
87
|
+
continue;
|
|
88
|
+
out.push(JSON.parse(trimmed));
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
async function readAll(path) {
|
|
93
|
+
if (path === '-') {
|
|
94
|
+
// Drain stdin.
|
|
95
|
+
const chunks = [];
|
|
96
|
+
const rl = readline.createInterface({ input, crlfDelay: Infinity });
|
|
97
|
+
for await (const line of rl)
|
|
98
|
+
chunks.push(Buffer.from(line + '\n', 'utf8'));
|
|
99
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
100
|
+
}
|
|
101
|
+
return fs.promises.readFile(path, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
export function scriptedAdapterFromSpec(spec) {
|
|
104
|
+
const steps = spec ?? [];
|
|
105
|
+
let i = 0;
|
|
106
|
+
return {
|
|
107
|
+
id: 'scripted',
|
|
108
|
+
async *stream() {
|
|
109
|
+
if (i < steps.length) {
|
|
110
|
+
const step = steps[i++];
|
|
111
|
+
if (!step)
|
|
112
|
+
return;
|
|
113
|
+
for (const tuple of step) {
|
|
114
|
+
yield tupleToEvent(tuple);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// No more scripted output — emit message_end so the loop terminates
|
|
119
|
+
// with status='no_final' if max_steps isn't reached, or max_steps otherwise.
|
|
120
|
+
yield { kind: 'message_start' };
|
|
121
|
+
yield { kind: 'message_end', finishReason: 'stop' };
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function tupleToEvent(tuple) {
|
|
127
|
+
const [kind, ...args] = tuple;
|
|
128
|
+
switch (kind) {
|
|
129
|
+
case 'message_start':
|
|
130
|
+
return { kind: 'message_start', id: args[0], model: args[1] };
|
|
131
|
+
case 'text_delta':
|
|
132
|
+
return { kind: 'text_delta', text: args[0] };
|
|
133
|
+
case 'message_end':
|
|
134
|
+
return { kind: 'message_end', finishReason: args[0], usage: args[1] };
|
|
135
|
+
case 'tool_call_start':
|
|
136
|
+
return { kind: 'tool_call_start', id: args[0], name: args[1] };
|
|
137
|
+
case 'tool_call_delta':
|
|
138
|
+
return { kind: 'tool_call_delta', id: args[0], argsJson: args[1] };
|
|
139
|
+
case 'tool_call_end':
|
|
140
|
+
return { kind: 'tool_call_end', id: args[0] };
|
|
141
|
+
case 'error':
|
|
142
|
+
return { kind: 'error', code: args[0], message: args[1], retryable: Boolean(args[2]) };
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`scriptedAdapterFromSpec: unknown event kind: ${kind}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export async function runScenario(sc) {
|
|
148
|
+
const failures = [];
|
|
149
|
+
const model = sc.model ?? 'mock';
|
|
150
|
+
const adapter = scriptedAdapterFromSpec(sc.scripted_events);
|
|
151
|
+
const registry = builtinRegistry();
|
|
152
|
+
const policy = new PolicyEngine(builtinRules(), DEFAULT_POLICY_CONFIG);
|
|
153
|
+
const result = await run({
|
|
154
|
+
task: sc.task,
|
|
155
|
+
cwd: process.cwd(),
|
|
156
|
+
model,
|
|
157
|
+
maxSteps: sc.maxSteps,
|
|
158
|
+
maxTokens: sc.maxTokens,
|
|
159
|
+
nonInteractive: true,
|
|
160
|
+
}, {
|
|
161
|
+
adapter,
|
|
162
|
+
registry,
|
|
163
|
+
policy,
|
|
164
|
+
approval: new DenyAllApprovalPrompt(),
|
|
165
|
+
systemPrompt: ({ cwd }) => `You are Klyro. cwd=${cwd}.`,
|
|
166
|
+
});
|
|
167
|
+
const exp = sc.expect ?? {};
|
|
168
|
+
if (exp.status !== undefined && result.status !== exp.status) {
|
|
169
|
+
failures.push(`status: expected ${exp.status}, got ${result.status}`);
|
|
170
|
+
}
|
|
171
|
+
if (exp.textContains !== undefined && !result.finalText.includes(exp.textContains)) {
|
|
172
|
+
failures.push(`text: expected to contain "${exp.textContains}", got "${result.finalText}"`);
|
|
173
|
+
}
|
|
174
|
+
if (exp.toolCallsAtLeast !== undefined && result.toolCalls < exp.toolCallsAtLeast) {
|
|
175
|
+
failures.push(`toolCalls: expected >= ${exp.toolCallsAtLeast}, got ${result.toolCalls}`);
|
|
176
|
+
}
|
|
177
|
+
if (exp.toolCallsAtMost !== undefined && result.toolCalls > exp.toolCallsAtMost) {
|
|
178
|
+
failures.push(`toolCalls: expected <= ${exp.toolCallsAtMost}, got ${result.toolCalls}`);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name: sc.name,
|
|
182
|
+
passed: failures.length === 0,
|
|
183
|
+
failures,
|
|
184
|
+
status: result.status,
|
|
185
|
+
steps: result.steps,
|
|
186
|
+
toolCalls: result.toolCalls,
|
|
187
|
+
text: result.finalText,
|
|
188
|
+
durationMs: 0,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `klyro` (no args) — the TUI REPL.
|
|
3
|
+
*
|
|
4
|
+
* Loads config from env, builds the agent runtime deps, mounts the
|
|
5
|
+
* Ink app, and bridges RuntimeEvents to the app's transcript/status
|
|
6
|
+
* via the global hooks installed by App.useEffect.
|
|
7
|
+
*/
|
|
8
|
+
export interface ReplOptions {
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
maxSteps?: number;
|
|
12
|
+
model?: string;
|
|
13
|
+
nonInteractive?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function startRepl(opts?: ReplOptions): Promise<number>;
|