nuabase 2.3.11 → 3.0.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/CHANGELOG.md +60 -0
- package/README.md +233 -200
- package/dist/chunks/local-agent-Bj-QKikS.d.cts +270 -0
- package/dist/chunks/local-agent-Bj-QKikS.d.mts +270 -0
- package/dist/cjs/chunks/strict-json-schema-bKwaMWm1.cjs +145 -0
- package/dist/cjs/index.cjs +989 -11504
- package/dist/cjs/local-agent.cjs +819 -877
- package/dist/esm/chunks/strict-json-schema-Du2LLrVP.mjs +138 -0
- package/dist/esm/index.mjs +987 -11502
- package/dist/esm/local-agent.mjs +792 -878
- package/dist/index.d.cts +94 -0
- package/dist/index.d.mts +94 -0
- package/dist/local-agent.d.cts +150 -0
- package/dist/local-agent.d.mts +150 -0
- package/package.json +12 -17
- package/dist/cjs/chunks/strict-json-schema-DUE7RTxG.cjs +0 -177
- package/dist/esm/chunks/strict-json-schema-DI9qiH5h.mjs +0 -174
- package/dist/types/__tests__/helpers/client-factory.d.ts +0 -39
- package/dist/types/__tests__/helpers/index.d.ts +0 -2
- package/dist/types/backend/direct.d.ts +0 -29
- package/dist/types/backend/gateway.d.ts +0 -24
- package/dist/types/backend/types.d.ts +0 -68
- package/dist/types/index.d.cts +0 -7
- package/dist/types/index.d.mts +0 -7
- package/dist/types/lib/auth.d.ts +0 -14
- package/dist/types/lib/env.d.ts +0 -1
- package/dist/types/lib/error-response.d.ts +0 -6
- package/dist/types/lib/error-utils.d.ts +0 -6
- package/dist/types/local-agent.d.cts +0 -2
- package/dist/types/local-agent.d.mts +0 -2
- package/dist/types/nua-llm-core/index.d.ts +0 -23
- package/dist/types/nua-llm-core/lib/error-utils.d.ts +0 -6
- package/dist/types/nua-llm-core/lib/logger.d.ts +0 -44
- package/dist/types/nua-llm-core/lib/nua-errors.d.ts +0 -12
- package/dist/types/nua-llm-core/lib/prompt-template-utils.d.ts +0 -2
- package/dist/types/nua-llm-core/lib/schema-utils.d.ts +0 -1
- package/dist/types/nua-llm-core/lib/stable-stringify.d.ts +0 -1
- package/dist/types/nua-llm-core/lib/strict-json-schema.d.ts +0 -7
- package/dist/types/nua-llm-core/lib/types.d.ts +0 -8
- package/dist/types/nua-llm-core/lib/validate-mappable-input-data.d.ts +0 -4
- package/dist/types/nua-llm-core/local-agent.d.ts +0 -7
- package/dist/types/nua-llm-core/modules/agent/agent-loop.d.ts +0 -10
- package/dist/types/nua-llm-core/modules/agent/agent-turn.d.ts +0 -17
- package/dist/types/nua-llm-core/modules/agent/provider-formatters.d.ts +0 -22
- package/dist/types/nua-llm-core/modules/agent/run-agent.d.ts +0 -7
- package/dist/types/nua-llm-core/modules/agent/types.d.ts +0 -90
- package/dist/types/nua-llm-core/modules/cast/cast-request.d.ts +0 -32
- package/dist/types/nua-llm-core/modules/cast/envelope.d.ts +0 -9
- package/dist/types/nua-llm-core/modules/cast/list-schema.d.ts +0 -32
- package/dist/types/nua-llm-core/modules/cast/prompts/list-prompt.d.ts +0 -3
- package/dist/types/nua-llm-core/modules/cast/prompts/render-prompt.d.ts +0 -7
- package/dist/types/nua-llm-core/modules/cast/prompts/value-prompt.d.ts +0 -3
- package/dist/types/nua-llm-core/modules/cast/run-cast.d.ts +0 -44
- package/dist/types/nua-llm-core/modules/engine/engine-router.d.ts +0 -23
- package/dist/types/nua-llm-core/modules/engine/http/http-engine.d.ts +0 -13
- package/dist/types/nua-llm-core/modules/engine/http/provider-call.d.ts +0 -33
- package/dist/types/nua-llm-core/modules/engine/http/provider-config.d.ts +0 -70
- package/dist/types/nua-llm-core/modules/engine/http/provider-engines.d.ts +0 -35
- package/dist/types/nua-llm-core/modules/engine/llm-engine.d.ts +0 -73
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/claude-code.d.ts +0 -2
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/codex.d.ts +0 -2
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/failure-classification.d.ts +0 -4
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/index.d.ts +0 -3
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/pi.d.ts +0 -2
- package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/types.d.ts +0 -61
- package/dist/types/nua-llm-core/modules/engine/local-agent/agent-id.d.ts +0 -4
- package/dist/types/nua-llm-core/modules/engine/local-agent/create-local-agent.d.ts +0 -36
- package/dist/types/nua-llm-core/modules/engine/local-agent/detect.d.ts +0 -20
- package/dist/types/nua-llm-core/modules/engine/local-agent/local-agent-engine.d.ts +0 -32
- package/dist/types/nua-llm-core/modules/engine/local-agent/local-agent.d.ts +0 -25
- package/dist/types/nua-llm-core/modules/engine/local-agent/process-runner.d.ts +0 -16
- package/dist/types/nua-llm-core/modules/engine/local-agent/semaphore.d.ts +0 -8
- package/dist/types/nua-llm-core/modules/execution/llm-response-extraction.d.ts +0 -5
- package/dist/types/nua-llm-core/modules/json-schema-validation/ajv-instances.d.ts +0 -12
- package/dist/types/nua-llm-core/modules/json-schema-validation/nua-json-schema-value-validation.d.ts +0 -10
- package/dist/types/nua-llm-core/modules/json-schema-validation/validate-json-schema.d.ts +0 -2
- package/dist/types/nua-llm-core/modules/model-info/index.d.ts +0 -15
- package/dist/types/nua-llm-core/modules/streaming/sse-parser.d.ts +0 -7
- package/dist/types/nua-llm-core/nua-llm-client.d.ts +0 -37
- package/dist/types/nua.d.ts +0 -42
package/dist/cjs/local-agent.cjs
CHANGED
|
@@ -1,957 +1,899 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var strictJsonSchema = require('./chunks/strict-json-schema-
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
var strictJsonSchema = require('./chunks/strict-json-schema-bKwaMWm1.cjs');
|
|
4
|
+
var node_child_process = require('node:child_process');
|
|
5
|
+
var fs = require('node:fs');
|
|
6
|
+
var os = require('node:os');
|
|
7
|
+
var path = require('node:path');
|
|
8
|
+
var node_crypto = require('node:crypto');
|
|
9
|
+
var node_util = require('node:util');
|
|
10
|
+
|
|
11
|
+
function _interopNamespaceDefault(e) {
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n.default = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
16
27
|
|
|
17
|
-
var
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
function stderrTail(result, maxChars = 2e3) {
|
|
49
|
-
const stderr = result.stderr.trim();
|
|
50
|
-
return stderr.length > maxChars ? stderr.slice(-maxChars) : stderr;
|
|
51
|
-
}
|
|
52
|
-
return failureClassification;
|
|
28
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
29
|
+
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
30
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
31
|
+
|
|
32
|
+
const FATAL_PATTERNS = [
|
|
33
|
+
/not logged in/i,
|
|
34
|
+
/logged out/i,
|
|
35
|
+
/(please|need to|must) (run .*)?log ?in/i,
|
|
36
|
+
/authenticat/i,
|
|
37
|
+
/unauthori[sz]ed/i,
|
|
38
|
+
/invalid api key/i,
|
|
39
|
+
/oauth/i,
|
|
40
|
+
/usage limit/i,
|
|
41
|
+
/rate.?limit/i,
|
|
42
|
+
/quota/i,
|
|
43
|
+
/credit balance/i,
|
|
44
|
+
/\b40[13]\b/,
|
|
45
|
+
/\b429\b/,
|
|
46
|
+
/not supported when using/i,
|
|
47
|
+
/model .* (does not exist|not found|not available)/i
|
|
48
|
+
];
|
|
49
|
+
function classifyAgentFailure(message) {
|
|
50
|
+
if (FATAL_PATTERNS.some((pattern) => pattern.test(message))) {
|
|
51
|
+
return { kind: "fatal", message };
|
|
52
|
+
}
|
|
53
|
+
return { kind: "transient", message };
|
|
54
|
+
}
|
|
55
|
+
function stderrTail(result, maxChars = 2e3) {
|
|
56
|
+
const stderr = result.stderr.trim();
|
|
57
|
+
return stderr.length > maxChars ? stderr.slice(-maxChars) : stderr;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
function
|
|
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
|
-
kind: "failed",
|
|
105
|
-
failure: (0, failure_classification_1.classifyAgentFailure)("claude", `exited with code ${result.exitCode} without a JSON result: ${(0, failure_classification_1.stderrTail)(result)}`)
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
if (json.is_error) {
|
|
109
|
-
const message = json.result || `error (${json.subtype ?? "unknown"})`;
|
|
110
|
-
if (json.api_error_status === 400 && /input_schema|json.?schema/i.test(message)) {
|
|
111
|
-
return {
|
|
112
|
-
kind: "failed",
|
|
113
|
-
failure: { kind: "schema-rejected", message: `claude rejected the output schema: ${message}` }
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (json.api_error_status === 401 || json.api_error_status === 403 || json.api_error_status === 429) {
|
|
117
|
-
return { kind: "failed", failure: { kind: "fatal", message: `claude: ${message}` } };
|
|
118
|
-
}
|
|
119
|
-
return { kind: "failed", failure: (0, failure_classification_1.classifyAgentFailure)("claude", message) };
|
|
120
|
-
}
|
|
121
|
-
if (json.subtype !== "success") {
|
|
122
|
-
return {
|
|
123
|
-
kind: "failed",
|
|
124
|
-
failure: {
|
|
125
|
-
kind: "transient",
|
|
126
|
-
message: `claude did not finish successfully (${json.subtype ?? "no subtype"})`
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const usage = json.usage ?? {};
|
|
131
|
-
const promptTokens = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
|
|
132
|
-
const completionTokens = usage.output_tokens ?? 0;
|
|
133
|
-
const reported = {
|
|
134
|
-
usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens },
|
|
135
|
-
model: Object.keys(json.modelUsage ?? {})[0],
|
|
136
|
-
costUsdEstimate: json.total_cost_usd
|
|
60
|
+
const SYSTEM_PROMPT$1 = [
|
|
61
|
+
"You are a data transformation function called by a program.",
|
|
62
|
+
"You have no tools. Follow the instructions in the user message exactly",
|
|
63
|
+
"and reply only with the requested JSON."
|
|
64
|
+
].join(" ");
|
|
65
|
+
const claudeCodeAdapter = {
|
|
66
|
+
id: "claude-code",
|
|
67
|
+
binaryName: "claude",
|
|
68
|
+
apiKeyEnvVars: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
|
|
69
|
+
modelAliases: { haiku: "haiku", sonnet: "sonnet", opus: "opus" },
|
|
70
|
+
// Claude Code accepts any JSON Schema with an object root.
|
|
71
|
+
nativeSchema(envelopeSchema) {
|
|
72
|
+
return envelopeSchema;
|
|
73
|
+
},
|
|
74
|
+
buildInvocation(request, { model, effort }) {
|
|
75
|
+
const args = [
|
|
76
|
+
"-p",
|
|
77
|
+
"--output-format",
|
|
78
|
+
"json",
|
|
79
|
+
"--tools",
|
|
80
|
+
"",
|
|
81
|
+
"--setting-sources",
|
|
82
|
+
"",
|
|
83
|
+
"--strict-mcp-config",
|
|
84
|
+
"--no-session-persistence",
|
|
85
|
+
"--system-prompt",
|
|
86
|
+
SYSTEM_PROMPT$1
|
|
87
|
+
];
|
|
88
|
+
if (model !== void 0) {
|
|
89
|
+
args.push("--model", model);
|
|
90
|
+
}
|
|
91
|
+
if (effort !== void 0) {
|
|
92
|
+
args.push("--effort", effort);
|
|
93
|
+
}
|
|
94
|
+
if (request.enforcement.kind === "native") {
|
|
95
|
+
args.push("--json-schema", JSON.stringify(request.enforcement.schema));
|
|
96
|
+
}
|
|
97
|
+
return { args, stdin: request.prompt };
|
|
98
|
+
},
|
|
99
|
+
readResult(result, request) {
|
|
100
|
+
let json;
|
|
101
|
+
try {
|
|
102
|
+
json = JSON.parse(result.stdout);
|
|
103
|
+
} catch {
|
|
104
|
+
return {
|
|
105
|
+
kind: "failed",
|
|
106
|
+
failure: classifyAgentFailure(
|
|
107
|
+
`exited with code ${result.exitCode} without a JSON result: ${stderrTail(result)}`
|
|
108
|
+
)
|
|
137
109
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (
|
|
110
|
+
}
|
|
111
|
+
if (json.is_error) {
|
|
112
|
+
const message = json.result || `error (${json.subtype ?? "unknown"})`;
|
|
113
|
+
if (json.api_error_status === 400 && /input_schema|json.?schema/i.test(message)) {
|
|
142
114
|
return {
|
|
143
115
|
kind: "failed",
|
|
144
|
-
failure: {
|
|
145
|
-
kind: "transient",
|
|
146
|
-
message: `claude returned no structured output. Reply: ${json.result ?? ""}`
|
|
147
|
-
}
|
|
116
|
+
failure: { kind: "schema-rejected", message: `rejected the output schema: ${message}` }
|
|
148
117
|
};
|
|
149
118
|
}
|
|
119
|
+
if (json.api_error_status === 401 || json.api_error_status === 403 || json.api_error_status === 429) {
|
|
120
|
+
return { kind: "failed", failure: { kind: "fatal", message } };
|
|
121
|
+
}
|
|
122
|
+
return { kind: "failed", failure: classifyAgentFailure(message) };
|
|
123
|
+
}
|
|
124
|
+
if (json.subtype !== "success") {
|
|
150
125
|
return {
|
|
151
|
-
kind: "
|
|
152
|
-
|
|
153
|
-
|
|
126
|
+
kind: "failed",
|
|
127
|
+
failure: {
|
|
128
|
+
kind: "transient",
|
|
129
|
+
message: `did not finish successfully (${json.subtype ?? "no subtype"})`
|
|
130
|
+
}
|
|
154
131
|
};
|
|
155
|
-
},
|
|
156
|
-
loginStatusArgs: ["auth", "status"],
|
|
157
|
-
readLoginStatus(result) {
|
|
158
|
-
try {
|
|
159
|
-
const status = JSON.parse(result.stdout);
|
|
160
|
-
return { loggedIn: status.loggedIn === true, detail: status.authMethod };
|
|
161
|
-
} catch {
|
|
162
|
-
return { loggedIn: false, detail: (0, failure_classification_1.stderrTail)(result) || result.stdout.trim() };
|
|
163
|
-
}
|
|
164
132
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
},
|
|
191
|
-
buildInvocation(request, model) {
|
|
192
|
-
const args = [
|
|
193
|
-
"exec",
|
|
194
|
-
"-",
|
|
195
|
-
"--skip-git-repo-check",
|
|
196
|
-
"--ephemeral",
|
|
197
|
-
"--ignore-user-config",
|
|
198
|
-
"--ignore-rules",
|
|
199
|
-
"--sandbox",
|
|
200
|
-
"read-only",
|
|
201
|
-
"-c",
|
|
202
|
-
"model_reasoning_effort=low",
|
|
203
|
-
"--json",
|
|
204
|
-
"-o",
|
|
205
|
-
LAST_MESSAGE_FILE
|
|
206
|
-
];
|
|
207
|
-
if (model !== void 0) {
|
|
208
|
-
args.push("-m", model);
|
|
209
|
-
}
|
|
210
|
-
const files = [];
|
|
211
|
-
if (request.enforcement.kind === "native") {
|
|
212
|
-
args.push("--output-schema", SCHEMA_FILE);
|
|
213
|
-
files.push({ name: SCHEMA_FILE, content: JSON.stringify(request.enforcement.schema) });
|
|
214
|
-
}
|
|
215
|
-
return { args, stdin: request.prompt, files, outputFiles: [LAST_MESSAGE_FILE] };
|
|
216
|
-
},
|
|
217
|
-
readResult(result, request) {
|
|
218
|
-
const events = parseEvents(result.stdout);
|
|
219
|
-
const failed = events.find((e) => e.type === "turn.failed" || e.type === "error");
|
|
220
|
-
if (failed) {
|
|
221
|
-
const message = failed.error?.message ?? failed.message ?? "unknown error";
|
|
222
|
-
if (/invalid_json_schema/.test(message)) {
|
|
223
|
-
return {
|
|
224
|
-
kind: "failed",
|
|
225
|
-
failure: { kind: "schema-rejected", message: `codex rejected the output schema: ${message}` }
|
|
226
|
-
};
|
|
133
|
+
const usage = json.usage ?? {};
|
|
134
|
+
const promptTokens = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
|
|
135
|
+
const completionTokens = usage.output_tokens ?? 0;
|
|
136
|
+
const reported = {
|
|
137
|
+
usage: {
|
|
138
|
+
promptTokens,
|
|
139
|
+
completionTokens,
|
|
140
|
+
totalTokens: promptTokens + completionTokens,
|
|
141
|
+
...usage.cache_read_input_tokens !== void 0 && { promptCacheReadTokens: usage.cache_read_input_tokens },
|
|
142
|
+
...usage.cache_creation_input_tokens !== void 0 && {
|
|
143
|
+
promptCacheWriteTokens: usage.cache_creation_input_tokens
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
model: Object.keys(json.modelUsage ?? {})[0],
|
|
147
|
+
costUsdEstimate: json.total_cost_usd
|
|
148
|
+
};
|
|
149
|
+
if (request.enforcement.kind === "in-prompt") {
|
|
150
|
+
return { kind: "answered", answer: { kind: "text", text: json.result ?? "" }, ...reported };
|
|
151
|
+
}
|
|
152
|
+
if (!json.structured_output) {
|
|
153
|
+
return {
|
|
154
|
+
kind: "failed",
|
|
155
|
+
failure: {
|
|
156
|
+
kind: "transient",
|
|
157
|
+
message: `returned no structured output. Reply: ${json.result ?? ""}`
|
|
227
158
|
}
|
|
228
|
-
return { kind: "failed", failure: (0, failure_classification_1.classifyAgentFailure)("codex", message) };
|
|
229
|
-
}
|
|
230
|
-
const agentMessages = events.filter((e) => e.item?.type === "agent_message");
|
|
231
|
-
const lastMessage = result.outputFiles[LAST_MESSAGE_FILE] ?? agentMessages[agentMessages.length - 1]?.item?.text;
|
|
232
|
-
if (result.exitCode !== 0 || lastMessage === void 0) {
|
|
233
|
-
return {
|
|
234
|
-
kind: "failed",
|
|
235
|
-
failure: (0, failure_classification_1.classifyAgentFailure)("codex", `exited with code ${result.exitCode} without a reply: ${(0, failure_classification_1.stderrTail)(result)}`)
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
const completed = events.filter((e) => e.type === "turn.completed");
|
|
239
|
-
const promptTokens = completed.reduce((sum, e) => sum + (e.usage?.input_tokens ?? 0), 0);
|
|
240
|
-
const completionTokens = completed.reduce((sum, e) => sum + (e.usage?.output_tokens ?? 0), 0);
|
|
241
|
-
const reported = {
|
|
242
|
-
usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens },
|
|
243
|
-
// Codex does not report which model answered.
|
|
244
|
-
model: void 0,
|
|
245
|
-
costUsdEstimate: void 0
|
|
246
159
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
kind: "answered",
|
|
163
|
+
answer: { kind: "structured", envelope: json.structured_output },
|
|
164
|
+
...reported
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
loginStatusArgs: ["auth", "status"],
|
|
168
|
+
readLoginStatus(result) {
|
|
169
|
+
try {
|
|
170
|
+
const status = JSON.parse(result.stdout);
|
|
171
|
+
return { loggedIn: status.loggedIn === true, detail: status.authMethod };
|
|
172
|
+
} catch {
|
|
173
|
+
return { loggedIn: false, detail: stderrTail(result) || result.stdout.trim() };
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
sessionArgs(model, approval) {
|
|
177
|
+
return [
|
|
178
|
+
...model === void 0 ? [] : ["--model", model],
|
|
179
|
+
...approval === "auto" ? ["--permission-mode", "auto"] : []
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const SCHEMA_FILE = "output-schema.json";
|
|
185
|
+
const LAST_MESSAGE_FILE = "last-message.txt";
|
|
186
|
+
const codexAdapter = {
|
|
187
|
+
id: "codex",
|
|
188
|
+
binaryName: "codex",
|
|
189
|
+
apiKeyEnvVars: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
|
190
|
+
// Codex's models have no equivalent among the aliases; set one with localAgent({ model }).
|
|
191
|
+
modelAliases: {},
|
|
192
|
+
// Codex accepts only OpenAI strict-mode schemas.
|
|
193
|
+
nativeSchema(envelopeSchema) {
|
|
194
|
+
return strictJsonSchema.toStrictSchema(envelopeSchema);
|
|
195
|
+
},
|
|
196
|
+
buildInvocation(request, { model, effort }) {
|
|
197
|
+
const args = [
|
|
198
|
+
"exec",
|
|
199
|
+
"-",
|
|
200
|
+
"--skip-git-repo-check",
|
|
201
|
+
"--ephemeral",
|
|
202
|
+
"--ignore-user-config",
|
|
203
|
+
"--ignore-rules",
|
|
204
|
+
"--sandbox",
|
|
205
|
+
"read-only",
|
|
206
|
+
"--json",
|
|
207
|
+
"-o",
|
|
208
|
+
LAST_MESSAGE_FILE
|
|
209
|
+
];
|
|
210
|
+
if (model !== void 0) {
|
|
211
|
+
args.push("-m", model);
|
|
212
|
+
}
|
|
213
|
+
if (effort !== void 0) {
|
|
214
|
+
args.push("-c", `model_reasoning_effort=${effort}`);
|
|
215
|
+
}
|
|
216
|
+
const files = [];
|
|
217
|
+
if (request.enforcement.kind === "native") {
|
|
218
|
+
args.push("--output-schema", SCHEMA_FILE);
|
|
219
|
+
files.push({ name: SCHEMA_FILE, content: JSON.stringify(request.enforcement.schema) });
|
|
220
|
+
}
|
|
221
|
+
return { args, stdin: request.prompt, files, outputFiles: [LAST_MESSAGE_FILE] };
|
|
222
|
+
},
|
|
223
|
+
readResult(result, request) {
|
|
224
|
+
const events = parseEvents$1(result.stdout);
|
|
225
|
+
const failed = events.find((e) => e.type === "turn.failed" || e.type === "error");
|
|
226
|
+
if (failed) {
|
|
227
|
+
const raw = failed.error?.message ?? failed.message ?? "unknown error";
|
|
228
|
+
const message = apiErrorMessage(raw);
|
|
229
|
+
if (/invalid_json_schema/.test(raw)) {
|
|
254
230
|
return {
|
|
255
231
|
kind: "failed",
|
|
256
|
-
failure: { kind: "
|
|
232
|
+
failure: { kind: "schema-rejected", message: `rejected the output schema: ${message}` }
|
|
257
233
|
};
|
|
258
234
|
}
|
|
259
|
-
return { kind: "
|
|
260
|
-
},
|
|
261
|
-
loginStatusArgs: ["login", "status"],
|
|
262
|
-
readLoginStatus(result) {
|
|
263
|
-
const detail = `${result.stdout}
|
|
264
|
-
${result.stderr}`.trim();
|
|
265
|
-
return { loggedIn: result.exitCode === 0, detail };
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
function parseEvents(stdout) {
|
|
269
|
-
const events = [];
|
|
270
|
-
for (const line of stdout.split("\n")) {
|
|
271
|
-
const trimmed = line.trim();
|
|
272
|
-
if (!trimmed.startsWith("{"))
|
|
273
|
-
continue;
|
|
274
|
-
try {
|
|
275
|
-
events.push(JSON.parse(trimmed));
|
|
276
|
-
} catch {
|
|
277
|
-
}
|
|
235
|
+
return { kind: "failed", failure: { ...classifyAgentFailure(raw), message } };
|
|
278
236
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
function requirePi() {
|
|
288
|
-
if (hasRequiredPi) return pi;
|
|
289
|
-
hasRequiredPi = 1;
|
|
290
|
-
Object.defineProperty(pi, "__esModule", { value: true });
|
|
291
|
-
pi.piAdapter = void 0;
|
|
292
|
-
const failure_classification_1 = requireFailureClassification();
|
|
293
|
-
const SYSTEM_PROMPT = [
|
|
294
|
-
"You are a data transformation function called by a program.",
|
|
295
|
-
"You have no tools. Follow the instructions in the user message exactly",
|
|
296
|
-
"and reply only with the requested JSON."
|
|
297
|
-
].join(" ");
|
|
298
|
-
pi.piAdapter = {
|
|
299
|
-
id: "pi",
|
|
300
|
-
binaryName: "pi",
|
|
301
|
-
// Every environment credential Pi's providers recognize. Under `auth:
|
|
302
|
-
// "subscription"` these are removed so Pi bills its own `auth.json` login.
|
|
303
|
-
apiKeyEnvVars: [
|
|
304
|
-
"AI_GATEWAY_API_KEY",
|
|
305
|
-
"ANT_LING_API_KEY",
|
|
306
|
-
"ANTHROPIC_API_KEY",
|
|
307
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
308
|
-
"ANTHROPIC_OAUTH_TOKEN",
|
|
309
|
-
"AWS_ACCESS_KEY_ID",
|
|
310
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
311
|
-
"AWS_SESSION_TOKEN",
|
|
312
|
-
"AZURE_OPENAI_API_KEY",
|
|
313
|
-
"BASETEN_API_KEY",
|
|
314
|
-
"CEREBRAS_API_KEY",
|
|
315
|
-
"CLOUDFLARE_API_KEY",
|
|
316
|
-
"COPILOT_GITHUB_TOKEN",
|
|
317
|
-
"DEEPSEEK_API_KEY",
|
|
318
|
-
"FIREWORKS_API_KEY",
|
|
319
|
-
"GEMINI_API_KEY",
|
|
320
|
-
"GOOGLE_API_KEY",
|
|
321
|
-
"GOOGLE_CLOUD_API_KEY",
|
|
322
|
-
"GROQ_API_KEY",
|
|
323
|
-
"HF_TOKEN",
|
|
324
|
-
"KIMI_API_KEY",
|
|
325
|
-
"META_API_KEY",
|
|
326
|
-
"MINIMAX_API_KEY",
|
|
327
|
-
"MINIMAX_CN_API_KEY",
|
|
328
|
-
"MISTRAL_API_KEY",
|
|
329
|
-
"MOONSHOT_API_KEY",
|
|
330
|
-
"NVIDIA_API_KEY",
|
|
331
|
-
"OPENAI_API_KEY",
|
|
332
|
-
"OPENCODE_API_KEY",
|
|
333
|
-
"OPENROUTER_API_KEY",
|
|
334
|
-
"QWEN_TOKEN_PLAN_API_KEY",
|
|
335
|
-
"QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
336
|
-
"RADIUS_API_KEY",
|
|
337
|
-
"TOGETHER_API_KEY",
|
|
338
|
-
"XAI_API_KEY",
|
|
339
|
-
"XIAOMI_API_KEY",
|
|
340
|
-
"XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
341
|
-
"XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
342
|
-
"XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
343
|
-
"ZAI_API_KEY",
|
|
344
|
-
"ZAI_CODING_CN_API_KEY"
|
|
345
|
-
],
|
|
346
|
-
// Pi uses its own `provider/id` model names; set one with localAgent({ model }).
|
|
347
|
-
modelAliases: {},
|
|
348
|
-
nativeSchema() {
|
|
349
|
-
return null;
|
|
350
|
-
},
|
|
351
|
-
buildInvocation(request, model) {
|
|
352
|
-
const args = [
|
|
353
|
-
"--mode",
|
|
354
|
-
"json",
|
|
355
|
-
"--no-session",
|
|
356
|
-
"--no-tools",
|
|
357
|
-
"--no-builtin-tools",
|
|
358
|
-
"--no-extensions",
|
|
359
|
-
"--no-skills",
|
|
360
|
-
"--no-prompt-templates",
|
|
361
|
-
"--no-themes",
|
|
362
|
-
"--no-context-files",
|
|
363
|
-
"--system-prompt",
|
|
364
|
-
SYSTEM_PROMPT,
|
|
365
|
-
"--offline"
|
|
366
|
-
];
|
|
367
|
-
if (model !== void 0) {
|
|
368
|
-
args.push("--model", model);
|
|
369
|
-
}
|
|
370
|
-
return { args, stdin: request.prompt };
|
|
371
|
-
},
|
|
372
|
-
readResult(result, request) {
|
|
373
|
-
const events = parseEvents(result.stdout);
|
|
374
|
-
const assistant = events.filter((event) => event.type === "message_end" && event.message?.role === "assistant").map((event) => event.message).pop();
|
|
375
|
-
if (assistant && (assistant.stopReason === "error" || assistant.errorMessage)) {
|
|
376
|
-
return failed((0, failure_classification_1.classifyAgentFailure)("pi", assistant.errorMessage ?? "pi stopped with an error"));
|
|
377
|
-
}
|
|
378
|
-
const retryFailed = events.find((event) => event.type === "auto_retry_end" && event.success === false);
|
|
379
|
-
if (retryFailed) {
|
|
380
|
-
return failed((0, failure_classification_1.classifyAgentFailure)("pi", retryFailed.finalError ?? "auto retry failed"));
|
|
381
|
-
}
|
|
382
|
-
if (!assistant) {
|
|
383
|
-
return failed((0, failure_classification_1.classifyAgentFailure)("pi", `exited with code ${result.exitCode} without a reply: ${(0, failure_classification_1.stderrTail)(result)}`));
|
|
384
|
-
}
|
|
385
|
-
const text = (assistant.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
386
|
-
const usage = assistant.usage ?? {};
|
|
387
|
-
const promptTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
388
|
-
const completionTokens = usage.output ?? 0;
|
|
389
|
-
const reported = {
|
|
390
|
-
usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens },
|
|
391
|
-
model: assistant.model,
|
|
392
|
-
costUsdEstimate: usage.cost?.total
|
|
237
|
+
const agentMessages = events.filter((e) => e.item?.type === "agent_message");
|
|
238
|
+
const lastMessage = result.outputFiles[LAST_MESSAGE_FILE] ?? agentMessages[agentMessages.length - 1]?.item?.text;
|
|
239
|
+
if (result.exitCode !== 0 || lastMessage === void 0) {
|
|
240
|
+
return {
|
|
241
|
+
kind: "failed",
|
|
242
|
+
failure: classifyAgentFailure(
|
|
243
|
+
`exited with code ${result.exitCode} without a reply: ${stderrTail(result)}`
|
|
244
|
+
)
|
|
393
245
|
};
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
246
|
+
}
|
|
247
|
+
const completed = events.filter((e) => e.type === "turn.completed");
|
|
248
|
+
const promptTokens = completed.reduce((sum, e) => sum + (e.usage?.input_tokens ?? 0), 0);
|
|
249
|
+
const completionTokens = completed.reduce((sum, e) => sum + (e.usage?.output_tokens ?? 0), 0);
|
|
250
|
+
const cacheReadReported = completed.some((e) => e.usage?.cached_input_tokens !== void 0);
|
|
251
|
+
const cacheReadTokens = completed.reduce((sum, e) => sum + (e.usage?.cached_input_tokens ?? 0), 0);
|
|
252
|
+
const reported = {
|
|
253
|
+
usage: {
|
|
254
|
+
promptTokens,
|
|
255
|
+
completionTokens,
|
|
256
|
+
totalTokens: promptTokens + completionTokens,
|
|
257
|
+
...cacheReadReported && { promptCacheReadTokens: cacheReadTokens }
|
|
258
|
+
},
|
|
259
|
+
// Codex does not report which model answered.
|
|
260
|
+
model: void 0,
|
|
261
|
+
costUsdEstimate: void 0
|
|
262
|
+
};
|
|
263
|
+
if (request.enforcement.kind === "in-prompt") {
|
|
264
|
+
return { kind: "answered", answer: { kind: "text", text: lastMessage }, ...reported };
|
|
265
|
+
}
|
|
266
|
+
let envelope;
|
|
267
|
+
try {
|
|
268
|
+
envelope = JSON.parse(lastMessage);
|
|
269
|
+
} catch {
|
|
411
270
|
return {
|
|
412
|
-
|
|
413
|
-
|
|
271
|
+
kind: "failed",
|
|
272
|
+
failure: { kind: "transient", message: `reply is not JSON: ${lastMessage}` }
|
|
414
273
|
};
|
|
415
274
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
275
|
+
return { kind: "answered", answer: { kind: "structured", envelope }, ...reported };
|
|
276
|
+
},
|
|
277
|
+
loginStatusArgs: ["login", "status"],
|
|
278
|
+
readLoginStatus(result) {
|
|
279
|
+
const detail = `${result.stdout}
|
|
280
|
+
${result.stderr}`.trim();
|
|
281
|
+
return { loggedIn: result.exitCode === 0, detail };
|
|
282
|
+
},
|
|
283
|
+
sessionArgs(model, approval) {
|
|
284
|
+
return [
|
|
285
|
+
...model === void 0 ? [] : ["--model", model],
|
|
286
|
+
...approval === "auto" ? ["--approve-for-me"] : []
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
function parseEvents$1(stdout) {
|
|
291
|
+
const events = [];
|
|
292
|
+
for (const line of stdout.split("\n")) {
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
if (!trimmed.startsWith("{")) continue;
|
|
295
|
+
try {
|
|
296
|
+
events.push(JSON.parse(trimmed));
|
|
297
|
+
} catch {
|
|
433
298
|
}
|
|
434
|
-
return events;
|
|
435
299
|
}
|
|
436
|
-
return
|
|
300
|
+
return events;
|
|
437
301
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const claude_code_1 = requireClaudeCode();
|
|
446
|
-
const codex_1 = requireCodex();
|
|
447
|
-
const pi_1 = requirePi();
|
|
448
|
-
adapters.AGENT_ADAPTERS = {
|
|
449
|
-
"claude-code": claude_code_1.claudeCodeAdapter,
|
|
450
|
-
codex: codex_1.codexAdapter,
|
|
451
|
-
pi: pi_1.piAdapter
|
|
452
|
-
};
|
|
453
|
-
return adapters;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
var agentId = {};
|
|
457
|
-
|
|
458
|
-
var hasRequiredAgentId;
|
|
459
|
-
function requireAgentId() {
|
|
460
|
-
if (hasRequiredAgentId) return agentId;
|
|
461
|
-
hasRequiredAgentId = 1;
|
|
462
|
-
(function(exports$1) {
|
|
463
|
-
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
464
|
-
exports$1.LOCAL_AGENT_IDS = void 0;
|
|
465
|
-
exports$1.isLocalAgentId = isLocalAgentId;
|
|
466
|
-
exports$1.LOCAL_AGENT_IDS = ["claude-code", "codex", "pi"];
|
|
467
|
-
const LOCAL_AGENT_ID_SET = new Set(exports$1.LOCAL_AGENT_IDS);
|
|
468
|
-
function isLocalAgentId(value) {
|
|
469
|
-
return typeof value === "string" && LOCAL_AGENT_ID_SET.has(value);
|
|
470
|
-
}
|
|
471
|
-
})(agentId);
|
|
472
|
-
return agentId;
|
|
302
|
+
function apiErrorMessage(message) {
|
|
303
|
+
try {
|
|
304
|
+
const inner = JSON.parse(message)?.error?.message;
|
|
305
|
+
if (typeof inner === "string") return inner;
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
return message;
|
|
473
309
|
}
|
|
474
310
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
311
|
+
const SYSTEM_PROMPT = [
|
|
312
|
+
"You are a data transformation function called by a program.",
|
|
313
|
+
"You have no tools. Follow the instructions in the user message exactly",
|
|
314
|
+
"and reply only with the requested JSON."
|
|
315
|
+
].join(" ");
|
|
316
|
+
const piAdapter = {
|
|
317
|
+
id: "pi",
|
|
318
|
+
binaryName: "pi",
|
|
319
|
+
// Every environment credential Pi's providers recognize. Under `auth:
|
|
320
|
+
// "subscription"` these are removed so Pi bills its own `auth.json` login.
|
|
321
|
+
apiKeyEnvVars: [
|
|
322
|
+
"AI_GATEWAY_API_KEY",
|
|
323
|
+
"ANT_LING_API_KEY",
|
|
324
|
+
"ANTHROPIC_API_KEY",
|
|
325
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
326
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
327
|
+
"AWS_ACCESS_KEY_ID",
|
|
328
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
329
|
+
"AWS_SESSION_TOKEN",
|
|
330
|
+
"AZURE_OPENAI_API_KEY",
|
|
331
|
+
"BASETEN_API_KEY",
|
|
332
|
+
"CEREBRAS_API_KEY",
|
|
333
|
+
"CLOUDFLARE_API_KEY",
|
|
334
|
+
"COPILOT_GITHUB_TOKEN",
|
|
335
|
+
"DEEPSEEK_API_KEY",
|
|
336
|
+
"FIREWORKS_API_KEY",
|
|
337
|
+
"GEMINI_API_KEY",
|
|
338
|
+
"GOOGLE_API_KEY",
|
|
339
|
+
"GOOGLE_CLOUD_API_KEY",
|
|
340
|
+
"GROQ_API_KEY",
|
|
341
|
+
"HF_TOKEN",
|
|
342
|
+
"KIMI_API_KEY",
|
|
343
|
+
"META_API_KEY",
|
|
344
|
+
"MINIMAX_API_KEY",
|
|
345
|
+
"MINIMAX_CN_API_KEY",
|
|
346
|
+
"MISTRAL_API_KEY",
|
|
347
|
+
"MOONSHOT_API_KEY",
|
|
348
|
+
"NVIDIA_API_KEY",
|
|
349
|
+
"OPENAI_API_KEY",
|
|
350
|
+
"OPENCODE_API_KEY",
|
|
351
|
+
"OPENROUTER_API_KEY",
|
|
352
|
+
"QWEN_TOKEN_PLAN_API_KEY",
|
|
353
|
+
"QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
354
|
+
"RADIUS_API_KEY",
|
|
355
|
+
"TOGETHER_API_KEY",
|
|
356
|
+
"XAI_API_KEY",
|
|
357
|
+
"XIAOMI_API_KEY",
|
|
358
|
+
"XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
359
|
+
"XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
360
|
+
"XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
361
|
+
"ZAI_API_KEY",
|
|
362
|
+
"ZAI_CODING_CN_API_KEY"
|
|
363
|
+
],
|
|
364
|
+
// Pi uses its own `provider/id` model names; set one with localAgent({ model }).
|
|
365
|
+
modelAliases: {},
|
|
366
|
+
nativeSchema() {
|
|
367
|
+
return null;
|
|
368
|
+
},
|
|
369
|
+
buildInvocation(request, { model, effort }) {
|
|
370
|
+
const args = [
|
|
371
|
+
"--mode",
|
|
372
|
+
"json",
|
|
373
|
+
"--no-session",
|
|
374
|
+
"--no-tools",
|
|
375
|
+
"--no-builtin-tools",
|
|
376
|
+
"--no-extensions",
|
|
377
|
+
"--no-skills",
|
|
378
|
+
"--no-prompt-templates",
|
|
379
|
+
"--no-themes",
|
|
380
|
+
"--no-context-files",
|
|
381
|
+
"--system-prompt",
|
|
382
|
+
SYSTEM_PROMPT,
|
|
383
|
+
"--offline"
|
|
384
|
+
];
|
|
385
|
+
if (model !== void 0) {
|
|
386
|
+
args.push("--model", model);
|
|
387
|
+
}
|
|
388
|
+
if (effort !== void 0) {
|
|
389
|
+
args.push("--thinking", effort);
|
|
390
|
+
}
|
|
391
|
+
return { args, stdin: request.prompt };
|
|
392
|
+
},
|
|
393
|
+
readResult(result, request) {
|
|
394
|
+
const events = parseEvents(result.stdout);
|
|
395
|
+
const assistant = events.filter((event) => event.type === "message_end" && event.message?.role === "assistant").map((event) => event.message).pop();
|
|
396
|
+
if (assistant && (assistant.stopReason === "error" || assistant.errorMessage)) {
|
|
397
|
+
return failed(classifyAgentFailure(assistant.errorMessage ?? "stopped with an error"));
|
|
398
|
+
}
|
|
399
|
+
const retryFailed = events.find((event) => event.type === "auto_retry_end" && event.success === false);
|
|
400
|
+
if (retryFailed) {
|
|
401
|
+
return failed(classifyAgentFailure(retryFailed.finalError ?? "auto retry failed"));
|
|
402
|
+
}
|
|
403
|
+
if (!assistant) {
|
|
404
|
+
return failed(
|
|
405
|
+
classifyAgentFailure(
|
|
406
|
+
`exited with code ${result.exitCode} without a reply: ${stderrTail(result)}`
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
const text = (assistant.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
411
|
+
const usage = assistant.usage ?? {};
|
|
412
|
+
const promptTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
413
|
+
const completionTokens = usage.output ?? 0;
|
|
414
|
+
const reported = {
|
|
415
|
+
usage: {
|
|
416
|
+
promptTokens,
|
|
417
|
+
completionTokens,
|
|
418
|
+
totalTokens: promptTokens + completionTokens,
|
|
419
|
+
...usage.cacheRead !== void 0 && { promptCacheReadTokens: usage.cacheRead },
|
|
420
|
+
...usage.cacheWrite !== void 0 && { promptCacheWriteTokens: usage.cacheWrite }
|
|
421
|
+
},
|
|
422
|
+
model: assistant.model,
|
|
423
|
+
costUsdEstimate: usage.cost?.total
|
|
521
424
|
};
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return
|
|
425
|
+
if (request.enforcement.kind === "in-prompt") {
|
|
426
|
+
return { kind: "answered", answer: { kind: "text", text }, ...reported };
|
|
427
|
+
}
|
|
428
|
+
let envelope;
|
|
429
|
+
try {
|
|
430
|
+
envelope = JSON.parse(text);
|
|
431
|
+
} catch {
|
|
432
|
+
return failed({ kind: "transient", message: `reply is not JSON: ${text}` });
|
|
433
|
+
}
|
|
434
|
+
return { kind: "answered", answer: { kind: "structured", envelope }, ...reported };
|
|
435
|
+
},
|
|
436
|
+
// Pi has no provider-agnostic login check: `pi auth check` requires --provider
|
|
437
|
+
// or --model, and the provider differs per user (it is not known until Pi's
|
|
438
|
+
// settings are read). Report installed Pi as logged in; the first real call
|
|
439
|
+
// surfaces any auth error, which classifyAgentFailure marks fatal.
|
|
440
|
+
loginStatusArgs: ["--version"],
|
|
441
|
+
readLoginStatus(result) {
|
|
442
|
+
return {
|
|
443
|
+
loggedIn: result.exitCode === 0,
|
|
444
|
+
detail: "installed; auth is verified on the first call"
|
|
530
445
|
};
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
446
|
+
},
|
|
447
|
+
sessionArgs(model, approval) {
|
|
448
|
+
return [
|
|
449
|
+
...model === void 0 ? [] : ["--model", model],
|
|
450
|
+
...approval === "auto" ? ["--approve"] : []
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
function failed(failure) {
|
|
455
|
+
return { kind: "failed", failure };
|
|
456
|
+
}
|
|
457
|
+
function parseEvents(stdout) {
|
|
458
|
+
const events = [];
|
|
459
|
+
for (const line of stdout.split("\n")) {
|
|
460
|
+
const trimmed = line.replace(/\r$/, "").trim();
|
|
461
|
+
if (!trimmed.startsWith("{")) continue;
|
|
542
462
|
try {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const result = await spawnAndCollect(options, workDir);
|
|
547
|
-
for (const name of options.invocation.outputFiles ?? []) {
|
|
548
|
-
const filePath = path.join(workDir, name);
|
|
549
|
-
result.outputFiles[name] = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : void 0;
|
|
463
|
+
const parsed = JSON.parse(trimmed);
|
|
464
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
465
|
+
events.push(parsed);
|
|
550
466
|
}
|
|
551
|
-
|
|
552
|
-
} finally {
|
|
553
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
467
|
+
} catch {
|
|
554
468
|
}
|
|
555
469
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
470
|
+
return events;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const AGENT_ADAPTERS = {
|
|
474
|
+
"claude-code": claudeCodeAdapter,
|
|
475
|
+
codex: codexAdapter,
|
|
476
|
+
pi: piAdapter
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const LOCAL_AGENT_IDS = ["claude-code", "codex", "pi"];
|
|
480
|
+
const LOCAL_AGENT_ID_SET = new Set(LOCAL_AGENT_IDS);
|
|
481
|
+
function isLocalAgentId(value) {
|
|
482
|
+
return typeof value === "string" && LOCAL_AGENT_ID_SET.has(value);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const KILL_GRACE_MS = 2e3;
|
|
486
|
+
async function runInvocation(options) {
|
|
487
|
+
const workDir = fs__namespace.mkdtempSync(path__namespace.join(os__namespace.tmpdir(), "nua-local-agent-"));
|
|
488
|
+
try {
|
|
489
|
+
for (const file of options.invocation.files ?? []) {
|
|
490
|
+
fs__namespace.writeFileSync(path__namespace.join(workDir, file.name), file.content);
|
|
560
491
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
492
|
+
const result = await spawnAndCollect(options, workDir);
|
|
493
|
+
for (const name of options.invocation.outputFiles ?? []) {
|
|
494
|
+
const filePath = path__namespace.join(workDir, name);
|
|
495
|
+
result.outputFiles[name] = fs__namespace.existsSync(filePath) ? fs__namespace.readFileSync(filePath, "utf8") : void 0;
|
|
496
|
+
}
|
|
497
|
+
return result;
|
|
498
|
+
} finally {
|
|
499
|
+
fs__namespace.rmSync(workDir, { recursive: true, force: true });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function spawnAndCollect(options, cwd) {
|
|
503
|
+
const env = { ...process.env };
|
|
504
|
+
for (const name of options.unsetEnv ?? []) {
|
|
505
|
+
delete env[name];
|
|
506
|
+
}
|
|
507
|
+
return new Promise((resolve, reject) => {
|
|
508
|
+
const child = node_child_process.spawn(options.binaryPath, options.invocation.args, {
|
|
509
|
+
cwd,
|
|
510
|
+
env,
|
|
511
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
512
|
+
windowsHide: true
|
|
513
|
+
});
|
|
514
|
+
const stdout = [];
|
|
515
|
+
const stderr = [];
|
|
516
|
+
let timedOut = false;
|
|
517
|
+
let killTimer;
|
|
518
|
+
const timeoutTimer = setTimeout(() => {
|
|
519
|
+
timedOut = true;
|
|
520
|
+
child.kill("SIGINT");
|
|
521
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
522
|
+
}, options.timeoutMs);
|
|
523
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
524
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
525
|
+
child.on("error", (error) => {
|
|
526
|
+
clearTimeout(timeoutTimer);
|
|
527
|
+
clearTimeout(killTimer);
|
|
528
|
+
const reason = error.code === "ENOENT" ? "not found" : error.message;
|
|
529
|
+
reject(new Error(`Could not start ${options.binaryPath}: ${reason}`));
|
|
530
|
+
});
|
|
531
|
+
child.on("close", (exitCode) => {
|
|
532
|
+
clearTimeout(timeoutTimer);
|
|
533
|
+
clearTimeout(killTimer);
|
|
534
|
+
resolve({
|
|
535
|
+
exitCode,
|
|
536
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
537
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
538
|
+
timedOut,
|
|
539
|
+
outputFiles: {}
|
|
595
540
|
});
|
|
596
|
-
child.stdin.on("error", () => void 0);
|
|
597
|
-
child.stdin.end(options.invocation.stdin);
|
|
598
541
|
});
|
|
542
|
+
child.stdin.on("error", () => void 0);
|
|
543
|
+
child.stdin.end(options.invocation.stdin);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function findOnPath(binaryName) {
|
|
547
|
+
const isWindows = process.platform === "win32";
|
|
548
|
+
const extensions = isWindows ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
549
|
+
if (isWindows) {
|
|
550
|
+
extensions.sort((a, b) => Number(b.toUpperCase() === ".EXE") - Number(a.toUpperCase() === ".EXE"));
|
|
599
551
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
continue;
|
|
609
|
-
for (const ext of extensions) {
|
|
610
|
-
const candidate = path.join(dir, binaryName + ext);
|
|
611
|
-
try {
|
|
612
|
-
if (fs.statSync(candidate).isFile()) {
|
|
613
|
-
if (!isWindows)
|
|
614
|
-
fs.accessSync(candidate, fs.constants.X_OK);
|
|
615
|
-
return candidate;
|
|
616
|
-
}
|
|
617
|
-
} catch {
|
|
552
|
+
for (const dir of (process.env.PATH ?? "").split(path__namespace.delimiter)) {
|
|
553
|
+
if (!dir) continue;
|
|
554
|
+
for (const ext of extensions) {
|
|
555
|
+
const candidate = path__namespace.join(dir, binaryName + ext);
|
|
556
|
+
try {
|
|
557
|
+
if (fs__namespace.statSync(candidate).isFile()) {
|
|
558
|
+
if (!isWindows) fs__namespace.accessSync(candidate, fs__namespace.constants.X_OK);
|
|
559
|
+
return candidate;
|
|
618
560
|
}
|
|
561
|
+
} catch {
|
|
619
562
|
}
|
|
620
563
|
}
|
|
621
|
-
return null;
|
|
622
564
|
}
|
|
623
|
-
return
|
|
565
|
+
return null;
|
|
624
566
|
}
|
|
625
567
|
|
|
626
|
-
|
|
627
|
-
function
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const binaryPath = binaryPathOverride ?? (0, process_runner_1.findOnPath)(adapter.binaryName);
|
|
641
|
-
if (!binaryPath) {
|
|
642
|
-
return { agent: adapter.id, installed: false, loggedIn: false };
|
|
643
|
-
}
|
|
644
|
-
try {
|
|
645
|
-
const [versionResult, statusResult] = await Promise.all([
|
|
646
|
-
(0, process_runner_1.runInvocation)({
|
|
647
|
-
binaryPath,
|
|
648
|
-
invocation: { args: ["--version"], stdin: "" },
|
|
649
|
-
timeoutMs: STATUS_TIMEOUT_MS
|
|
650
|
-
}),
|
|
651
|
-
(0, process_runner_1.runInvocation)({
|
|
652
|
-
binaryPath,
|
|
653
|
-
invocation: { args: adapter.loginStatusArgs, stdin: "" },
|
|
654
|
-
timeoutMs: STATUS_TIMEOUT_MS,
|
|
655
|
-
// Report the CLI's own login, not an API key that happens to be set.
|
|
656
|
-
unsetEnv: adapter.apiKeyEnvVars
|
|
657
|
-
})
|
|
658
|
-
]);
|
|
659
|
-
if (versionResult.exitCode !== 0) {
|
|
660
|
-
return {
|
|
661
|
-
agent: adapter.id,
|
|
662
|
-
installed: false,
|
|
663
|
-
loggedIn: false,
|
|
664
|
-
detail: `${binaryPath} --version exited with code ${versionResult.exitCode}`
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
const login = adapter.readLoginStatus(statusResult);
|
|
668
|
-
return {
|
|
669
|
-
agent: adapter.id,
|
|
670
|
-
installed: true,
|
|
671
|
-
loggedIn: login.loggedIn,
|
|
568
|
+
const STATUS_TIMEOUT_MS = 2e4;
|
|
569
|
+
async function detectLocalAgents(binaryPaths = {}) {
|
|
570
|
+
return Promise.all(
|
|
571
|
+
LOCAL_AGENT_IDS.map((id) => detectAgent(AGENT_ADAPTERS[id], binaryPaths[id]))
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
async function detectAgent(adapter, binaryPathOverride) {
|
|
575
|
+
const binaryPath = binaryPathOverride ?? findOnPath(adapter.binaryName);
|
|
576
|
+
if (!binaryPath) {
|
|
577
|
+
return { agent: adapter.id, installed: false, loggedIn: false };
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const [versionResult, statusResult] = await Promise.all([
|
|
581
|
+
runInvocation({
|
|
672
582
|
binaryPath,
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
|
|
583
|
+
invocation: { args: ["--version"], stdin: "" },
|
|
584
|
+
timeoutMs: STATUS_TIMEOUT_MS
|
|
585
|
+
}),
|
|
586
|
+
runInvocation({
|
|
587
|
+
binaryPath,
|
|
588
|
+
invocation: { args: adapter.loginStatusArgs, stdin: "" },
|
|
589
|
+
timeoutMs: STATUS_TIMEOUT_MS,
|
|
590
|
+
// Report the CLI's own login, not an API key that happens to be set.
|
|
591
|
+
unsetEnv: adapter.apiKeyEnvVars
|
|
592
|
+
})
|
|
593
|
+
]);
|
|
594
|
+
if (versionResult.exitCode !== 0) {
|
|
677
595
|
return {
|
|
678
596
|
agent: adapter.id,
|
|
679
597
|
installed: false,
|
|
680
598
|
loggedIn: false,
|
|
681
|
-
detail:
|
|
599
|
+
detail: `${binaryPath} --version exited with code ${versionResult.exitCode}`
|
|
682
600
|
};
|
|
683
601
|
}
|
|
602
|
+
const login = adapter.readLoginStatus(statusResult);
|
|
603
|
+
return {
|
|
604
|
+
agent: adapter.id,
|
|
605
|
+
installed: true,
|
|
606
|
+
loggedIn: login.loggedIn,
|
|
607
|
+
binaryPath,
|
|
608
|
+
version: versionResult.stdout.trim() || void 0,
|
|
609
|
+
detail: login.detail
|
|
610
|
+
};
|
|
611
|
+
} catch (error) {
|
|
612
|
+
return {
|
|
613
|
+
agent: adapter.id,
|
|
614
|
+
installed: false,
|
|
615
|
+
loggedIn: false,
|
|
616
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
617
|
+
};
|
|
684
618
|
}
|
|
685
|
-
return detect;
|
|
686
619
|
}
|
|
687
620
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
method,
|
|
621
|
+
class LocalAgentEngine {
|
|
622
|
+
constructor(runner, call) {
|
|
623
|
+
this.runner = runner;
|
|
624
|
+
this.call = call;
|
|
625
|
+
}
|
|
626
|
+
nativeSchema(envelopeSchema) {
|
|
627
|
+
return this.runner.adapter.nativeSchema(envelopeSchema);
|
|
628
|
+
}
|
|
629
|
+
attempt(request) {
|
|
630
|
+
return this.runner.limiter.run(() => this.runAgent(request));
|
|
631
|
+
}
|
|
632
|
+
async runAgent(request) {
|
|
633
|
+
const { adapter, binaryPath, logger, unsetEnv } = this.runner;
|
|
634
|
+
const { model, effort, timeoutMs } = this.call;
|
|
635
|
+
const spanId = strictJsonSchema.generateSpanId();
|
|
636
|
+
const startTime = Date.now();
|
|
637
|
+
const method = request.enforcement.kind;
|
|
638
|
+
logger.info("LLM call started", {
|
|
639
|
+
type: "llm_call_start",
|
|
640
|
+
span_id: spanId,
|
|
641
|
+
service: adapter.id,
|
|
642
|
+
method,
|
|
643
|
+
binaryPath,
|
|
644
|
+
model: model ?? "default"
|
|
645
|
+
});
|
|
646
|
+
const failed = (failure) => {
|
|
647
|
+
strictJsonSchema.logLlmCallError(logger, spanId, adapter.id, method, failure.message, Date.now() - startTime);
|
|
648
|
+
return { kind: "failed", failure };
|
|
649
|
+
};
|
|
650
|
+
let result;
|
|
651
|
+
try {
|
|
652
|
+
result = await runInvocation({
|
|
721
653
|
binaryPath,
|
|
722
|
-
|
|
654
|
+
invocation: adapter.buildInvocation(request, { model, effort }),
|
|
655
|
+
timeoutMs,
|
|
656
|
+
unsetEnv
|
|
723
657
|
});
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return { kind: "failed", failure };
|
|
727
|
-
};
|
|
728
|
-
let result;
|
|
729
|
-
try {
|
|
730
|
-
result = await (0, process_runner_1.runInvocation)({
|
|
731
|
-
binaryPath,
|
|
732
|
-
invocation: adapter.buildInvocation(request, this.model),
|
|
733
|
-
timeoutMs,
|
|
734
|
-
unsetEnv
|
|
735
|
-
});
|
|
736
|
-
} catch (error) {
|
|
737
|
-
return failed({ kind: "fatal", message: error instanceof Error ? error.message : String(error) });
|
|
738
|
-
}
|
|
739
|
-
if (result.timedOut) {
|
|
740
|
-
return failed({ kind: "fatal", message: `${adapter.id} did not finish within ${timeoutMs}ms` });
|
|
741
|
-
}
|
|
742
|
-
const reply = adapter.readResult(result, request);
|
|
743
|
-
if (reply.kind === "failed") {
|
|
744
|
-
return failed(reply.failure);
|
|
745
|
-
}
|
|
746
|
-
logger.info("LLM call completed", {
|
|
747
|
-
type: "llm_call_complete",
|
|
748
|
-
span_id: spanId,
|
|
749
|
-
service: adapter.id,
|
|
750
|
-
method,
|
|
751
|
-
exitCode: result.exitCode,
|
|
752
|
-
usage: reply.usage,
|
|
753
|
-
duration_ms: Date.now() - startTime
|
|
754
|
-
});
|
|
755
|
-
return {
|
|
756
|
-
kind: "answered",
|
|
757
|
-
answer: reply.answer,
|
|
758
|
-
usage: reply.usage,
|
|
759
|
-
origin: {
|
|
760
|
-
engine: "local-agent",
|
|
761
|
-
agent: adapter.id,
|
|
762
|
-
model: reply.model ?? this.model,
|
|
763
|
-
costUsdEstimate: reply.costUsdEstimate
|
|
764
|
-
}
|
|
765
|
-
};
|
|
658
|
+
} catch (error) {
|
|
659
|
+
return failed({ kind: "fatal", message: error instanceof Error ? error.message : String(error) });
|
|
766
660
|
}
|
|
661
|
+
if (result.timedOut) {
|
|
662
|
+
return failed({ kind: "fatal", message: `did not finish within ${timeoutMs}ms` });
|
|
663
|
+
}
|
|
664
|
+
const reply = adapter.readResult(result, request);
|
|
665
|
+
if (reply.kind === "failed") {
|
|
666
|
+
return failed(reply.failure);
|
|
667
|
+
}
|
|
668
|
+
logger.info("LLM call completed", {
|
|
669
|
+
type: "llm_call_complete",
|
|
670
|
+
span_id: spanId,
|
|
671
|
+
service: adapter.id,
|
|
672
|
+
method,
|
|
673
|
+
exitCode: result.exitCode,
|
|
674
|
+
usage: reply.usage,
|
|
675
|
+
duration_ms: Date.now() - startTime
|
|
676
|
+
});
|
|
677
|
+
return {
|
|
678
|
+
kind: "answered",
|
|
679
|
+
answer: reply.answer,
|
|
680
|
+
usage: reply.usage,
|
|
681
|
+
origin: {
|
|
682
|
+
engine: "local-agent",
|
|
683
|
+
agent: adapter.id,
|
|
684
|
+
model: reply.model ?? model,
|
|
685
|
+
costUsdEstimate: reply.costUsdEstimate
|
|
686
|
+
}
|
|
687
|
+
};
|
|
767
688
|
}
|
|
768
|
-
localAgentEngine.LocalAgentEngine = LocalAgentEngine;
|
|
769
|
-
return localAgentEngine;
|
|
770
689
|
}
|
|
771
690
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
return
|
|
691
|
+
class LocalAgent {
|
|
692
|
+
constructor(runner, call) {
|
|
693
|
+
this.runner = runner;
|
|
694
|
+
this.call = call;
|
|
695
|
+
this.agent = runner.adapter.id;
|
|
696
|
+
this.logger = runner.logger;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* The same agent with other call options, sharing this one's executable,
|
|
700
|
+
* environment, logger and limit on processes running at once. Options left
|
|
701
|
+
* out keep this agent's; a model or effort given as undefined is the CLI's
|
|
702
|
+
* default.
|
|
703
|
+
*/
|
|
704
|
+
with(options) {
|
|
705
|
+
if (options.model !== void 0 && options.model.trim() === "") {
|
|
706
|
+
throw new TypeError("model must be a non-empty string; pass undefined to use the agent's default model");
|
|
707
|
+
}
|
|
708
|
+
return new LocalAgent(this.runner, {
|
|
709
|
+
...this.call,
|
|
710
|
+
...options,
|
|
711
|
+
timeoutMs: options.timeoutMs ?? this.call.timeoutMs
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* No model choice runs the default model. An alias runs the CLI's equivalent
|
|
716
|
+
* model, if it has one. Provider models cannot run on a local agent.
|
|
717
|
+
*/
|
|
718
|
+
route(model) {
|
|
719
|
+
if (model === void 0) {
|
|
720
|
+
return this.routeTo(this.call);
|
|
721
|
+
}
|
|
722
|
+
if (!("alias" in model)) {
|
|
723
|
+
return {
|
|
724
|
+
kind: "unroutable",
|
|
725
|
+
message: `${this.agent} takes model aliases, not provider models. Set the agent's own model with localAgent({ model }) or with().`
|
|
726
|
+
};
|
|
805
727
|
}
|
|
806
|
-
|
|
807
|
-
|
|
728
|
+
const agentModel = this.runner.adapter.modelAliases[model.alias];
|
|
729
|
+
if (agentModel === void 0) {
|
|
730
|
+
return { kind: "unroutable", message: `Model alias "${model.alias}" has no ${this.agent} equivalent` };
|
|
808
731
|
}
|
|
732
|
+
return this.routeTo({ ...this.call, model: agentModel });
|
|
733
|
+
}
|
|
734
|
+
routeTo(call) {
|
|
735
|
+
return { kind: "routed", engine: new LocalAgentEngine(this.runner, call) };
|
|
809
736
|
}
|
|
810
|
-
localAgent.LocalAgent = LocalAgent;
|
|
811
|
-
return localAgent;
|
|
812
737
|
}
|
|
813
738
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
this.
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
739
|
+
class Semaphore {
|
|
740
|
+
constructor(limit) {
|
|
741
|
+
this.limit = limit;
|
|
742
|
+
this.active = 0;
|
|
743
|
+
this.waiting = [];
|
|
744
|
+
}
|
|
745
|
+
async run(task) {
|
|
746
|
+
if (this.active >= this.limit) {
|
|
747
|
+
await new Promise((resolve) => this.waiting.push(resolve));
|
|
748
|
+
} else {
|
|
749
|
+
this.active++;
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
return await task();
|
|
753
|
+
} finally {
|
|
754
|
+
const next = this.waiting.shift();
|
|
755
|
+
if (next) {
|
|
756
|
+
next();
|
|
831
757
|
} else {
|
|
832
|
-
this.active
|
|
833
|
-
}
|
|
834
|
-
try {
|
|
835
|
-
return await task();
|
|
836
|
-
} finally {
|
|
837
|
-
const next = this.waiting.shift();
|
|
838
|
-
if (next) {
|
|
839
|
-
next();
|
|
840
|
-
} else {
|
|
841
|
-
this.active--;
|
|
842
|
-
}
|
|
758
|
+
this.active--;
|
|
843
759
|
}
|
|
844
760
|
}
|
|
845
761
|
}
|
|
846
|
-
semaphore.Semaphore = Semaphore;
|
|
847
|
-
return semaphore;
|
|
848
762
|
}
|
|
849
763
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
createLocalAgent.localAgent = localAgent;
|
|
856
|
-
createLocalAgent.findLocalAgent = findLocalAgent;
|
|
857
|
-
const logger_1 = strictJsonSchema.requireLogger();
|
|
858
|
-
const adapters_1 = requireAdapters();
|
|
859
|
-
const agent_id_1 = requireAgentId();
|
|
860
|
-
const detect_1 = requireDetect();
|
|
861
|
-
const local_agent_1 = requireLocalAgent$1();
|
|
862
|
-
const process_runner_1 = requireProcessRunner();
|
|
863
|
-
const semaphore_1 = requireSemaphore();
|
|
864
|
-
const DEFAULT_TIMEOUT_MS = 18e4;
|
|
865
|
-
const DEFAULT_CONCURRENCY = 2;
|
|
866
|
-
function localAgent(config) {
|
|
867
|
-
if (!(0, agent_id_1.isLocalAgentId)(config.agent)) {
|
|
868
|
-
throw new TypeError(`Unknown local agent: ${String(config.agent)}`);
|
|
869
|
-
}
|
|
870
|
-
const settings = parseOptions(config);
|
|
871
|
-
const adapter = adapters_1.AGENT_ADAPTERS[config.agent];
|
|
872
|
-
const binaryPath = config.binaryPath ?? (0, process_runner_1.findOnPath)(adapter.binaryName);
|
|
873
|
-
if (!binaryPath) {
|
|
874
|
-
throw new Error(`${adapter.binaryName} was not found on PATH. Install ${config.agent} or set binaryPath.`);
|
|
875
|
-
}
|
|
876
|
-
return setUpLocalAgent(adapter, binaryPath, settings);
|
|
877
|
-
}
|
|
878
|
-
async function findLocalAgent(options = {}) {
|
|
879
|
-
const settings = parseOptions(options);
|
|
880
|
-
const statuses = await (0, detect_1.detectLocalAgents)();
|
|
881
|
-
for (const status of statuses) {
|
|
882
|
-
if (status.installed && status.loggedIn) {
|
|
883
|
-
return setUpLocalAgent(adapters_1.AGENT_ADAPTERS[status.agent], status.binaryPath, settings);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
const summary = statuses.map((s) => `${s.agent}: ${s.installed ? "not logged in" : "not installed"}`).join(", ");
|
|
887
|
-
throw new Error(`No logged-in coding agent found (${summary})`);
|
|
764
|
+
const DEFAULT_TIMEOUT_MS = 18e4;
|
|
765
|
+
const DEFAULT_CONCURRENCY = 2;
|
|
766
|
+
function localAgent(config) {
|
|
767
|
+
if (!isLocalAgentId(config.agent)) {
|
|
768
|
+
throw new TypeError(`Unknown local agent: ${String(config.agent)}`);
|
|
888
769
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
if (options.model !== void 0 && options.model.trim() === "") {
|
|
895
|
-
throw new TypeError("model must be a non-empty string; omit it to use the agent's default model");
|
|
896
|
-
}
|
|
897
|
-
return {
|
|
898
|
-
model: options.model,
|
|
899
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
900
|
-
concurrency,
|
|
901
|
-
auth: options.auth ?? "subscription",
|
|
902
|
-
logger: options.logger ?? new logger_1.ConsoleLogger()
|
|
903
|
-
};
|
|
770
|
+
const settings = parseOptions(config);
|
|
771
|
+
const adapter = AGENT_ADAPTERS[config.agent];
|
|
772
|
+
const binaryPath = config.binaryPath ?? findOnPath(adapter.binaryName);
|
|
773
|
+
if (!binaryPath) {
|
|
774
|
+
throw new Error(`${adapter.binaryName} was not found on PATH. Install ${config.agent} or set binaryPath.`);
|
|
904
775
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
776
|
+
return setUpLocalAgent(adapter, binaryPath, settings);
|
|
777
|
+
}
|
|
778
|
+
async function findLocalAgent(options = {}) {
|
|
779
|
+
const settings = parseOptions(options);
|
|
780
|
+
const statuses = await detectLocalAgents();
|
|
781
|
+
for (const status of statuses) {
|
|
782
|
+
if (status.installed && status.loggedIn) {
|
|
783
|
+
return setUpLocalAgent(AGENT_ADAPTERS[status.agent], status.binaryPath, settings);
|
|
908
784
|
}
|
|
909
|
-
const runner = {
|
|
910
|
-
adapter,
|
|
911
|
-
binaryPath,
|
|
912
|
-
timeoutMs: settings.timeoutMs,
|
|
913
|
-
unsetEnv: settings.auth === "subscription" ? adapter.apiKeyEnvVars : [],
|
|
914
|
-
limiter: new semaphore_1.Semaphore(settings.concurrency),
|
|
915
|
-
logger: settings.logger
|
|
916
|
-
};
|
|
917
|
-
return new local_agent_1.LocalAgent(runner, settings.model);
|
|
918
785
|
}
|
|
919
|
-
|
|
786
|
+
const summary = statuses.map((s) => `${s.agent}: ${s.installed ? "not logged in" : "not installed"}`).join(", ");
|
|
787
|
+
throw new Error(`No logged-in coding agent found (${summary})`);
|
|
920
788
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
789
|
+
function parseOptions(options) {
|
|
790
|
+
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
791
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
792
|
+
throw new TypeError("concurrency must be a positive integer");
|
|
793
|
+
}
|
|
794
|
+
if (options.model !== void 0 && options.model.trim() === "") {
|
|
795
|
+
throw new TypeError("model must be a non-empty string; omit it to use the agent's default model");
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
model: options.model,
|
|
799
|
+
effort: options.effort,
|
|
800
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
801
|
+
concurrency,
|
|
802
|
+
auth: options.auth ?? "subscription",
|
|
803
|
+
logger: options.logger ?? new strictJsonSchema.ConsoleLogger()
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function setUpLocalAgent(adapter, binaryPath, settings) {
|
|
807
|
+
if (/\.(cmd|bat)$/i.test(binaryPath)) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`${binaryPath} is a .cmd shim, which local agents cannot run safely. Set binaryPath to the agent's native executable.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const runner = {
|
|
813
|
+
adapter,
|
|
814
|
+
binaryPath,
|
|
815
|
+
unsetEnv: settings.auth === "subscription" ? adapter.apiKeyEnvVars : [],
|
|
816
|
+
limiter: new Semaphore(settings.concurrency),
|
|
817
|
+
logger: settings.logger
|
|
818
|
+
};
|
|
819
|
+
return new LocalAgent(runner, {
|
|
820
|
+
model: settings.model,
|
|
821
|
+
effort: settings.effort,
|
|
822
|
+
timeoutMs: settings.timeoutMs
|
|
823
|
+
});
|
|
949
824
|
}
|
|
950
825
|
|
|
951
|
-
|
|
826
|
+
const LINUX_MAX_ARGUMENT_BYTES = 131072;
|
|
827
|
+
function agentSessionMode(platform = process.platform) {
|
|
828
|
+
if (platform === "win32") return "none";
|
|
829
|
+
return platform === "darwin" ? "terminal" : "command";
|
|
830
|
+
}
|
|
831
|
+
async function startAgentSession(config) {
|
|
832
|
+
if (!isLocalAgentId(config.agent)) {
|
|
833
|
+
throw new TypeError(`Unknown local agent: ${String(config.agent)}`);
|
|
834
|
+
}
|
|
835
|
+
const platform = config.platform ?? process.platform;
|
|
836
|
+
const mode = agentSessionMode(platform);
|
|
837
|
+
if (mode === "none") {
|
|
838
|
+
return refused("unsupported_platform", `Agent sessions need a POSIX shell, which ${platform} lacks.`);
|
|
839
|
+
}
|
|
840
|
+
if (platform === "linux" && Buffer.byteLength(config.prompt) >= LINUX_MAX_ARGUMENT_BYTES) {
|
|
841
|
+
return refused("prompt_too_long", `The prompt is too long to pass to ${config.agent} on ${platform}.`);
|
|
842
|
+
}
|
|
843
|
+
const adapter = AGENT_ADAPTERS[config.agent];
|
|
844
|
+
const binaryPath = config.binaryPath ?? findOnPath(adapter.binaryName);
|
|
845
|
+
if (!binaryPath) {
|
|
846
|
+
return refused(
|
|
847
|
+
"agent_not_found",
|
|
848
|
+
`${adapter.binaryName} was not found on PATH. Install ${config.agent} or set binaryPath.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const files = writeSessionFiles(config, binaryPath, adapter.sessionArgs(config.model, config.approval ?? "ask"));
|
|
852
|
+
if (mode === "command") return { kind: "command", files };
|
|
853
|
+
try {
|
|
854
|
+
await node_util.promisify(node_child_process.execFile)("open", [files.launcherPath]);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
return { kind: "open_failed", files, error: error instanceof Error ? error.message : String(error) };
|
|
857
|
+
}
|
|
858
|
+
return { kind: "opened", files };
|
|
859
|
+
}
|
|
860
|
+
function refused(reason, message) {
|
|
861
|
+
return { kind: "refused", reason, message };
|
|
862
|
+
}
|
|
863
|
+
function writeSessionFiles(config, binaryPath, agentArgs) {
|
|
864
|
+
const name = config.name ?? defaultSessionName(config.agent);
|
|
865
|
+
fs__namespace.mkdirSync(config.dir, { recursive: true, mode: 448 });
|
|
866
|
+
const promptPath = path__namespace.join(config.dir, `${name}.md`);
|
|
867
|
+
const launcherPath = path__namespace.join(config.dir, `${name}.command`);
|
|
868
|
+
fs__namespace.writeFileSync(promptPath, config.prompt, { mode: 384, flag: "wx" });
|
|
869
|
+
fs__namespace.writeFileSync(launcherPath, sessionLauncher({ cwd: config.cwd, binaryPath, agentArgs, promptPath }), {
|
|
870
|
+
mode: 448,
|
|
871
|
+
flag: "wx"
|
|
872
|
+
});
|
|
873
|
+
return { promptPath, launcherPath, command: `sh ${shellQuote(launcherPath)}` };
|
|
874
|
+
}
|
|
875
|
+
function defaultSessionName(agent) {
|
|
876
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(":", "-");
|
|
877
|
+
return `${stamp}-${agent}-${node_crypto.randomBytes(2).toString("hex")}`;
|
|
878
|
+
}
|
|
879
|
+
function sessionLauncher({ cwd, binaryPath, agentArgs, promptPath }) {
|
|
880
|
+
const command = [binaryPath, ...agentArgs].map(shellQuote).join(" ");
|
|
881
|
+
return [
|
|
882
|
+
"#!/bin/sh",
|
|
883
|
+
`cd ${shellQuote(cwd)} || exit 1`,
|
|
884
|
+
`exec ${command} -- "$(cat ${shellQuote(promptPath)})"`,
|
|
885
|
+
""
|
|
886
|
+
].join("\n");
|
|
887
|
+
}
|
|
888
|
+
function shellQuote(value) {
|
|
889
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
890
|
+
}
|
|
952
891
|
|
|
953
|
-
exports.LOCAL_AGENT_IDS =
|
|
954
|
-
exports.
|
|
955
|
-
exports.
|
|
956
|
-
exports.
|
|
957
|
-
exports.
|
|
892
|
+
exports.LOCAL_AGENT_IDS = LOCAL_AGENT_IDS;
|
|
893
|
+
exports.agentSessionMode = agentSessionMode;
|
|
894
|
+
exports.detectLocalAgents = detectLocalAgents;
|
|
895
|
+
exports.findLocalAgent = findLocalAgent;
|
|
896
|
+
exports.isLocalAgentId = isLocalAgentId;
|
|
897
|
+
exports.localAgent = localAgent;
|
|
898
|
+
exports.sessionLauncher = sessionLauncher;
|
|
899
|
+
exports.startAgentSession = startAgentSession;
|