pi-harness-delegate 0.1.0 → 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/extensions/activity.ts +225 -201
- package/extensions/command.ts +61 -61
- package/extensions/config.ts +137 -131
- package/extensions/harnesses/amp.ts +122 -115
- package/extensions/harnesses/claude.ts +136 -111
- package/extensions/harnesses/codex.ts +191 -147
- package/extensions/harnesses/opencode.ts +133 -116
- package/extensions/harnesses/registry.ts +22 -22
- package/extensions/harnesses/types.ts +60 -60
- package/extensions/hint.ts +22 -19
- package/extensions/index.ts +1011 -741
- package/extensions/progress.ts +104 -104
- package/extensions/run-claude.ts +44 -35
- package/extensions/runner.ts +111 -99
- package/extensions/stream-parse.ts +32 -24
- package/extensions/templates.ts +134 -117
- package/extensions/usage.ts +24 -24
- package/package.json +69 -56
package/extensions/config.ts
CHANGED
|
@@ -4,155 +4,161 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { DEFAULT_TIMEOUT_MS } from './harnesses/types.ts';
|
|
5
5
|
|
|
6
6
|
export interface HarnessConfig {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
model?: string;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
allowDangerous?: boolean;
|
|
10
|
+
maxBudgetUsd?: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface DelegateConfig {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
model?: string;
|
|
15
|
+
timeoutMs: number;
|
|
16
|
+
defaultMode: string;
|
|
17
|
+
defaultHarness: string;
|
|
18
|
+
allowDangerous: boolean;
|
|
19
|
+
inspectThinking: boolean;
|
|
20
|
+
maxBudgetUsd?: number;
|
|
21
|
+
autoDelegateHints: boolean;
|
|
22
|
+
modelAliases: Record<string, string>;
|
|
23
|
+
maxConcurrent: number | { global?: number; perHarness?: Record<string, number> };
|
|
24
|
+
maxTranscripts: number;
|
|
25
|
+
harnesses: Record<string, HarnessConfig>;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function agentDir(): string {
|
|
29
|
-
|
|
29
|
+
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function outputsDir(harness?: string): string {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const base = join(agentDir(), 'delegate', 'outputs');
|
|
34
|
+
return harness ? join(base, harness) : base;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function legacyOutputsDir(): string {
|
|
38
|
-
|
|
38
|
+
return join(agentDir(), 'claude-delegate', 'outputs');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function loadConfig(): DelegateConfig {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
42
|
+
const cfg: DelegateConfig = {
|
|
43
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
44
|
+
defaultMode: 'general',
|
|
45
|
+
defaultHarness: 'claude',
|
|
46
|
+
allowDangerous: false,
|
|
47
|
+
inspectThinking: false,
|
|
48
|
+
autoDelegateHints: false,
|
|
49
|
+
modelAliases: { economy: 'haiku', balanced: 'sonnet', max: 'opus' },
|
|
50
|
+
maxConcurrent: 1,
|
|
51
|
+
maxTranscripts: 100,
|
|
52
|
+
harnesses: {},
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const file = join(agentDir(), 'settings.json');
|
|
56
|
+
if (!existsSync(file)) return cfg;
|
|
57
|
+
const settings = JSON.parse(readFileSync(file, 'utf8')) as {
|
|
58
|
+
delegate?: Partial<DelegateConfig & { harnesses: Record<string, HarnessConfig> }>;
|
|
59
|
+
claudeDelegate?: Partial<DelegateConfig & { model?: string }>;
|
|
60
|
+
};
|
|
61
|
+
// Legacy claudeDelegate -> delegate.harnesses.claude migration
|
|
62
|
+
if (settings.claudeDelegate && !settings.delegate) {
|
|
63
|
+
const c = settings.claudeDelegate as Partial<DelegateConfig>;
|
|
64
|
+
if (typeof c.model === 'string') cfg.harnesses['claude'] = { ...(cfg.harnesses['claude'] ?? {}), model: c.model };
|
|
65
|
+
if (typeof c.timeoutMs === 'number' && c.timeoutMs > 0) cfg.timeoutMs = c.timeoutMs;
|
|
66
|
+
if (typeof c.defaultMode === 'string') cfg.defaultMode = c.defaultMode;
|
|
67
|
+
if (typeof c.allowDangerous === 'boolean') cfg.allowDangerous = c.allowDangerous;
|
|
68
|
+
if (typeof c.inspectThinking === 'boolean') cfg.inspectThinking = c.inspectThinking;
|
|
69
|
+
if (typeof (c as DelegateConfig).maxBudgetUsd === 'number' && (c as DelegateConfig).maxBudgetUsd! > 0)
|
|
70
|
+
cfg.maxBudgetUsd = (c as DelegateConfig).maxBudgetUsd;
|
|
71
|
+
if (typeof c.autoDelegateHints === 'boolean') cfg.autoDelegateHints = c.autoDelegateHints;
|
|
72
|
+
if ((c as DelegateConfig).modelAliases && typeof (c as DelegateConfig).modelAliases === 'object') {
|
|
73
|
+
for (const [k, v] of Object.entries((c as DelegateConfig).modelAliases!)) {
|
|
74
|
+
if (typeof v === 'string' && v) cfg.modelAliases[k] = v;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (typeof c.maxConcurrent === 'number' && c.maxConcurrent >= 0) cfg.maxConcurrent = c.maxConcurrent;
|
|
78
|
+
else if (c.maxConcurrent && typeof c.maxConcurrent === 'object') {
|
|
79
|
+
const obj = c.maxConcurrent as { global?: unknown; perHarness?: unknown };
|
|
80
|
+
const out: { global?: number; perHarness?: Record<string, number> } = {};
|
|
81
|
+
if (typeof obj.global === 'number' && obj.global >= 0) out.global = obj.global;
|
|
82
|
+
if (obj.perHarness && typeof obj.perHarness === 'object') {
|
|
83
|
+
const ph: Record<string, number> = {};
|
|
84
|
+
for (const [k, v] of Object.entries(obj.perHarness as Record<string, unknown>)) {
|
|
85
|
+
if (typeof v === 'number' && v >= 0) ph[k] = v;
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(ph).length > 0) out.perHarness = ph;
|
|
88
|
+
}
|
|
89
|
+
if (out.global !== undefined || out.perHarness) cfg.maxConcurrent = out;
|
|
90
|
+
}
|
|
91
|
+
if (typeof c.maxTranscripts === 'number' && c.maxTranscripts >= 0) cfg.maxTranscripts = c.maxTranscripts;
|
|
92
|
+
if (c.harnesses && typeof c.harnesses === 'object') {
|
|
93
|
+
for (const [k, v] of Object.entries(c.harnesses)) {
|
|
94
|
+
if (v && typeof v === 'object') cfg.harnesses[k] = { ...(cfg.harnesses[k] ?? {}), ...(v as HarnessConfig) };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// also map harnesses.claude if any
|
|
98
|
+
return cfg;
|
|
99
|
+
}
|
|
100
|
+
const d = settings.delegate ?? {};
|
|
101
|
+
if (typeof d.defaultHarness === 'string' && d.defaultHarness) cfg.defaultHarness = d.defaultHarness;
|
|
102
|
+
if (typeof d.defaultMode === 'string') cfg.defaultMode = d.defaultMode;
|
|
103
|
+
if (typeof d.model === 'string') cfg.model = d.model;
|
|
104
|
+
if (typeof d.timeoutMs === 'number' && d.timeoutMs > 0) cfg.timeoutMs = d.timeoutMs;
|
|
105
|
+
if (typeof d.allowDangerous === 'boolean') cfg.allowDangerous = d.allowDangerous;
|
|
106
|
+
if (typeof d.inspectThinking === 'boolean') cfg.inspectThinking = d.inspectThinking;
|
|
107
|
+
if (typeof d.maxBudgetUsd === 'number' && d.maxBudgetUsd > 0) cfg.maxBudgetUsd = d.maxBudgetUsd;
|
|
108
|
+
if (typeof d.autoDelegateHints === 'boolean') cfg.autoDelegateHints = d.autoDelegateHints;
|
|
109
|
+
if (d.modelAliases && typeof d.modelAliases === 'object') {
|
|
110
|
+
for (const [k, v] of Object.entries(d.modelAliases)) {
|
|
111
|
+
if (typeof v === 'string' && v) cfg.modelAliases[k] = v;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (typeof d.maxConcurrent === 'number' && d.maxConcurrent >= 0) cfg.maxConcurrent = d.maxConcurrent;
|
|
115
|
+
else if (d.maxConcurrent && typeof d.maxConcurrent === 'object') {
|
|
116
|
+
const obj = d.maxConcurrent as { global?: unknown; perHarness?: unknown };
|
|
117
|
+
const out: { global?: number; perHarness?: Record<string, number> } = {};
|
|
118
|
+
if (typeof obj.global === 'number' && obj.global >= 0) out.global = obj.global;
|
|
119
|
+
if (obj.perHarness && typeof obj.perHarness === 'object') {
|
|
120
|
+
const ph: Record<string, number> = {};
|
|
121
|
+
for (const [k, v] of Object.entries(obj.perHarness as Record<string, unknown>)) {
|
|
122
|
+
if (typeof v === 'number' && v >= 0) ph[k] = v;
|
|
123
|
+
}
|
|
124
|
+
if (Object.keys(ph).length > 0) out.perHarness = ph;
|
|
125
|
+
}
|
|
126
|
+
if (out.global !== undefined || out.perHarness) cfg.maxConcurrent = out;
|
|
127
|
+
}
|
|
128
|
+
if (typeof d.maxTranscripts === 'number' && d.maxTranscripts >= 0) cfg.maxTranscripts = d.maxTranscripts;
|
|
129
|
+
if (d.harnesses && typeof d.harnesses === 'object') {
|
|
130
|
+
for (const [k, v] of Object.entries(d.harnesses)) {
|
|
131
|
+
if (v && typeof v === 'object') cfg.harnesses[k] = { ...(v as HarnessConfig) };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// also support legacy claudeDelegate merged when delegate also present (delegate wins)
|
|
135
|
+
if (settings.claudeDelegate) {
|
|
136
|
+
const c = settings.claudeDelegate as Partial<DelegateConfig>;
|
|
137
|
+
if (typeof c.model === 'string' && !cfg.harnesses['claude']?.model) {
|
|
138
|
+
cfg.harnesses['claude'] = { ...(cfg.harnesses['claude'] ?? {}), model: c.model };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// invalid settings — fall back to defaults
|
|
143
|
+
}
|
|
144
|
+
return cfg;
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
export function resolveModelForHarness(
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
export function resolveModelForHarness(
|
|
148
|
+
cfg: DelegateConfig,
|
|
149
|
+
harness: string,
|
|
150
|
+
model?: string,
|
|
151
|
+
templateModel?: string,
|
|
152
|
+
): string | undefined {
|
|
153
|
+
const resolve = (m?: string) => (m ? (cfg.modelAliases[m] ?? m) : undefined);
|
|
154
|
+
return resolve(model) ?? resolve(templateModel) ?? resolve(cfg.harnesses[harness]?.model) ?? resolve(cfg.model);
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
export function getMaxConcurrent(cfg: DelegateConfig, harness?: string): number {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
if (typeof cfg.maxConcurrent === 'number') return cfg.maxConcurrent;
|
|
159
|
+
// if object shape {global, perHarness}
|
|
160
|
+
const mc = cfg.maxConcurrent as unknown as { global?: number; perHarness?: Record<string, number> };
|
|
161
|
+
if (harness && mc.perHarness && typeof mc.perHarness[harness] === 'number') return mc.perHarness[harness]!;
|
|
162
|
+
if (typeof mc.global === 'number') return mc.global;
|
|
163
|
+
return 1;
|
|
158
164
|
}
|
|
@@ -1,137 +1,144 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
BuildArgsOpts,
|
|
5
|
+
Harness,
|
|
6
|
+
NormalizedPermission,
|
|
7
|
+
ParseOutcome,
|
|
8
|
+
ParseState,
|
|
9
|
+
StreamedResult,
|
|
10
|
+
} from './types.ts';
|
|
4
11
|
|
|
5
12
|
const execFileAsync = promisify(execFile);
|
|
6
13
|
|
|
7
14
|
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
8
|
-
|
|
15
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
const PERMISSION_MAP: Record<NormalizedPermission, string> = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
readonly: 'read-only',
|
|
20
|
+
edit: 'workspace',
|
|
21
|
+
danger: 'danger',
|
|
15
22
|
};
|
|
16
23
|
|
|
17
24
|
function extractAmpText(o: Record<string, unknown>): string | undefined {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
if (typeof o.text === 'string') return o.text;
|
|
26
|
+
if (typeof o.output === 'string') return o.output;
|
|
27
|
+
if (typeof o.delta === 'string') return o.delta;
|
|
28
|
+
if (typeof o.content === 'string') return o.content;
|
|
29
|
+
return undefined;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
export function parseAmpLine(line: string, state: ParseState): ParseOutcome {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
let o: unknown;
|
|
34
|
+
try {
|
|
35
|
+
o = JSON.parse(line);
|
|
36
|
+
} catch {
|
|
37
|
+
if (line.trim().length > 0) return { streamedText: line + '\n', activities: [] };
|
|
38
|
+
return { activities: [] };
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(o)) return { activities: [] };
|
|
41
|
+
const activities: ParseOutcome['activities'] = [];
|
|
42
|
+
let streamedText: string | undefined;
|
|
43
|
+
const typeStr = typeof o.type === 'string' ? o.type : '';
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
if (typeStr.includes('tool') || o.type === 'tool_use') {
|
|
46
|
+
const name = typeof o.name === 'string' ? o.name : 'tool';
|
|
47
|
+
if (typeStr.includes('start') || o.type === 'tool_use') {
|
|
48
|
+
activities.push({ kind: 'tool_start', name });
|
|
49
|
+
if (isRecord(o.input)) activities.push({ kind: 'tool_input', name, input: o.input as Record<string, unknown> });
|
|
50
|
+
} else {
|
|
51
|
+
activities.push({ kind: 'tool_result', isError: o.is_error === true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeStr.includes('thinking')) activities.push({ kind: 'thinking', chars: 10 });
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
const text = extractAmpText(o);
|
|
57
|
+
if (text && !typeStr.includes('tool')) streamedText = text;
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
if (o.type === 'result' || o.type === 'done' || o.type === 'completed') {
|
|
60
|
+
const usage = isRecord(o.usage) ? o.usage : null;
|
|
61
|
+
const result: StreamedResult = {
|
|
62
|
+
result: typeof o.result === 'string' ? o.result : state.streamedText + (streamedText ?? ''),
|
|
63
|
+
isError: o.is_error === true,
|
|
64
|
+
numTurns: typeof o.num_turns === 'number' ? o.num_turns : 0,
|
|
65
|
+
totalCostUsd: typeof o.total_cost_usd === 'number' ? o.total_cost_usd : 0,
|
|
66
|
+
sessionId: typeof o.session_id === 'string' ? o.session_id : null,
|
|
67
|
+
stopReason: typeof o.stop_reason === 'string' ? o.stop_reason : null,
|
|
68
|
+
permissionDenials: [],
|
|
69
|
+
durationMs: typeof o.duration_ms === 'number' ? o.duration_ms : null,
|
|
70
|
+
durationApiMs: null,
|
|
71
|
+
ttftMs: null,
|
|
72
|
+
model: typeof o.model === 'string' ? o.model : null,
|
|
73
|
+
contextWindow: null,
|
|
74
|
+
maxOutputTokens: null,
|
|
75
|
+
usage: usage
|
|
76
|
+
? {
|
|
77
|
+
inputTokens: typeof usage.input_tokens === 'number' ? usage.input_tokens : 0,
|
|
78
|
+
outputTokens: typeof usage.output_tokens === 'number' ? usage.output_tokens : 0,
|
|
79
|
+
cacheCreationInputTokens: 0,
|
|
80
|
+
cacheReadInputTokens: 0,
|
|
81
|
+
}
|
|
82
|
+
: null,
|
|
83
|
+
};
|
|
84
|
+
return { activities, streamedText, result };
|
|
85
|
+
}
|
|
86
|
+
return { activities, streamedText };
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
export const ampHarness: Harness = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
90
|
+
name: 'amp',
|
|
91
|
+
displayName: 'Amp',
|
|
92
|
+
binary: 'amp',
|
|
93
|
+
async detect() {
|
|
94
|
+
try {
|
|
95
|
+
const { stdout } = await execFileAsync('amp', ['--version'], { timeout: 5000 });
|
|
96
|
+
return { ok: true, version: stdout.trim() };
|
|
97
|
+
} catch {
|
|
98
|
+
try {
|
|
99
|
+
const { stdout } = await execFileAsync('omp', ['--version'], { timeout: 5000 });
|
|
100
|
+
return { ok: true, version: stdout.trim() };
|
|
101
|
+
} catch {
|
|
102
|
+
return { ok: false, hint: 'Install Amp: https://ampcode.com' };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
buildArgs(opts: BuildArgsOpts): string[] {
|
|
107
|
+
const perm = opts.nativePermission ?? PERMISSION_MAP[opts.permission] ?? 'workspace';
|
|
108
|
+
const args = ['--output', 'jsonl', opts.prompt, '--permission', perm];
|
|
109
|
+
if (opts.model) args.push('--model', opts.model);
|
|
110
|
+
if (opts.resumeSessionId) args.push('--resume', opts.resumeSessionId);
|
|
111
|
+
for (const dir of opts.addDirs ?? []) args.push('--add-dir', dir);
|
|
112
|
+
return args;
|
|
113
|
+
},
|
|
114
|
+
parseLine(line: string, state: ParseState): ParseOutcome {
|
|
115
|
+
return parseAmpLine(line, state);
|
|
116
|
+
},
|
|
117
|
+
extractResult(state: ParseState): StreamedResult | null {
|
|
118
|
+
if (state.result) return state.result;
|
|
119
|
+
if (state.streamedText.trim().length > 0) {
|
|
120
|
+
return {
|
|
121
|
+
result: state.streamedText,
|
|
122
|
+
isError: false,
|
|
123
|
+
numTurns: 1,
|
|
124
|
+
totalCostUsd: 0,
|
|
125
|
+
sessionId: null,
|
|
126
|
+
stopReason: null,
|
|
127
|
+
permissionDenials: [],
|
|
128
|
+
durationMs: null,
|
|
129
|
+
durationApiMs: null,
|
|
130
|
+
ttftMs: null,
|
|
131
|
+
model: null,
|
|
132
|
+
contextWindow: null,
|
|
133
|
+
maxOutputTokens: null,
|
|
134
|
+
usage: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
permissionMap: {
|
|
140
|
+
readonly: ['read-only'],
|
|
141
|
+
edit: ['workspace'],
|
|
142
|
+
danger: ['danger'],
|
|
143
|
+
},
|
|
137
144
|
};
|