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