@the-open-engine/zeroshot 6.4.0 → 6.5.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.
Files changed (144) hide show
  1. package/cli/commands/providers.js +11 -5
  2. package/cli/index.js +79 -16
  3. package/cli/lib/first-run.js +14 -3
  4. package/docker/zeroshot-cluster/Dockerfile +11 -1
  5. package/lib/agent-cli-provider/acp-stdio-runner.d.ts +4 -0
  6. package/lib/agent-cli-provider/acp-stdio-runner.d.ts.map +1 -0
  7. package/lib/agent-cli-provider/acp-stdio-runner.js +259 -0
  8. package/lib/agent-cli-provider/acp-stdio-runner.js.map +1 -0
  9. package/lib/agent-cli-provider/adapters/acp.d.ts +23 -0
  10. package/lib/agent-cli-provider/adapters/acp.d.ts.map +1 -0
  11. package/lib/agent-cli-provider/adapters/acp.js +373 -0
  12. package/lib/agent-cli-provider/adapters/acp.js.map +1 -0
  13. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
  14. package/lib/agent-cli-provider/adapters/claude.js +2 -29
  15. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  16. package/lib/agent-cli-provider/adapters/copilot-parser.d.ts +3 -0
  17. package/lib/agent-cli-provider/adapters/copilot-parser.d.ts.map +1 -0
  18. package/lib/agent-cli-provider/adapters/copilot-parser.js +154 -0
  19. package/lib/agent-cli-provider/adapters/copilot-parser.js.map +1 -0
  20. package/lib/agent-cli-provider/adapters/copilot.d.ts +3 -0
  21. package/lib/agent-cli-provider/adapters/copilot.d.ts.map +1 -0
  22. package/lib/agent-cli-provider/adapters/copilot.js +176 -0
  23. package/lib/agent-cli-provider/adapters/copilot.js.map +1 -0
  24. package/lib/agent-cli-provider/adapters/gateway.d.ts +6 -0
  25. package/lib/agent-cli-provider/adapters/gateway.d.ts.map +1 -0
  26. package/lib/agent-cli-provider/adapters/gateway.js +161 -0
  27. package/lib/agent-cli-provider/adapters/gateway.js.map +1 -0
  28. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
  29. package/lib/agent-cli-provider/adapters/gemini.js +9 -2
  30. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
  31. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/adapters/index.js +6 -34
  33. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  34. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  35. package/lib/agent-cli-provider/adapters/opencode.js +5 -1
  36. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  37. package/lib/agent-cli-provider/adapters/pi.d.ts +3 -0
  38. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -0
  39. package/lib/agent-cli-provider/adapters/pi.js +331 -0
  40. package/lib/agent-cli-provider/adapters/pi.js.map +1 -0
  41. package/lib/agent-cli-provider/claude-command.d.ts +6 -0
  42. package/lib/agent-cli-provider/claude-command.d.ts.map +1 -0
  43. package/lib/agent-cli-provider/claude-command.js +43 -0
  44. package/lib/agent-cli-provider/claude-command.js.map +1 -0
  45. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
  46. package/lib/agent-cli-provider/contract-actions.js +13 -17
  47. package/lib/agent-cli-provider/contract-actions.js.map +1 -1
  48. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
  49. package/lib/agent-cli-provider/contract-invoke.js +10 -3
  50. package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
  51. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
  52. package/lib/agent-cli-provider/contract-options.js +57 -2
  53. package/lib/agent-cli-provider/contract-options.js.map +1 -1
  54. package/lib/agent-cli-provider/contract-support.d.ts +1 -0
  55. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -1
  56. package/lib/agent-cli-provider/contract-support.js +25 -3
  57. package/lib/agent-cli-provider/contract-support.js.map +1 -1
  58. package/lib/agent-cli-provider/errors.d.ts +1 -0
  59. package/lib/agent-cli-provider/errors.d.ts.map +1 -1
  60. package/lib/agent-cli-provider/errors.js +14 -5
  61. package/lib/agent-cli-provider/errors.js.map +1 -1
  62. package/lib/agent-cli-provider/gateway-client.d.ts +32 -0
  63. package/lib/agent-cli-provider/gateway-client.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/gateway-client.js +135 -0
  65. package/lib/agent-cli-provider/gateway-client.js.map +1 -0
  66. package/lib/agent-cli-provider/gateway-runner.d.ts +9 -0
  67. package/lib/agent-cli-provider/gateway-runner.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/gateway-runner.js +294 -0
  69. package/lib/agent-cli-provider/gateway-runner.js.map +1 -0
  70. package/lib/agent-cli-provider/gateway-tools.d.ts +14 -0
  71. package/lib/agent-cli-provider/gateway-tools.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/gateway-tools.js +495 -0
  73. package/lib/agent-cli-provider/gateway-tools.js.map +1 -0
  74. package/lib/agent-cli-provider/index.d.ts +3 -2
  75. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  76. package/lib/agent-cli-provider/index.js +14 -1
  77. package/lib/agent-cli-provider/index.js.map +1 -1
  78. package/lib/agent-cli-provider/invoke-evidence.d.ts +2 -2
  79. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  80. package/lib/agent-cli-provider/invoke-evidence.js +17 -3
  81. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  82. package/lib/agent-cli-provider/provider-registry.d.ts +437 -0
  83. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/provider-registry.js +411 -0
  85. package/lib/agent-cli-provider/provider-registry.js.map +1 -0
  86. package/lib/agent-cli-provider/single-agent-runtime.d.ts +7 -0
  87. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  88. package/lib/agent-cli-provider/single-agent-runtime.js +139 -19
  89. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  90. package/lib/agent-cli-provider/types.d.ts +106 -4
  91. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  92. package/lib/agent-cli-provider/types.js.map +1 -1
  93. package/lib/docker-config.js +24 -13
  94. package/lib/provider-defaults.js +15 -4
  95. package/lib/provider-detection.js +2 -0
  96. package/lib/provider-names.js +32 -12
  97. package/lib/repo-settings.js +15 -1
  98. package/lib/settings/claude-auth.js +3 -6
  99. package/lib/settings.js +17 -2
  100. package/lib/setup-apply.js +300 -0
  101. package/lib/setup-journal.js +109 -0
  102. package/lib/setup-plan.js +406 -0
  103. package/lib/setup-undo.js +88 -0
  104. package/lib/start-cluster.js +12 -1
  105. package/lib/stream-json-parser.js +7 -5
  106. package/package.json +29 -1
  107. package/scripts/live-provider-smoke.js +221 -0
  108. package/src/agent/agent-lifecycle.js +5 -4
  109. package/src/agent/agent-quality-gate-schema.js +2 -2
  110. package/src/agent/agent-task-executor.js +41 -2
  111. package/src/agent-cli-provider/acp-stdio-runner.ts +314 -0
  112. package/src/agent-cli-provider/adapters/acp.ts +493 -0
  113. package/src/agent-cli-provider/adapters/claude.ts +2 -32
  114. package/src/agent-cli-provider/adapters/copilot-parser.ts +166 -0
  115. package/src/agent-cli-provider/adapters/copilot.ts +231 -0
  116. package/src/agent-cli-provider/adapters/gateway.ts +186 -0
  117. package/src/agent-cli-provider/adapters/gemini.ts +9 -2
  118. package/src/agent-cli-provider/adapters/index.ts +5 -36
  119. package/src/agent-cli-provider/adapters/opencode.ts +4 -1
  120. package/src/agent-cli-provider/adapters/pi.ts +410 -0
  121. package/src/agent-cli-provider/claude-command.ts +47 -0
  122. package/src/agent-cli-provider/contract-actions.ts +14 -17
  123. package/src/agent-cli-provider/contract-invoke.ts +12 -3
  124. package/src/agent-cli-provider/contract-options.ts +72 -3
  125. package/src/agent-cli-provider/contract-support.ts +30 -3
  126. package/src/agent-cli-provider/errors.ts +14 -4
  127. package/src/agent-cli-provider/gateway-client.ts +170 -0
  128. package/src/agent-cli-provider/gateway-runner.ts +353 -0
  129. package/src/agent-cli-provider/gateway-tools.ts +616 -0
  130. package/src/agent-cli-provider/index.ts +28 -0
  131. package/src/agent-cli-provider/invoke-evidence.ts +24 -3
  132. package/src/agent-cli-provider/provider-registry.ts +515 -0
  133. package/src/agent-cli-provider/single-agent-runtime.ts +192 -18
  134. package/src/agent-cli-provider/types.ts +145 -4
  135. package/src/config-validator.js +33 -15
  136. package/src/isolation-manager.js +124 -19
  137. package/src/orchestrator.js +23 -10
  138. package/src/preflight.js +83 -30
  139. package/src/providers/capabilities.js +15 -42
  140. package/src/providers/index.js +52 -72
  141. package/src/worktree-claude-config.js +17 -0
  142. package/task-lib/commands/run.js +1 -0
  143. package/task-lib/provider-helper-runtime.js +11 -0
  144. package/task-lib/runner.js +7 -0
@@ -0,0 +1,493 @@
1
+ import { appendJsonSchemaPrompt } from '../schema';
2
+ import { contractError } from '../contract-errors';
3
+ import {
4
+ getArray,
5
+ getBoolean,
6
+ getNumber,
7
+ getOptionalString,
8
+ getRecord,
9
+ getString,
10
+ isRecord,
11
+ tryParseJson,
12
+ } from '../json';
13
+ import {
14
+ type AcpCliFeatures,
15
+ type BuildProviderCommandOptions,
16
+ type CommandSpec,
17
+ type ErrorClassification,
18
+ type LevelModelSpec,
19
+ type LevelOverrides,
20
+ type ModelCatalogEntry,
21
+ type ModelLevel,
22
+ type OutputEvent,
23
+ type ProviderAdapter,
24
+ type ProviderId,
25
+ type ProviderParseResult,
26
+ type ProviderParserState,
27
+ type ResolvedModelSpec,
28
+ type WarningMetadata,
29
+ } from '../types';
30
+ import {
31
+ classifyBaseProviderError,
32
+ commandSpec,
33
+ createParserState,
34
+ optionFeatures,
35
+ resolveModelSpecWithConfig,
36
+ validateModelIdFromCatalog,
37
+ warning,
38
+ } from './common';
39
+
40
+ export interface AcpAdapterMetadata {
41
+ readonly provider: ProviderId;
42
+ readonly displayName: string;
43
+ readonly binary: string;
44
+ readonly commandArgs: readonly string[];
45
+ readonly credentialEnvKeys: readonly string[];
46
+ readonly modelCatalog?: Readonly<Record<string, ModelCatalogEntry>>;
47
+ readonly levelMapping?: Readonly<Record<ModelLevel, LevelModelSpec>>;
48
+ readonly defaultLevel?: ModelLevel;
49
+ readonly defaultMaxLevel?: ModelLevel;
50
+ readonly defaultMinLevel?: ModelLevel;
51
+ readonly supportsPromptImages: boolean;
52
+ readonly supportsLoadSession: boolean;
53
+ readonly supportsSessionCancel: boolean;
54
+ readonly supportsSessionSetModel: boolean;
55
+ readonly supportsSessionSetMode: boolean;
56
+ readonly retryableErrorPatterns?: readonly RegExp[];
57
+ readonly permanentErrorPatterns?: readonly RegExp[];
58
+ }
59
+
60
+ const DEFAULT_MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
61
+ const DEFAULT_LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
62
+ level1: { rank: 1, model: null },
63
+ level2: { rank: 2, model: null },
64
+ level3: { rank: 3, model: null },
65
+ };
66
+
67
+ function detectCliFeatures(
68
+ meta: AcpAdapterMetadata,
69
+ helpText?: string | null
70
+ ): AcpCliFeatures {
71
+ const help = helpText ?? '';
72
+ const unknown = !help;
73
+ return {
74
+ provider: meta.provider,
75
+ supportsAcpStdio: unknown ? true : /\bacp\b/.test(help),
76
+ supportsPromptImages: meta.supportsPromptImages,
77
+ supportsLoadSession: meta.supportsLoadSession,
78
+ supportsSessionCancel: meta.supportsSessionCancel,
79
+ supportsSessionSetModel: meta.supportsSessionSetModel,
80
+ supportsSessionSetMode: meta.supportsSessionSetMode,
81
+ supportsRemoteTransport: false,
82
+ supportsCustomTransport: false,
83
+ supportsPermissionRequests: false,
84
+ supportsFsTools: false,
85
+ supportsTerminalTools: false,
86
+ unknown,
87
+ };
88
+ }
89
+
90
+ function failClosedUnsupportedSessionControl(
91
+ provider: ProviderId,
92
+ options: BuildProviderCommandOptions
93
+ ): void {
94
+ const hasResumeSessionId = options.resumeSessionId !== undefined;
95
+ if (!hasResumeSessionId && !options.continueSession) return;
96
+ const field = hasResumeSessionId ? 'options.resumeSessionId' : 'options.continueSession';
97
+ throw contractError({
98
+ code: 'invalid-field',
99
+ field,
100
+ exitCode: 2,
101
+ message: `${provider} ACP stdio adapter only supports fresh headless sessions; resume/continue is capability-gated off.`,
102
+ });
103
+ }
104
+
105
+ function failClosedUnsupportedAcpStdio(
106
+ meta: AcpAdapterMetadata,
107
+ options: BuildProviderCommandOptions
108
+ ): void {
109
+ if (optionFeatures(options).supportsAcpStdio !== false) return;
110
+ throw contractError({
111
+ code: 'invalid-field',
112
+ field: 'options.cliFeatures.supportsAcpStdio',
113
+ exitCode: 2,
114
+ message: `${meta.displayName} CLI does not advertise ACP stdio support; fail closed instead of launching the unsupported ACP lane.`,
115
+ });
116
+ }
117
+
118
+ function collectWarnings(
119
+ meta: AcpAdapterMetadata,
120
+ options: BuildProviderCommandOptions
121
+ ): WarningMetadata[] {
122
+ const warnings: WarningMetadata[] = [];
123
+ if (options.autoApprove) {
124
+ warnings.push(
125
+ warning(
126
+ meta.provider,
127
+ 'acp-auto-approve',
128
+ `${meta.displayName} ACP stdio does not support interactive approval callbacks; failing closed if the agent requests one.`
129
+ )
130
+ );
131
+ }
132
+ if (options.jsonSchema) {
133
+ warnings.push(
134
+ warning(
135
+ meta.provider,
136
+ 'acp-jsonschema',
137
+ `${meta.displayName} ACP stdio has no provider-native JSON schema lane; schema instructions are appended to the prompt.`
138
+ )
139
+ );
140
+ }
141
+ return warnings;
142
+ }
143
+
144
+ function buildCommand(
145
+ meta: AcpAdapterMetadata,
146
+ _context: string,
147
+ options: BuildProviderCommandOptions = {}
148
+ ): CommandSpec {
149
+ failClosedUnsupportedAcpStdio(meta, options);
150
+ failClosedUnsupportedSessionControl(meta.provider, options);
151
+ return commandSpec({
152
+ binary: meta.binary,
153
+ args: [...meta.commandArgs],
154
+ env: {},
155
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
156
+ warnings: collectWarnings(meta, options),
157
+ });
158
+ }
159
+
160
+ export function buildAcpPrompt(context: string, options: BuildProviderCommandOptions = {}): string {
161
+ return options.jsonSchema ? appendJsonSchemaPrompt(context, options.jsonSchema) : context;
162
+ }
163
+
164
+ function createAcpState(provider: ProviderId): ProviderParserState {
165
+ return {
166
+ ...createParserState(provider),
167
+ lastAssistantText: '',
168
+ lastAssistantThinking: '',
169
+ assistantTextByMessageId: new Map(),
170
+ assistantThinkingByMessageId: new Map(),
171
+ toolCalls: new Map(),
172
+ usage: null,
173
+ };
174
+ }
175
+
176
+ function textSnapshots(state: ProviderParserState): Map<string, string> {
177
+ if (!state.assistantTextByMessageId) state.assistantTextByMessageId = new Map();
178
+ return state.assistantTextByMessageId;
179
+ }
180
+
181
+ function thinkingSnapshots(state: ProviderParserState): Map<string, string> {
182
+ if (!state.assistantThinkingByMessageId) state.assistantThinkingByMessageId = new Map();
183
+ return state.assistantThinkingByMessageId;
184
+ }
185
+
186
+ function toolCalls(state: ProviderParserState): Map<string, { name: string | null | undefined; input: unknown }> {
187
+ if (!state.toolCalls) state.toolCalls = new Map();
188
+ return state.toolCalls;
189
+ }
190
+
191
+ function snapshotDelta(previous: string, current: string): string | null {
192
+ if (!current || previous === current) return null;
193
+ if (current.startsWith(previous)) return current.slice(previous.length) || null;
194
+ return current;
195
+ }
196
+
197
+ function resolveChunkSnapshot(previous: string, chunk: string): string {
198
+ if (!previous) return chunk;
199
+ if (!chunk || previous === chunk) return previous;
200
+ if (chunk.startsWith(previous)) return chunk;
201
+ if (previous.startsWith(chunk)) return chunk;
202
+ return previous + chunk;
203
+ }
204
+
205
+ function joinTextBlocks(content: unknown, allowedTypes: readonly string[]): string | null {
206
+ if (typeof content === 'string') return content;
207
+ const blocks = Array.isArray(content) ? content : [content];
208
+ let value = '';
209
+ let matched = false;
210
+ for (const block of blocks) {
211
+ if (!isRecord(block)) continue;
212
+ const type = getString(block, 'type');
213
+ if (type !== null && !allowedTypes.includes(type)) continue;
214
+ const text = getOptionalString(block, 'text') ?? getOptionalString(block, 'thinking');
215
+ if (text === null) continue;
216
+ value += text;
217
+ matched = true;
218
+ }
219
+ return matched ? value : null;
220
+ }
221
+
222
+ function normalizeToolInput(input: unknown): unknown {
223
+ if (typeof input !== 'string') return input ?? {};
224
+ const parsed = tryParseJson(input);
225
+ return parsed ?? input;
226
+ }
227
+
228
+ function normalizeToolContent(content: unknown): unknown {
229
+ if (typeof content === 'string') return content;
230
+ if (!Array.isArray(content)) return content;
231
+ const text = content
232
+ .map((item) => (isRecord(item) ? getString(item, 'text') : null))
233
+ .filter((value): value is string => typeof value === 'string')
234
+ .join('');
235
+ return text || content;
236
+ }
237
+
238
+ function updateUsage(state: ProviderParserState, update: Record<string, unknown>): void {
239
+ const usage = getRecord(update, 'usage') ?? update;
240
+ const inputTokens = getNumber(usage, 'inputTokens');
241
+ const outputTokens = getNumber(usage, 'outputTokens');
242
+ const cacheReadInputTokens = getNumber(usage, 'cacheReadInputTokens');
243
+ const cacheCreationInputTokens = getNumber(usage, 'cacheCreationInputTokens');
244
+ if (
245
+ inputTokens === null &&
246
+ outputTokens === null &&
247
+ cacheReadInputTokens === null &&
248
+ cacheCreationInputTokens === null
249
+ ) {
250
+ return;
251
+ }
252
+ state.usage = {
253
+ ...(inputTokens === null ? {} : { inputTokens }),
254
+ ...(outputTokens === null ? {} : { outputTokens }),
255
+ ...(cacheReadInputTokens === null ? {} : { cacheReadInputTokens }),
256
+ ...(cacheCreationInputTokens === null ? {} : { cacheCreationInputTokens }),
257
+ };
258
+ }
259
+
260
+ function parseAssistantChunk(
261
+ update: Record<string, unknown>,
262
+ state: ProviderParserState,
263
+ options: {
264
+ readonly textTypes?: readonly string[];
265
+ readonly thinkingTypes?: readonly string[];
266
+ }
267
+ ): readonly OutputEvent[] {
268
+ const messageId = getOptionalString(update, 'messageId') ?? 'assistant';
269
+ const content = update.content ?? getArray(update, 'content');
270
+ const textByMessageId = textSnapshots(state);
271
+ const thinkingByMessageId = thinkingSnapshots(state);
272
+ const nextText = options.textTypes ? joinTextBlocks(content, options.textTypes) : null;
273
+ const nextThinking = options.thinkingTypes ? joinTextBlocks(content, options.thinkingTypes) : null;
274
+
275
+ const events: OutputEvent[] = [];
276
+ if (nextText !== null) {
277
+ const previousText = textByMessageId.get(messageId) ?? '';
278
+ const currentText = resolveChunkSnapshot(previousText, nextText);
279
+ const textDelta = snapshotDelta(previousText, currentText);
280
+ textByMessageId.set(messageId, currentText);
281
+ if (currentText) state.lastAssistantText = currentText;
282
+ if (textDelta) events.push({ type: 'text', text: textDelta });
283
+ }
284
+ if (nextThinking !== null) {
285
+ const previousThinking = thinkingByMessageId.get(messageId) ?? '';
286
+ const currentThinking = resolveChunkSnapshot(previousThinking, nextThinking);
287
+ const thinkingDelta = snapshotDelta(previousThinking, currentThinking);
288
+ thinkingByMessageId.set(messageId, currentThinking);
289
+ if (currentThinking) state.lastAssistantThinking = currentThinking;
290
+ if (thinkingDelta) events.push({ type: 'thinking', text: thinkingDelta });
291
+ }
292
+ return events;
293
+ }
294
+
295
+ function parseToolCall(
296
+ update: Record<string, unknown>,
297
+ state: ProviderParserState
298
+ ): OutputEvent | null {
299
+ const toolId =
300
+ getOptionalString(update, 'toolCallId') ??
301
+ getOptionalString(update, 'toolId') ??
302
+ getOptionalString(update, 'id');
303
+ if (!toolId) return null;
304
+ const toolName =
305
+ getOptionalString(update, 'title') ??
306
+ getOptionalString(update, 'toolName') ??
307
+ getOptionalString(update, 'name');
308
+ const input = normalizeToolInput(update.rawInput ?? update.input ?? update.arguments ?? {});
309
+ state.lastToolId = toolId;
310
+ toolCalls(state).set(toolId, { name: toolName, input });
311
+ return {
312
+ type: 'tool_call',
313
+ toolName,
314
+ toolId,
315
+ input,
316
+ };
317
+ }
318
+
319
+ function parseToolCallUpdate(
320
+ update: Record<string, unknown>,
321
+ state: ProviderParserState
322
+ ): OutputEvent | null {
323
+ const toolId =
324
+ getOptionalString(update, 'toolCallId') ??
325
+ getOptionalString(update, 'toolId') ??
326
+ getOptionalString(update, 'id') ??
327
+ state.lastToolId;
328
+ if (!toolId) return null;
329
+ state.lastToolId = toolId;
330
+ const content =
331
+ update.rawOutput ?? update.output ?? update.partialResult ?? update.content ?? update.result;
332
+ if (content === undefined) return null;
333
+ const status = getOptionalString(update, 'status');
334
+ const isError =
335
+ getBoolean(update, 'isError') ?? (status === 'failed' || status === 'cancelled');
336
+ return {
337
+ type: 'tool_result',
338
+ toolId,
339
+ content: normalizeToolContent(content),
340
+ isError,
341
+ };
342
+ }
343
+
344
+ function resultFromStopReason(
345
+ state: ProviderParserState,
346
+ response: Record<string, unknown>
347
+ ): OutputEvent {
348
+ updateUsage(state, response);
349
+ const stopReason =
350
+ getOptionalString(response, 'stopReason') ?? getOptionalString(response, 'stop_reason');
351
+ const explicitError =
352
+ getOptionalString(response, 'error') ??
353
+ getOptionalString(response, 'errorMessage') ??
354
+ getOptionalString(response, 'message');
355
+ const success = stopReason !== 'cancelled' && stopReason !== 'refusal';
356
+ const resultText =
357
+ typeof state.lastAssistantText === 'string' && state.lastAssistantText.length > 0
358
+ ? state.lastAssistantText
359
+ : null;
360
+ return {
361
+ type: 'result',
362
+ success,
363
+ result: success ? resultText : null,
364
+ error: success ? null : explicitError ?? stopReason ?? 'ACP prompt failed.',
365
+ ...(state.usage?.inputTokens === undefined ? {} : { inputTokens: state.usage.inputTokens }),
366
+ ...(state.usage?.outputTokens === undefined ? {} : { outputTokens: state.usage.outputTokens }),
367
+ ...(state.usage?.cacheReadInputTokens === undefined
368
+ ? {}
369
+ : { cacheReadInputTokens: state.usage.cacheReadInputTokens }),
370
+ ...(state.usage?.cacheCreationInputTokens === undefined
371
+ ? {}
372
+ : { cacheCreationInputTokens: state.usage.cacheCreationInputTokens }),
373
+ cost: null,
374
+ modelUsage: state.usage,
375
+ };
376
+ }
377
+
378
+ function resultFromRpcError(error: Record<string, unknown>): OutputEvent {
379
+ return {
380
+ type: 'result',
381
+ success: false,
382
+ result: null,
383
+ error: getString(error, 'message') ?? 'ACP request failed.',
384
+ };
385
+ }
386
+
387
+ function parseSessionUpdate(
388
+ params: Record<string, unknown>,
389
+ state: ProviderParserState
390
+ ): ProviderParseResult {
391
+ const update = getRecord(params, 'update') ?? params;
392
+ const sessionUpdate =
393
+ getOptionalString(update, 'sessionUpdate') ??
394
+ getOptionalString(update, 'session_update') ??
395
+ getOptionalString(update, 'type');
396
+ if (!sessionUpdate || sessionUpdate.startsWith('_')) return null;
397
+ if (sessionUpdate === 'agent_message_chunk') {
398
+ return parseAssistantChunk(update, state, {
399
+ textTypes: ['text'],
400
+ thinkingTypes: ['thinking'],
401
+ });
402
+ }
403
+ if (sessionUpdate === 'agent_thought_chunk') {
404
+ return parseAssistantChunk(update, state, {
405
+ thinkingTypes: ['thinking', 'text'],
406
+ });
407
+ }
408
+ if (sessionUpdate === 'tool_call') return parseToolCall(update, state);
409
+ if (sessionUpdate === 'tool_call_update') return parseToolCallUpdate(update, state);
410
+ if (sessionUpdate === 'usage_update') {
411
+ updateUsage(state, update);
412
+ return null;
413
+ }
414
+ return null;
415
+ }
416
+
417
+ function parseRpcObject(parsed: Record<string, unknown>, state: ProviderParserState): ProviderParseResult {
418
+ const method = getOptionalString(parsed, 'method');
419
+ if (method === 'session/update') {
420
+ const params = getRecord(parsed, 'params') ?? {};
421
+ return parseSessionUpdate(params, state);
422
+ }
423
+ if (method?.startsWith('_')) return null;
424
+ if (parsed.error && isRecord(parsed.error)) return resultFromRpcError(parsed.error);
425
+ const result = getRecord(parsed, 'result');
426
+ if (!result) return null;
427
+ if (getOptionalString(result, 'stopReason') || getOptionalString(result, 'stop_reason')) {
428
+ return resultFromStopReason(state, result);
429
+ }
430
+ return null;
431
+ }
432
+
433
+ function parseEvent(line: string, state: ProviderParserState): OutputEvent | readonly OutputEvent[] | null {
434
+ const parsed = tryParseJson(line);
435
+ if (!isRecord(parsed)) return null;
436
+ return parseRpcObject(parsed, state);
437
+ }
438
+
439
+ function resolveModelSpec(
440
+ meta: AcpAdapterMetadata,
441
+ level: ModelLevel,
442
+ overrides?: LevelOverrides
443
+ ): ResolvedModelSpec {
444
+ const mapping = meta.levelMapping ?? DEFAULT_LEVEL_MAPPING;
445
+ return resolveModelSpecWithConfig({
446
+ mapping,
447
+ defaultLevel: meta.defaultLevel ?? 'level2',
448
+ level,
449
+ overrides,
450
+ validateModelId: (modelId) => validateModelId(meta, modelId),
451
+ });
452
+ }
453
+
454
+ function validateModelId(
455
+ meta: AcpAdapterMetadata,
456
+ modelId: string | null | undefined
457
+ ): string | null | undefined {
458
+ return validateModelIdFromCatalog(
459
+ meta.provider,
460
+ meta.modelCatalog ?? DEFAULT_MODEL_CATALOG,
461
+ modelId
462
+ );
463
+ }
464
+
465
+ function classifyError(meta: AcpAdapterMetadata, error: unknown): ErrorClassification {
466
+ return classifyBaseProviderError(
467
+ error,
468
+ meta.retryableErrorPatterns ?? [],
469
+ meta.permanentErrorPatterns ?? []
470
+ );
471
+ }
472
+
473
+ export function createAcpAdapter(meta: AcpAdapterMetadata): ProviderAdapter {
474
+ return {
475
+ id: meta.provider,
476
+ displayName: meta.displayName,
477
+ binary: meta.binary,
478
+ adapterVersion: '1',
479
+ credentialEnvKeys: meta.credentialEnvKeys,
480
+ modelCatalog: meta.modelCatalog ?? DEFAULT_MODEL_CATALOG,
481
+ levelMapping: meta.levelMapping ?? DEFAULT_LEVEL_MAPPING,
482
+ defaultLevel: meta.defaultLevel ?? 'level2',
483
+ defaultMaxLevel: meta.defaultMaxLevel ?? 'level3',
484
+ defaultMinLevel: meta.defaultMinLevel ?? 'level1',
485
+ detectCliFeatures: (helpText) => detectCliFeatures(meta, helpText),
486
+ buildCommand: (context, options) => buildCommand(meta, context, options),
487
+ parseEvent,
488
+ createParserState: () => createAcpState(meta.provider),
489
+ resolveModelSpec: (level, overrides) => resolveModelSpec(meta, level, overrides),
490
+ validateModelId: (modelId) => validateModelId(meta, modelId),
491
+ classifyError: (error) => classifyError(meta, error),
492
+ };
493
+ }
@@ -1,7 +1,4 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { getString, isRecord, stringifyJson } from '../json';
1
+ import { stringifyJson } from '../json';
5
2
  import {
6
3
  type BuildProviderCommandOptions,
7
4
  type ClaudeCliFeatures,
@@ -16,6 +13,7 @@ import {
16
13
  type ResolvedModelSpec,
17
14
  type WarningMetadata,
18
15
  } from '../types';
16
+ import { resolveClaudeCommand } from '../claude-command';
19
17
  import {
20
18
  classifyBaseProviderError,
21
19
  commandSpec,
@@ -107,34 +105,6 @@ function addSessionArgs(args: string[], options: BuildProviderCommandOptions): v
107
105
  }
108
106
  }
109
107
 
110
- function readConfiguredClaudeCommand(): string {
111
- if (process.env.ZEROSHOT_CLAUDE_COMMAND?.trim()) {
112
- return process.env.ZEROSHOT_CLAUDE_COMMAND;
113
- }
114
-
115
- const settingsPath =
116
- process.env.ZEROSHOT_SETTINGS_FILE || join(homedir(), '.zeroshot', 'settings.json');
117
- if (!existsSync(settingsPath)) return 'claude';
118
-
119
- try {
120
- const settings: unknown = JSON.parse(readFileSync(settingsPath, 'utf8'));
121
- const configured = isRecord(settings) ? getString(settings, 'claudeCommand') : null;
122
- if (configured?.trim()) return configured;
123
- } catch {
124
- return 'claude';
125
- }
126
-
127
- return 'claude';
128
- }
129
-
130
- function resolveClaudeCommand(): { readonly command: string; readonly args: readonly string[] } {
131
- const parts = readConfiguredClaudeCommand().trim().split(/\s+/);
132
- return {
133
- command: parts[0] ?? 'claude',
134
- args: parts.slice(1),
135
- };
136
- }
137
-
138
108
  function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
139
109
  const features = optionFeatures(options);
140
110
  const warnings: WarningMetadata[] = [];