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