@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,166 @@
1
+ // Copilot `--output-format json` emits JSONL; payload under `data`, dot-namespaced `type` (verified
2
+ // v1.0.69). Unknown types are ignored (fail-open). Mapping:
3
+ // assistant.message_delta/message → text, or thinking when phase==='commentary' (final answer is
4
+ // the result text). assistant.reasoning → thinking. tool.execution_start → tool_call.
5
+ // tool.execution_complete → tool_result. result → terminal (success = top-level exitCode === 0).
6
+ import { getBoolean, getNumber, getRecord, getString, isRecord, tryParseJson } from '../json';
7
+ import type { OutputEvent, ProviderParseResult, ProviderParserState } from '../types';
8
+
9
+ const IGNORED_TYPES = new Set([
10
+ 'user.message',
11
+ 'assistant.turn_start',
12
+ 'assistant.turn_end',
13
+ 'assistant.idle',
14
+ 'assistant.tool_call_delta',
15
+ 'tool.execution_partial_result',
16
+ ]);
17
+
18
+ function messageTextMap(state: ProviderParserState): Map<string, string> {
19
+ if (!state.assistantTextByMessageId) state.assistantTextByMessageId = new Map();
20
+ return state.assistantTextByMessageId;
21
+ }
22
+
23
+ function messagePhaseMap(state: ProviderParserState): Map<string, string> {
24
+ if (!state.messagePhaseById) state.messagePhaseById = new Map();
25
+ return state.messagePhaseById;
26
+ }
27
+
28
+ // `commentary` = the model narrating its plan (thinking); anything else is user-facing output.
29
+ function isCommentaryPhase(phase: string | null | undefined): boolean {
30
+ return phase === 'commentary';
31
+ }
32
+
33
+ function snapshotDelta(previous: string, current: string): string | null {
34
+ if (!current) return null;
35
+ if (previous === current) return null;
36
+ if (current.startsWith(previous)) return current.slice(previous.length) || null;
37
+ return current;
38
+ }
39
+
40
+ function accrueOutputTokens(state: ProviderParserState, data: Record<string, unknown>): void {
41
+ const tokens = getNumber(data, 'outputTokens');
42
+ if (tokens === null) return;
43
+ const usage = state.usage ?? {};
44
+ state.usage = { ...usage, outputTokens: (usage.outputTokens ?? 0) + tokens };
45
+ }
46
+
47
+ function parseMessageStart(data: Record<string, unknown>, state: ProviderParserState): null {
48
+ const messageId = getString(data, 'messageId');
49
+ if (messageId === null) return null;
50
+ messageTextMap(state).set(messageId, '');
51
+ const phase = getString(data, 'phase');
52
+ if (phase !== null) messagePhaseMap(state).set(messageId, phase);
53
+ return null;
54
+ }
55
+
56
+ function parseMessageDelta(
57
+ data: Record<string, unknown>,
58
+ state: ProviderParserState
59
+ ): OutputEvent | null {
60
+ const delta = getString(data, 'deltaContent');
61
+ if (!delta) return null;
62
+ const messageId = getString(data, 'messageId') ?? '';
63
+ const map = messageTextMap(state);
64
+ map.set(messageId, (map.get(messageId) ?? '') + delta);
65
+ const commentary = isCommentaryPhase(messagePhaseMap(state).get(messageId));
66
+ return commentary ? { type: 'thinking', text: delta } : { type: 'text', text: delta };
67
+ }
68
+
69
+ function parseMessage(
70
+ data: Record<string, unknown>,
71
+ state: ProviderParserState
72
+ ): OutputEvent | null {
73
+ const content = getString(data, 'content') ?? '';
74
+ accrueOutputTokens(state, data);
75
+ const messageId = getString(data, 'messageId') ?? '';
76
+ const map = messageTextMap(state);
77
+ const delta = snapshotDelta(map.get(messageId) ?? '', content);
78
+ map.set(messageId, content);
79
+ const commentary = isCommentaryPhase(getString(data, 'phase') ?? messagePhaseMap(state).get(messageId));
80
+ // Only the final answer (not commentary narration) is the run's result text.
81
+ if (content && !commentary) state.lastAssistantText = content;
82
+ if (!delta) return null;
83
+ return commentary ? { type: 'thinking', text: delta } : { type: 'text', text: delta };
84
+ }
85
+
86
+ function parseReasoning(data: Record<string, unknown>): OutputEvent | null {
87
+ const text = getString(data, 'content');
88
+ return text ? { type: 'thinking', text } : null;
89
+ }
90
+
91
+ function parseToolExecutionStart(
92
+ data: Record<string, unknown>,
93
+ state: ProviderParserState
94
+ ): OutputEvent {
95
+ const toolId = getString(data, 'toolCallId');
96
+ state.lastToolId = toolId;
97
+ return {
98
+ type: 'tool_call',
99
+ toolName: getString(data, 'toolName'),
100
+ toolId,
101
+ input: Object.prototype.hasOwnProperty.call(data, 'arguments') ? data.arguments : {},
102
+ };
103
+ }
104
+
105
+ function toolResultContent(data: Record<string, unknown>): unknown {
106
+ const result = getRecord(data, 'result');
107
+ if (result !== null) return result.content ?? result.detailedContent ?? '';
108
+ return data.result ?? '';
109
+ }
110
+
111
+ function parseToolExecutionComplete(
112
+ data: Record<string, unknown>,
113
+ state: ProviderParserState
114
+ ): OutputEvent {
115
+ const success = getBoolean(data, 'success');
116
+ return {
117
+ type: 'tool_result',
118
+ toolId: getString(data, 'toolCallId') ?? state.lastToolId,
119
+ content: toolResultContent(data),
120
+ isError: success === false,
121
+ };
122
+ }
123
+
124
+ function parseResult(event: Record<string, unknown>, state: ProviderParserState): OutputEvent {
125
+ const exitCode = getNumber(event, 'exitCode') ?? 0;
126
+ const success = exitCode === 0;
127
+ const usage = state.usage ?? {};
128
+ return {
129
+ type: 'result',
130
+ success,
131
+ result: success ? (state.lastAssistantText ?? null) : null,
132
+ error: success ? null : `Copilot exited with code ${exitCode}`,
133
+ inputTokens: usage.inputTokens ?? 0,
134
+ outputTokens: usage.outputTokens ?? 0,
135
+ cacheReadInputTokens: usage.cacheReadInputTokens ?? 0,
136
+ cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
137
+ };
138
+ }
139
+
140
+ export function parseCopilotEvent(line: string, state: ProviderParserState): ProviderParseResult {
141
+ const event = tryParseJson(line);
142
+ if (!isRecord(event)) return null;
143
+
144
+ const type = getString(event, 'type');
145
+ if (type === null || type.startsWith('session.') || IGNORED_TYPES.has(type)) return null;
146
+
147
+ if (type === 'result') return parseResult(event, state);
148
+
149
+ const data = getRecord(event, 'data') ?? {};
150
+ switch (type) {
151
+ case 'assistant.message_start':
152
+ return parseMessageStart(data, state);
153
+ case 'assistant.message_delta':
154
+ return parseMessageDelta(data, state);
155
+ case 'assistant.message':
156
+ return parseMessage(data, state);
157
+ case 'assistant.reasoning':
158
+ return parseReasoning(data);
159
+ case 'tool.execution_start':
160
+ return parseToolExecutionStart(data, state);
161
+ case 'tool.execution_complete':
162
+ return parseToolExecutionComplete(data, state);
163
+ default:
164
+ return null;
165
+ }
166
+ }
@@ -0,0 +1,231 @@
1
+ import { appendJsonSchemaPrompt } from '../schema';
2
+ import { unknownToMessage } from '../json';
3
+ import {
4
+ type BuildProviderCommandOptions,
5
+ type CommandSpec,
6
+ type CopilotCliFeatures,
7
+ type ErrorClassification,
8
+ type LevelModelSpec,
9
+ type LevelOverrides,
10
+ type ModelCatalogEntry,
11
+ type ModelLevel,
12
+ type ProviderAdapter,
13
+ type ResolvedModelSpec,
14
+ type WarningMetadata,
15
+ } from '../types';
16
+ import {
17
+ classifyBaseProviderError,
18
+ commandSpec,
19
+ createParserState,
20
+ optionFeatures,
21
+ resolveModelSpecWithConfig,
22
+ unsupportedSessionControlWarnings,
23
+ warning,
24
+ } from './common';
25
+ import { parseCopilotEvent } from './copilot-parser';
26
+ import type { ProviderParserState } from '../types';
27
+
28
+ // Empty catalog: Copilot's models are plan-dependent, so modelLevel is a no-op (uses Copilot's
29
+ // default). Pin a model via the `model` field or COPILOT_MODEL.
30
+ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
31
+
32
+ const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
33
+ level1: { rank: 1, model: null },
34
+ level2: { rank: 2, model: null },
35
+ level3: { rank: 3, model: null },
36
+ };
37
+
38
+ function createCopilotState(): ProviderParserState {
39
+ return {
40
+ ...createParserState('copilot'),
41
+ lastAssistantText: '',
42
+ messagePhaseById: new Map(),
43
+ assistantTextByMessageId: new Map(),
44
+ usage: { outputTokens: 0 },
45
+ };
46
+ }
47
+
48
+ function supports(help: string, pattern: RegExp): boolean {
49
+ return help ? pattern.test(help) : true;
50
+ }
51
+
52
+ function detectCliFeatures(helpText?: string | null): CopilotCliFeatures {
53
+ const help = helpText ?? '';
54
+ const unknown = !help;
55
+ return {
56
+ provider: 'copilot',
57
+ supportsJsonOutput: supports(help, /--output-format\b/),
58
+ supportsModel: supports(help, /--model\b/),
59
+ supportsAllowAll: supports(help, /--allow-all\b/),
60
+ supportsNoAskUser: supports(help, /--no-ask-user\b/),
61
+ supportsAddDir: supports(help, /--add-dir\b/),
62
+ supportsMcpConfig: supports(help, /--additional-mcp-config\b/),
63
+ unknown,
64
+ };
65
+ }
66
+
67
+ function addOutputArgs(args: string[], options: BuildProviderCommandOptions): void {
68
+ const features = optionFeatures(options);
69
+ if (
70
+ (options.outputFormat === 'json' || options.outputFormat === 'stream-json') &&
71
+ features.supportsJsonOutput !== false
72
+ ) {
73
+ args.push('--output-format', 'json');
74
+ }
75
+ }
76
+
77
+ function addModelArgs(args: string[], options: BuildProviderCommandOptions): void {
78
+ const features = optionFeatures(options);
79
+ if (options.modelSpec?.model && features.supportsModel !== false) {
80
+ args.push('--model', options.modelSpec.model);
81
+ }
82
+ }
83
+
84
+ function addAddDirArgs(args: string[], options: BuildProviderCommandOptions): void {
85
+ const features = optionFeatures(options);
86
+ if (options.cwd && features.supportsAddDir !== false) {
87
+ args.push('--add-dir', options.cwd);
88
+ }
89
+ }
90
+
91
+ function addAutoApproveArgs(args: string[], options: BuildProviderCommandOptions): void {
92
+ const features = optionFeatures(options);
93
+ if (!options.autoApprove) return;
94
+ if (features.supportsAllowAll !== false) args.push('--allow-all');
95
+ if (features.supportsNoAskUser !== false) args.push('--no-ask-user');
96
+ }
97
+
98
+ // Copilot consumes MCP servers via the `--additional-mcp-config` CLI flag (each value augments the
99
+ // session config from ~/.copilot/mcp-config.json). Unlike Claude — which reads a `.mcp.json` file
100
+ // overlaid into its config dir — Copilot's config lives under $HOME/.copilot, unreachable by the
101
+ // worktree overlay, so the CLI flag is the only delivery path. Copilot and Claude share the same
102
+ // `{"mcpServers": {...}}` envelope, so entries are forwarded verbatim (inline JSON string or
103
+ // `@<path>`) with no translation.
104
+ function addMcpArgs(args: string[], options: BuildProviderCommandOptions): void {
105
+ const features = optionFeatures(options);
106
+ if (!options.mcpConfig || options.mcpConfig.length === 0) return;
107
+ if (features.supportsMcpConfig === false) return;
108
+ for (const entry of options.mcpConfig) {
109
+ args.push('--additional-mcp-config', entry);
110
+ }
111
+ }
112
+
113
+ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
114
+ const features = optionFeatures(options);
115
+ const warnings: WarningMetadata[] = unsupportedSessionControlWarnings('copilot', options);
116
+
117
+ if (options.jsonSchema) {
118
+ warnings.push(
119
+ warning(
120
+ 'copilot',
121
+ 'copilot-jsonschema',
122
+ 'Copilot CLI does not support provider-native JSON schema; appending schema instructions to the prompt.'
123
+ )
124
+ );
125
+ }
126
+ if (options.autoApprove && features.supportsAllowAll === false) {
127
+ warnings.push(
128
+ warning(
129
+ 'copilot',
130
+ 'copilot-auto-approve',
131
+ 'Copilot CLI does not advertise --allow-all; continuing without the tool auto-approve flag.'
132
+ )
133
+ );
134
+ }
135
+ if (options.mcpConfig && options.mcpConfig.length > 0 && features.supportsMcpConfig === false) {
136
+ warnings.push(
137
+ warning(
138
+ 'copilot',
139
+ 'copilot-mcp-config',
140
+ 'Copilot CLI does not advertise --additional-mcp-config; ignoring the requested MCP server config.'
141
+ )
142
+ );
143
+ }
144
+ return warnings;
145
+ }
146
+
147
+ function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
148
+ const finalContext = options.jsonSchema
149
+ ? appendJsonSchemaPrompt(context, options.jsonSchema)
150
+ : context;
151
+ const args: string[] = [];
152
+
153
+ addOutputArgs(args, options);
154
+ addModelArgs(args, options);
155
+ addAddDirArgs(args, options);
156
+ addAutoApproveArgs(args, options);
157
+ addMcpArgs(args, options);
158
+ args.push('-p', finalContext);
159
+
160
+ return commandSpec({
161
+ binary: 'copilot',
162
+ args,
163
+ env: {},
164
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
165
+ warnings: collectWarnings(options),
166
+ });
167
+ }
168
+
169
+ function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
170
+ return resolveModelSpecWithConfig({
171
+ mapping: LEVEL_MAPPING,
172
+ defaultLevel: 'level2',
173
+ level,
174
+ overrides,
175
+ validateModelId,
176
+ });
177
+ }
178
+
179
+ function validateModelId(modelId: string | null | undefined): string | null | undefined {
180
+ if (modelId === undefined || modelId === null) return modelId;
181
+ if (typeof modelId !== 'string') {
182
+ throw new Error(`Invalid model "${unknownToMessage(modelId)}" for provider "copilot".`);
183
+ }
184
+ return modelId;
185
+ }
186
+
187
+ function classifyError(error: unknown): ErrorClassification {
188
+ return classifyBaseProviderError(
189
+ error,
190
+ [
191
+ /\brate(?:[_ -]?limit| limited)\b/i,
192
+ /\b429\b/,
193
+ /\boverloaded\b/i,
194
+ /\bservice unavailable\b/i,
195
+ /\b503\b/,
196
+ /\btemporar(?:y|ily)\b/i,
197
+ /\btimeout\b/i,
198
+ ],
199
+ [
200
+ /\bunauthorized\b/i,
201
+ /\bforbidden\b/i,
202
+ /\bbad credentials\b/i,
203
+ /\bauthentication\b/i,
204
+ /\binvalid token\b/i,
205
+ /\b(GH_TOKEN|GITHUB_TOKEN|COPILOT_GITHUB_TOKEN)\b/,
206
+ /\bquota\b/i,
207
+ /\b(cancelled|canceled|aborted|interrupted)\b/i,
208
+ /\bunknown option\b/i,
209
+ ]
210
+ );
211
+ }
212
+
213
+ export const copilotAdapter: ProviderAdapter = {
214
+ id: 'copilot',
215
+ displayName: 'Copilot',
216
+ binary: 'copilot',
217
+ adapterVersion: '1',
218
+ credentialEnvKeys: ['COPILOT_GITHUB_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN'],
219
+ modelCatalog: MODEL_CATALOG,
220
+ levelMapping: LEVEL_MAPPING,
221
+ defaultLevel: 'level2',
222
+ defaultMaxLevel: 'level3',
223
+ defaultMinLevel: 'level1',
224
+ detectCliFeatures,
225
+ buildCommand,
226
+ parseEvent: parseCopilotEvent,
227
+ createParserState: createCopilotState,
228
+ resolveModelSpec,
229
+ validateModelId,
230
+ classifyError,
231
+ };
@@ -0,0 +1,186 @@
1
+ import path from 'node:path';
2
+ import {
3
+ type BuildProviderCommandOptions,
4
+ type CommandSpec,
5
+ type ErrorClassification,
6
+ type GatewayCliFeatures,
7
+ type LevelModelSpec,
8
+ type LevelOverrides,
9
+ type ModelCatalogEntry,
10
+ type ModelLevel,
11
+ type OutputEvent,
12
+ type ProviderAdapter,
13
+ type ProviderParserState,
14
+ type ResolvedModelSpec,
15
+ InvalidProviderModelError,
16
+ } from '../types';
17
+ import { classifyBaseProviderError, commandSpec, createParserState, envRedactions } from './common';
18
+ import { resolveGatewayConfiguration, validateGatewaySettings } from '../gateway-tools';
19
+ import { getBoolean, getString, isRecord, tryParseJson } from '../json';
20
+
21
+ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
22
+
23
+ const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
24
+ level1: { rank: 1, model: null },
25
+ level2: { rank: 2, model: null },
26
+ level3: { rank: 3, model: null },
27
+ };
28
+
29
+ export const gatewaySettingsDefaults: Readonly<Record<string, unknown>> = Object.freeze({
30
+ baseUrl: null,
31
+ apiKey: null,
32
+ headers: null,
33
+ model: null,
34
+ toolPolicy: null,
35
+ });
36
+
37
+ export { validateGatewaySettings };
38
+
39
+ function detectCliFeatures(): GatewayCliFeatures {
40
+ return {
41
+ provider: 'gateway',
42
+ supportsBundledRunner: true,
43
+ unknown: false,
44
+ };
45
+ }
46
+
47
+ function buildCommand(context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
48
+ const cwd = options.cwd ?? process.cwd();
49
+ const gateway = resolveGatewayConfiguration(options.gateway, 'options.gateway', cwd);
50
+ const headerEnv = buildGatewayHeaderEnv(gateway.headers);
51
+ const request = {
52
+ context,
53
+ cwd,
54
+ gateway: {
55
+ baseUrl: gateway.baseUrl,
56
+ model: gateway.model,
57
+ toolPolicy: gateway.toolPolicy,
58
+ },
59
+ ...(Object.keys(headerEnv.mapping).length === 0 ? {} : { gatewayHeaderEnv: headerEnv.mapping }),
60
+ };
61
+ const env = {
62
+ ZEROSHOT_GATEWAY_REQUEST: JSON.stringify(request),
63
+ ZEROSHOT_GATEWAY_API_KEY: gateway.apiKey,
64
+ ...headerEnv.values,
65
+ };
66
+
67
+ return commandSpec({
68
+ binary: process.execPath,
69
+ args: [gatewayRunnerPath()],
70
+ env,
71
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
72
+ redactions: envRedactions(env),
73
+ });
74
+ }
75
+
76
+ function gatewayRunnerPath(): string {
77
+ return path.resolve(__dirname, '..', 'gateway-runner.js');
78
+ }
79
+
80
+ function buildGatewayHeaderEnv(headers: Readonly<Record<string, string>>): {
81
+ readonly mapping: Readonly<Record<string, string>>;
82
+ readonly values: Readonly<Record<string, string>>;
83
+ } {
84
+ const mapping: Record<string, string> = {};
85
+ const values: Record<string, string> = {};
86
+ let index = 0;
87
+ for (const [name, value] of Object.entries(headers)) {
88
+ const envKey = `ZEROSHOT_GATEWAY_HEADER_${index}`;
89
+ index += 1;
90
+ mapping[name] = envKey;
91
+ values[envKey] = value;
92
+ }
93
+ return { mapping, values };
94
+ }
95
+
96
+ function parseEvent(line: string, _state: ProviderParserState): OutputEvent | null {
97
+ const parsed = tryParseJson(line);
98
+ if (!isRecord(parsed)) return null;
99
+ const type = getString(parsed, 'type');
100
+ if (type === 'text') {
101
+ const text = getString(parsed, 'text');
102
+ return text === null ? null : { type: 'text', text };
103
+ }
104
+ if (type === 'tool_call') {
105
+ if (!Object.prototype.hasOwnProperty.call(parsed, 'toolName')) return null;
106
+ return {
107
+ type: 'tool_call',
108
+ toolName: getString(parsed, 'toolName'),
109
+ toolId: getString(parsed, 'toolId'),
110
+ input: parsed.input ?? null,
111
+ };
112
+ }
113
+ if (type === 'tool_result') {
114
+ if (!Object.prototype.hasOwnProperty.call(parsed, 'content')) return null;
115
+ return {
116
+ type: 'tool_result',
117
+ toolId: getString(parsed, 'toolId'),
118
+ content: parsed.content ?? null,
119
+ isError: getBoolean(parsed, 'isError') ?? false,
120
+ };
121
+ }
122
+ if (type === 'result') {
123
+ return {
124
+ type: 'result',
125
+ success: getBoolean(parsed, 'success') ?? false,
126
+ ...(Object.prototype.hasOwnProperty.call(parsed, 'result') ? { result: parsed.result } : {}),
127
+ ...(Object.prototype.hasOwnProperty.call(parsed, 'error') ? { error: parsed.error } : {}),
128
+ };
129
+ }
130
+ return null;
131
+ }
132
+
133
+ function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
134
+ const override = overrides?.[level];
135
+ return {
136
+ level,
137
+ model: override?.model ?? null,
138
+ reasoningEffort: override?.reasoningEffort,
139
+ };
140
+ }
141
+
142
+ function validateModelId(modelId: string | null | undefined): string | null | undefined {
143
+ if (modelId === undefined || modelId === null) return modelId;
144
+ const normalized = modelId.trim();
145
+ if (normalized) return normalized;
146
+ throw new InvalidProviderModelError(
147
+ 'Invalid model "" for provider "gateway". Use a non-empty model identifier.'
148
+ );
149
+ }
150
+
151
+ function classifyError(error: unknown): ErrorClassification {
152
+ return classifyBaseProviderError(
153
+ error,
154
+ [/rate[_ -]?limit/i, /\b429\b/i, /\b5\d{2}\b/, /\btimed out\b/i],
155
+ [
156
+ /invalid[_ -]?api[_ -]?key/i,
157
+ /\bunauthorized\b/i,
158
+ /\bforbidden\b/i,
159
+ /\bmodel not found\b/i,
160
+ /\bmust be a valid url\b/i,
161
+ /\btoolpolicy\b/i,
162
+ /\bnon-empty model identifier\b/i,
163
+ /\bgateway\.(?:baseUrl|apiKey|model|toolPolicy)\b/i,
164
+ ]
165
+ );
166
+ }
167
+
168
+ export const gatewayAdapter: ProviderAdapter = {
169
+ id: 'gateway',
170
+ displayName: 'Gateway',
171
+ binary: process.execPath,
172
+ adapterVersion: '1',
173
+ credentialEnvKeys: ['ZEROSHOT_GATEWAY_API_KEY'],
174
+ modelCatalog: MODEL_CATALOG,
175
+ levelMapping: LEVEL_MAPPING,
176
+ defaultLevel: 'level2',
177
+ defaultMaxLevel: 'level3',
178
+ defaultMinLevel: 'level1',
179
+ detectCliFeatures,
180
+ buildCommand,
181
+ parseEvent,
182
+ createParserState: () => createParserState('gateway'),
183
+ resolveModelSpec,
184
+ validateModelId,
185
+ classifyError,
186
+ };
@@ -103,7 +103,7 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
103
103
  return commandSpec({
104
104
  binary: 'gemini',
105
105
  args,
106
- env: {},
106
+ env: { GEMINI_CLI_TRUST_WORKSPACE: 'true' },
107
107
  ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
108
108
  warnings: collectGeminiWarnings(options),
109
109
  });
@@ -218,7 +218,14 @@ function classifyError(error: unknown): ErrorClassification {
218
218
  /No capacity available/i,
219
219
  /quota.?exceeded/i,
220
220
  ],
221
- [/\bINVALID_ARGUMENT\b/i, /\bPERMISSION_DENIED\b/i, /\bNOT_FOUND\b/i]
221
+ [
222
+ /\bINVALID_ARGUMENT\b/i,
223
+ /\bPERMISSION_DENIED\b/i,
224
+ /\bNOT_FOUND\b/i,
225
+ /\bIneligibleTierError\b/i,
226
+ /\bUNSUPPORTED_CLIENT\b/i,
227
+ /\bno longer supported\b/i,
228
+ ]
222
229
  );
223
230
  }
224
231
 
@@ -1,4 +1,5 @@
1
1
  import { stripTimestampPrefix } from '../log-prefix';
2
+ import { getProviderRegistryEntry, normalizeProviderName, providerIds } from '../provider-registry';
2
3
  import type {
3
4
  BuildProviderCommandOptions,
4
5
  CommandSpec,
@@ -11,7 +12,6 @@ import type {
11
12
  ProviderId,
12
13
  ResolvedModelSpec,
13
14
  } from '../types';
14
- import { claudeAdapter } from './claude';
15
15
  export {
16
16
  NO_MESSAGES_RETURNED,
17
17
  STREAMING_MODE_ERROR,
@@ -22,18 +22,6 @@ export {
22
22
  type StreamingModeError,
23
23
  type StructuredOutputRecovery,
24
24
  } from './claude-recovery';
25
- import { codexAdapter } from './codex';
26
- import { geminiAdapter } from './gemini';
27
- import { opencodeAdapter } from './opencode';
28
-
29
- const ADAPTERS: Readonly<Record<ProviderId, ProviderAdapter>> = {
30
- claude: claudeAdapter,
31
- codex: codexAdapter,
32
- gemini: geminiAdapter,
33
- opencode: opencodeAdapter,
34
- };
35
-
36
- const PROVIDER_IDS: readonly ProviderId[] = ['claude', 'codex', 'gemini', 'opencode'];
37
25
 
38
26
  function isOutputEventArray(
39
27
  event: OutputEvent | readonly OutputEvent[]
@@ -41,41 +29,22 @@ function isOutputEventArray(
41
29
  return Array.isArray(event);
42
30
  }
43
31
 
44
- function normalizeProviderName(name: string): ProviderId | string {
45
- const normalized = name.toLowerCase();
46
- switch (normalized) {
47
- case 'anthropic':
48
- case 'claude':
49
- return 'claude';
50
- case 'openai':
51
- case 'codex':
52
- return 'codex';
53
- case 'google':
54
- case 'gemini':
55
- return 'gemini';
56
- case 'opencode':
57
- return 'opencode';
58
- default:
59
- return name;
60
- }
61
- }
62
-
63
32
  function adapterForProviderId(provider: ProviderId): ProviderAdapter {
64
- return ADAPTERS[provider];
33
+ return getProviderRegistryEntry(provider).adapter;
65
34
  }
66
35
 
67
36
  function isProviderId(name: string): name is ProviderId {
68
- return name === 'claude' || name === 'codex' || name === 'gemini' || name === 'opencode';
37
+ return (providerIds as readonly string[]).includes(name);
69
38
  }
70
39
 
71
40
  export function getProviderAdapter(name: KnownProviderName | string): ProviderAdapter {
72
41
  const normalized = normalizeProviderName(name || '');
73
42
  if (isProviderId(normalized)) return adapterForProviderId(normalized);
74
- throw new Error(`Unknown provider: ${name}. Valid: ${PROVIDER_IDS.join(', ')}`);
43
+ throw new Error(`Unknown provider: ${name}. Valid: ${providerIds.join(', ')}`);
75
44
  }
76
45
 
77
46
  export function listProviderAdapters(): readonly ProviderId[] {
78
- return PROVIDER_IDS;
47
+ return providerIds;
79
48
  }
80
49
 
81
50
  export function buildProviderCommand(