subharness 0.0.3 → 0.0.5

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 (241) hide show
  1. package/README.md +39 -9
  2. package/dist/adapters/approval-context.d.ts +2 -0
  3. package/dist/adapters/approval-context.js +9 -0
  4. package/dist/adapters/approval-context.js.map +1 -0
  5. package/dist/adapters/approval-lifetime.d.ts +8 -0
  6. package/dist/adapters/approval-lifetime.js +50 -0
  7. package/dist/adapters/approval-lifetime.js.map +1 -0
  8. package/dist/adapters/claude-approvals.d.ts +4 -0
  9. package/dist/adapters/claude-approvals.js +32 -0
  10. package/dist/adapters/claude-approvals.js.map +1 -0
  11. package/dist/adapters/claude-permissions.js +1 -1
  12. package/dist/adapters/claude-permissions.js.map +1 -1
  13. package/dist/adapters/claude-process.d.ts +3 -0
  14. package/dist/adapters/claude-process.js +11 -1
  15. package/dist/adapters/claude-process.js.map +1 -1
  16. package/dist/adapters/claude-result.d.ts +2 -0
  17. package/dist/adapters/claude-result.js +22 -0
  18. package/dist/adapters/claude-result.js.map +1 -0
  19. package/dist/adapters/claude.js +60 -12
  20. package/dist/adapters/claude.js.map +1 -1
  21. package/dist/adapters/codex-approvals.d.ts +6 -0
  22. package/dist/adapters/codex-approvals.js +111 -0
  23. package/dist/adapters/codex-approvals.js.map +1 -0
  24. package/dist/adapters/codex.js +36 -3
  25. package/dist/adapters/codex.js.map +1 -1
  26. package/dist/adapters/copilot-permissions.d.ts +26 -0
  27. package/dist/adapters/copilot-permissions.js +121 -0
  28. package/dist/adapters/copilot-permissions.js.map +1 -0
  29. package/dist/adapters/copilot-tools.d.ts +12 -0
  30. package/dist/adapters/copilot-tools.js +61 -0
  31. package/dist/adapters/copilot-tools.js.map +1 -0
  32. package/dist/adapters/copilot.d.ts +108 -0
  33. package/dist/adapters/copilot.js +819 -0
  34. package/dist/adapters/copilot.js.map +1 -0
  35. package/dist/adapters/cursor-cli-approvals.d.ts +2 -0
  36. package/dist/adapters/cursor-cli-approvals.js +83 -0
  37. package/dist/adapters/cursor-cli-approvals.js.map +1 -0
  38. package/dist/adapters/cursor-cli-model.d.ts +4 -0
  39. package/dist/adapters/cursor-cli-model.js +64 -0
  40. package/dist/adapters/cursor-cli-model.js.map +1 -0
  41. package/dist/adapters/cursor-cli-session.d.ts +32 -0
  42. package/dist/adapters/cursor-cli-session.js +316 -0
  43. package/dist/adapters/cursor-cli-session.js.map +1 -0
  44. package/dist/adapters/cursor-cli-tools.d.ts +20 -0
  45. package/dist/adapters/cursor-cli-tools.js +181 -0
  46. package/dist/adapters/cursor-cli-tools.js.map +1 -0
  47. package/dist/adapters/cursor-cli.d.ts +2 -0
  48. package/dist/adapters/cursor-cli.js +306 -0
  49. package/dist/adapters/cursor-cli.js.map +1 -0
  50. package/dist/adapters/cursor-model.d.ts +7 -0
  51. package/dist/adapters/cursor-model.js +67 -0
  52. package/dist/adapters/cursor-model.js.map +1 -0
  53. package/dist/adapters/cursor-rpc.d.ts +42 -0
  54. package/dist/adapters/cursor-rpc.js +271 -0
  55. package/dist/adapters/cursor-rpc.js.map +1 -0
  56. package/dist/adapters/cursor-session.d.ts +4 -0
  57. package/dist/adapters/cursor-session.js +327 -0
  58. package/dist/adapters/cursor-session.js.map +1 -0
  59. package/dist/adapters/cursor-startup.d.ts +16 -0
  60. package/dist/adapters/cursor-startup.js +74 -0
  61. package/dist/adapters/cursor-startup.js.map +1 -0
  62. package/dist/adapters/cursor-tools.d.ts +11 -0
  63. package/dist/adapters/cursor-tools.js +49 -0
  64. package/dist/adapters/cursor-tools.js.map +1 -0
  65. package/dist/adapters/cursor.d.ts +6 -0
  66. package/dist/adapters/cursor.js +17 -0
  67. package/dist/adapters/cursor.js.map +1 -0
  68. package/dist/adapters/fx-approvals.d.ts +2 -0
  69. package/dist/adapters/fx-approvals.js +14 -0
  70. package/dist/adapters/fx-approvals.js.map +1 -0
  71. package/dist/adapters/fx-auth.d.ts +12 -2
  72. package/dist/adapters/fx-auth.js +51 -61
  73. package/dist/adapters/fx-auth.js.map +1 -1
  74. package/dist/adapters/fx-profile.d.ts +8 -0
  75. package/dist/adapters/fx-profile.js +101 -0
  76. package/dist/adapters/fx-profile.js.map +1 -0
  77. package/dist/adapters/fx-rpc.d.ts +2 -0
  78. package/dist/adapters/fx-rpc.js +30 -4
  79. package/dist/adapters/fx-rpc.js.map +1 -1
  80. package/dist/adapters/fx-status.d.ts +2 -0
  81. package/dist/adapters/fx-status.js +96 -0
  82. package/dist/adapters/fx-status.js.map +1 -0
  83. package/dist/adapters/fx.js +77 -30
  84. package/dist/adapters/fx.js.map +1 -1
  85. package/dist/adapters/opencode-access.d.ts +13 -0
  86. package/dist/adapters/opencode-access.js +76 -0
  87. package/dist/adapters/opencode-access.js.map +1 -0
  88. package/dist/adapters/opencode-config.d.ts +11 -0
  89. package/dist/adapters/opencode-config.js +238 -0
  90. package/dist/adapters/opencode-config.js.map +1 -0
  91. package/dist/adapters/opencode-http.d.ts +28 -0
  92. package/dist/adapters/opencode-http.js +297 -0
  93. package/dist/adapters/opencode-http.js.map +1 -0
  94. package/dist/adapters/opencode-tools.d.ts +19 -0
  95. package/dist/adapters/opencode-tools.js +127 -0
  96. package/dist/adapters/opencode-tools.js.map +1 -0
  97. package/dist/adapters/opencode.d.ts +2 -0
  98. package/dist/adapters/opencode.js +569 -0
  99. package/dist/adapters/opencode.js.map +1 -0
  100. package/dist/adapters/rpc.d.ts +3 -0
  101. package/dist/adapters/rpc.js +45 -4
  102. package/dist/adapters/rpc.js.map +1 -1
  103. package/dist/adapters/types.d.ts +7 -0
  104. package/dist/adapters/types.js.map +1 -1
  105. package/dist/approvals/schema.d.ts +4 -0
  106. package/dist/approvals/schema.js +70 -0
  107. package/dist/approvals/schema.js.map +1 -0
  108. package/dist/approvals/types.d.ts +47 -0
  109. package/dist/approvals/types.js +2 -0
  110. package/dist/approvals/types.js.map +1 -0
  111. package/dist/cli/args.d.ts +3 -2
  112. package/dist/cli/args.js +24 -3
  113. package/dist/cli/args.js.map +1 -1
  114. package/dist/cli/dashboard-client.d.ts +5 -1
  115. package/dist/cli/dashboard-client.js +162 -15
  116. package/dist/cli/dashboard-client.js.map +1 -1
  117. package/dist/cli/dashboard-controller.d.ts +7 -0
  118. package/dist/cli/dashboard-controller.js +27 -0
  119. package/dist/cli/dashboard-controller.js.map +1 -0
  120. package/dist/cli/dashboard-detail-view.d.ts +16 -0
  121. package/dist/cli/dashboard-detail-view.js +162 -0
  122. package/dist/cli/dashboard-detail-view.js.map +1 -0
  123. package/dist/cli/dashboard-history-view.d.ts +29 -0
  124. package/dist/cli/dashboard-history-view.js +211 -0
  125. package/dist/cli/dashboard-history-view.js.map +1 -0
  126. package/dist/cli/dashboard-input.d.ts +14 -0
  127. package/dist/cli/dashboard-input.js +128 -0
  128. package/dist/cli/dashboard-input.js.map +1 -0
  129. package/dist/cli/dashboard-layout.d.ts +12 -1
  130. package/dist/cli/dashboard-layout.js +32 -30
  131. package/dist/cli/dashboard-layout.js.map +1 -1
  132. package/dist/cli/dashboard-renderer.d.ts +30 -2
  133. package/dist/cli/dashboard-renderer.js +125 -7
  134. package/dist/cli/dashboard-renderer.js.map +1 -1
  135. package/dist/cli/dashboard-style.d.ts +9 -0
  136. package/dist/cli/dashboard-style.js +36 -0
  137. package/dist/cli/dashboard-style.js.map +1 -0
  138. package/dist/cli/dashboard.d.ts +5 -3
  139. package/dist/cli/dashboard.js +102 -12
  140. package/dist/cli/dashboard.js.map +1 -1
  141. package/dist/cli/help.d.ts +1 -1
  142. package/dist/cli/help.js +37 -12
  143. package/dist/cli/help.js.map +1 -1
  144. package/dist/cli/input.d.ts +1 -0
  145. package/dist/cli/input.js +23 -17
  146. package/dist/cli/input.js.map +1 -1
  147. package/dist/cli/main.js +6 -2
  148. package/dist/cli/main.js.map +1 -1
  149. package/dist/cli/output.js +17 -1
  150. package/dist/cli/output.js.map +1 -1
  151. package/dist/config/access-provenance.d.ts +10 -0
  152. package/dist/config/access-provenance.js +31 -0
  153. package/dist/config/access-provenance.js.map +1 -0
  154. package/dist/config/access.d.ts +12 -2
  155. package/dist/config/access.js +125 -27
  156. package/dist/config/access.js.map +1 -1
  157. package/dist/config/loader.js +1 -0
  158. package/dist/config/loader.js.map +1 -1
  159. package/dist/config/oidc.js +4 -2
  160. package/dist/config/oidc.js.map +1 -1
  161. package/dist/config/project.js +2 -1
  162. package/dist/config/project.js.map +1 -1
  163. package/dist/config/resolve-access.js +24 -10
  164. package/dist/config/resolve-access.js.map +1 -1
  165. package/dist/index.d.ts +2 -2
  166. package/dist/index.js +1 -1
  167. package/dist/index.js.map +1 -1
  168. package/dist/process-diagnostics.d.ts +2 -0
  169. package/dist/process-diagnostics.js +11 -0
  170. package/dist/process-diagnostics.js.map +1 -0
  171. package/dist/runtime/access-errors.d.ts +4 -0
  172. package/dist/runtime/access-errors.js +25 -0
  173. package/dist/runtime/access-errors.js.map +1 -0
  174. package/dist/runtime/approval-registry.d.ts +27 -0
  175. package/dist/runtime/approval-registry.js +126 -0
  176. package/dist/runtime/approval-registry.js.map +1 -0
  177. package/dist/runtime/client.js +54 -14
  178. package/dist/runtime/client.js.map +1 -1
  179. package/dist/runtime/coordinator.d.ts +17 -1
  180. package/dist/runtime/coordinator.js +124 -6
  181. package/dist/runtime/coordinator.js.map +1 -1
  182. package/dist/runtime/dashboard-sanitize.d.ts +2 -0
  183. package/dist/runtime/dashboard-sanitize.js +7 -0
  184. package/dist/runtime/dashboard-sanitize.js.map +1 -0
  185. package/dist/runtime/dashboard.d.ts +43 -1
  186. package/dist/runtime/dashboard.js +2 -3
  187. package/dist/runtime/dashboard.js.map +1 -1
  188. package/dist/runtime/definition.js +11 -1
  189. package/dist/runtime/definition.js.map +1 -1
  190. package/dist/runtime/select-native.d.ts +2 -1
  191. package/dist/runtime/select-native.js +28 -10
  192. package/dist/runtime/select-native.js.map +1 -1
  193. package/dist/runtime/service.js +24 -1
  194. package/dist/runtime/service.js.map +1 -1
  195. package/dist/runtime/types.d.ts +9 -1
  196. package/dist/runtime/types.js.map +1 -1
  197. package/dist/runtime/worker-approvals.d.ts +21 -0
  198. package/dist/runtime/worker-approvals.js +51 -0
  199. package/dist/runtime/worker-approvals.js.map +1 -0
  200. package/dist/runtime/worker-client.js +73 -20
  201. package/dist/runtime/worker-client.js.map +1 -1
  202. package/dist/runtime/worker-server.d.ts +2 -0
  203. package/dist/runtime/worker-server.js +39 -10
  204. package/dist/runtime/worker-server.js.map +1 -1
  205. package/dist/sdk/definitions.d.ts +4 -1
  206. package/dist/sdk/definitions.js +16 -2
  207. package/dist/sdk/definitions.js.map +1 -1
  208. package/dist/sdk/permission-validation.js +10 -1
  209. package/dist/sdk/permission-validation.js.map +1 -1
  210. package/dist/sdk/types.d.ts +29 -1
  211. package/dist/sdk/types.js.map +1 -1
  212. package/package.json +4 -1
  213. package/sdk/access-config.md +86 -4
  214. package/sdk/adapter-contract.md +7 -13
  215. package/sdk/additional-harnesses.md +43 -0
  216. package/sdk/agent-skill.md +7 -4
  217. package/sdk/agent.md +5 -3
  218. package/sdk/approvals.md +120 -0
  219. package/sdk/authentication.md +1 -1
  220. package/sdk/cli/dashboard-design.md +44 -4
  221. package/sdk/cli/dashboard-inspection.md +25 -0
  222. package/sdk/cli/index.md +20 -11
  223. package/sdk/cli/output.md +10 -4
  224. package/sdk/completion-notifications.md +2 -0
  225. package/sdk/config.md +1 -1
  226. package/sdk/copilot.md +53 -0
  227. package/sdk/cursor.md +68 -0
  228. package/sdk/diagnostics.md +29 -0
  229. package/sdk/distribution.md +53 -1
  230. package/sdk/evals.md +10 -4
  231. package/sdk/fx.md +51 -6
  232. package/sdk/harnesses.md +13 -13
  233. package/sdk/index.md +4 -2
  234. package/sdk/message-delivery.md +1 -1
  235. package/sdk/opencode.md +57 -0
  236. package/sdk/permissions.md +7 -5
  237. package/sdk/pr-integration.md +1 -1
  238. package/sdk/project-team.md +2 -0
  239. package/sdk/sessions.md +4 -1
  240. package/sdk/tools.md +1 -1
  241. package/sdk/v1-runtime.md +6 -6
@@ -0,0 +1,819 @@
1
+ import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";
2
+ import { chmod, mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { AgentError } from "../errors.js";
6
+ import { CopilotPermissions } from "./copilot-permissions.js";
7
+ import { copilotTools } from "./copilot-tools.js";
8
+ const GATEWAY_URL = "https://ai-gateway.vercel.sh/coding-agent/v1";
9
+ const dependencies = {
10
+ createClient: options => new CopilotClient(options),
11
+ };
12
+ export const openCopilot = createCopilotOpener(dependencies);
13
+ /** Dependency injection replaces only the native SDK/runtime boundary in tests. */
14
+ export function createCopilotOpener(deps) {
15
+ return async (options) => {
16
+ const route = validateOptions(options);
17
+ const tools = copilotTools(options.agent);
18
+ const cleanupTimeoutMs = deps.cleanupTimeoutMs ?? 25_000;
19
+ const healthIntervalMs = Math.max(1, deps.healthIntervalMs ?? 1_000);
20
+ const healthTimeoutMs = Math.max(1, deps.healthTimeoutMs ?? 1_000);
21
+ const stateDirectory = await privateStateDirectory();
22
+ const queuedEvents = [];
23
+ let runtimeEventHandler;
24
+ const onEvent = (event) => {
25
+ if (runtimeEventHandler)
26
+ runtimeEventHandler(event);
27
+ else
28
+ queuedEvents.push(event);
29
+ };
30
+ let client;
31
+ let native;
32
+ const environment = isolatedEnvironment(options.env, route.kind === "subscription");
33
+ try {
34
+ client = deps.createClient({
35
+ connection: RuntimeConnection.forStdio({ env: environment }),
36
+ mode: "copilot-cli",
37
+ workingDirectory: options.cwd,
38
+ baseDirectory: route.kind === "subscription" ? undefined : stateDirectory,
39
+ useLoggedInUser: route.kind === "subscription",
40
+ gitHubToken: route.kind === "github" ? route.token : undefined,
41
+ logLevel: "none",
42
+ });
43
+ await client.start();
44
+ }
45
+ catch {
46
+ if (await cleanup(client, undefined, stateDirectory, cleanupTimeoutMs))
47
+ throw cleanupError();
48
+ throw new AgentError("HARNESS_UNAVAILABLE", "The Copilot native runtime is missing or incompatible.");
49
+ }
50
+ try {
51
+ await verifyClientAccess(client, route);
52
+ native = await client.createSession({
53
+ model: route.model,
54
+ workingDirectory: options.cwd,
55
+ configDirectory: stateDirectory,
56
+ enableConfigDiscovery: false,
57
+ ...(route.kind === "github" ? { enableManagedSettings: true } : {}),
58
+ enableSessionStore: false,
59
+ skipEmbeddingRetrieval: true,
60
+ embeddingCacheStorage: "in-memory",
61
+ remoteSession: "off",
62
+ onEvent,
63
+ systemMessage: options.agent.instructions
64
+ ? { mode: "append", content: options.agent.instructions }
65
+ : undefined,
66
+ ...tools.config,
67
+ gitHubToken: route.kind === "github" ? route.token : undefined,
68
+ provider: providerConfiguration(route),
69
+ });
70
+ }
71
+ catch (error) {
72
+ const failure = classifySessionStartup(error);
73
+ if (await cleanup(client, undefined, stateDirectory, cleanupTimeoutMs))
74
+ throw cleanupError();
75
+ throw failure;
76
+ }
77
+ try {
78
+ if (queuedEvents.some(requiresInput))
79
+ throw inputRequired();
80
+ await verifyNativeSelection(client, native, route);
81
+ await verifyNativeTools(native, tools.names);
82
+ if (queuedEvents.some(requiresInput))
83
+ throw inputRequired();
84
+ }
85
+ catch (error) {
86
+ const cleanupFailed = await cleanup(client, native, stateDirectory, cleanupTimeoutMs);
87
+ if (cleanupFailed)
88
+ throw cleanupError();
89
+ if (error instanceof AgentError)
90
+ throw error;
91
+ throw new AgentError("HARNESS_ERROR", "Copilot startup verification failed.");
92
+ }
93
+ let closed = false;
94
+ let closing;
95
+ let active;
96
+ let selectionChanged = false;
97
+ let poisoned = false;
98
+ const cancellationTimeoutMs = deps.cancellationTimeoutMs ?? 3_000;
99
+ let stopAffectedWork;
100
+ const permissions = new CopilotPermissions(options.onApproval, error => {
101
+ if (active) {
102
+ active.terminalError = error;
103
+ stopAffectedWork?.(active);
104
+ }
105
+ });
106
+ const finish = async (current) => {
107
+ if (current.settled)
108
+ return;
109
+ current.settled = true;
110
+ current.stopHealth?.();
111
+ tools.stopAdmission();
112
+ permissions.clear();
113
+ try {
114
+ await tools.drain();
115
+ if (current.closing)
116
+ throw new AgentError("SESSION_CLOSED", "The Copilot session closed during the turn.");
117
+ if (current.interrupted)
118
+ throw new AgentError("INTERRUPTED", "The Copilot turn was interrupted.");
119
+ await verifyPinnedSelection(client, native, route);
120
+ if (selectionChanged)
121
+ throw selectionError();
122
+ if (current.terminalError)
123
+ throw current.terminalError;
124
+ if (Buffer.byteLength(current.response, "utf8") > 1024 * 1024) {
125
+ throw new AgentError("RESPONSE_TOO_LARGE", "The Copilot response exceeds 1 MiB.");
126
+ }
127
+ current.resolve(current.response);
128
+ }
129
+ catch (error) {
130
+ current.reject(error instanceof AgentError
131
+ ? error
132
+ : new AgentError("HARNESS_ERROR", "Copilot could not complete the turn."));
133
+ }
134
+ finally {
135
+ if (active === current)
136
+ active = undefined;
137
+ }
138
+ };
139
+ const stopActiveTurn = async (current) => {
140
+ tools.stopAdmission();
141
+ permissions.clear();
142
+ let acknowledgement;
143
+ try {
144
+ acknowledgement = await boundedNativeCall(native.rpc.abort({}), cancellationTimeoutMs);
145
+ }
146
+ catch {
147
+ throw cleanupError();
148
+ }
149
+ if (!acknowledgement.success)
150
+ throw cleanupError();
151
+ await waitForNativeIdle(native, cancellationTimeoutMs);
152
+ await tools.drain();
153
+ await finish(current);
154
+ await current.completion.catch(() => { });
155
+ };
156
+ const ensureStopped = (current) => current.stopping ??= stopActiveTurn(current);
157
+ const failUnconfirmedAffectedWork = async (current) => {
158
+ if (current.settled)
159
+ return;
160
+ poisoned = true;
161
+ current.stopHealth?.();
162
+ tools.stopAdmission();
163
+ permissions.clear();
164
+ current.settled = true;
165
+ current.terminalError = cleanupError();
166
+ await tools.drain();
167
+ current.reject(current.terminalError);
168
+ };
169
+ const failTerminalTurn = async (current, error) => {
170
+ if (current.settled)
171
+ return;
172
+ poisoned = true;
173
+ current.stopHealth?.();
174
+ tools.stopAdmission();
175
+ permissions.clear();
176
+ current.settled = true;
177
+ await tools.drain();
178
+ current.reject(error);
179
+ if (active === current)
180
+ active = undefined;
181
+ };
182
+ const startHealthMonitor = (current) => {
183
+ const controller = new AbortController();
184
+ current.stopHealth = () => controller.abort();
185
+ void (async () => {
186
+ while (await monitorDelay(healthIntervalMs, controller.signal)) {
187
+ if (current.settled || closed)
188
+ return;
189
+ try {
190
+ await boundedNativeCall(client.ping(), healthTimeoutMs, controller.signal);
191
+ }
192
+ catch {
193
+ if (controller.signal.aborted || current.settled || closed)
194
+ return;
195
+ await failTerminalTurn(current, new AgentError("HARNESS_ERROR", "The Copilot native transport became unavailable."));
196
+ return;
197
+ }
198
+ }
199
+ })();
200
+ };
201
+ stopAffectedWork = current => {
202
+ void ensureStopped(current).catch(() => failUnconfirmedAffectedWork(current));
203
+ };
204
+ const handleEvent = (event) => {
205
+ if (event.type === "session.model_change" && event.agentId === undefined) {
206
+ const changed = event.data.newModel;
207
+ if (changed !== route.model)
208
+ selectionChanged = true;
209
+ }
210
+ const current = active;
211
+ if (event.type === "session.shutdown") {
212
+ if (current && !current.settled) {
213
+ void failTerminalTurn(current, new AgentError("HARNESS_ERROR", "The Copilot native session shut down unexpectedly."));
214
+ }
215
+ return;
216
+ }
217
+ if (event.type === "permission.completed") {
218
+ permissions.withdraw(event.data.requestId);
219
+ return;
220
+ }
221
+ if (event.type === "permission.requested") {
222
+ if (current && !current.settled)
223
+ permissions.handle(native.rpc.permissions, event.data);
224
+ else if (!event.data.resolvedByHook) {
225
+ void native.rpc.permissions.handlePendingPermissionRequest({
226
+ requestId: event.data.requestId,
227
+ result: { kind: "user-not-available" },
228
+ }).catch(() => { });
229
+ }
230
+ return;
231
+ }
232
+ if (!current || current.settled)
233
+ return;
234
+ if (requiresInput(event)) {
235
+ current.terminalError = inputRequired();
236
+ stopAffectedWork?.(current);
237
+ return;
238
+ }
239
+ if (event.agentId !== undefined)
240
+ return;
241
+ if (current.messageId === undefined) {
242
+ current.buffered.push(event);
243
+ return;
244
+ }
245
+ if (event.type === "user.message") {
246
+ if (event.data.messageId === current.messageId) {
247
+ current.started = true;
248
+ current.turnId = event.data.turnId;
249
+ }
250
+ return;
251
+ }
252
+ if (event.type === "assistant.message") {
253
+ if (event.data.originatingMessageId !== current.messageId)
254
+ return;
255
+ current.started = true;
256
+ current.turnId = event.data.turnId ?? current.turnId;
257
+ // BYOK reports the provider-facing model/deployment here, not the separately pinned behavior model.
258
+ if (route.kind !== "byok" && event.data.model !== undefined && event.data.model !== route.model) {
259
+ current.terminalError = selectionError();
260
+ return;
261
+ }
262
+ if (event.data.chunkCount !== undefined && event.data.chunkIndex !== undefined) {
263
+ current.chunks ??= [];
264
+ current.chunks[event.data.chunkIndex] = event.data.content;
265
+ current.response = current.chunks.join("");
266
+ }
267
+ else {
268
+ current.chunks = undefined;
269
+ current.response = event.data.content;
270
+ }
271
+ if (Buffer.byteLength(current.response, "utf8") > 1024 * 1024) {
272
+ current.terminalError = new AgentError("RESPONSE_TOO_LARGE", "The Copilot response exceeds 1 MiB.");
273
+ }
274
+ }
275
+ else if (event.type === "session.error") {
276
+ current.terminalError = new AgentError("HARNESS_ERROR", "Copilot reported a native execution error.");
277
+ }
278
+ else if (event.type === "session.idle") {
279
+ if (!current.started)
280
+ return;
281
+ if (event.data.aborted)
282
+ current.interrupted = true;
283
+ void waitForNativeIdle(native, cancellationTimeoutMs)
284
+ .then(() => finish(current), () => failUnconfirmedAffectedWork(current));
285
+ }
286
+ };
287
+ runtimeEventHandler = handleEvent;
288
+ for (const event of queuedEvents.splice(0))
289
+ handleEvent(event);
290
+ return {
291
+ async turn(prompt) {
292
+ if (closed)
293
+ throw new AgentError("SESSION_CLOSED", "The Copilot session has closed.");
294
+ if (poisoned)
295
+ throw cleanupError();
296
+ if (active)
297
+ throw new AgentError("SESSION_BUSY", "Copilot already has an active turn.");
298
+ selectionChanged = false;
299
+ await verifyPinnedSelection(client, native, route);
300
+ let resolve;
301
+ let reject;
302
+ const current = {
303
+ buffered: [], response: "", started: false, interrupted: false, closing: false, settled: false,
304
+ completion: new Promise((onResolve, onReject) => { resolve = onResolve; reject = onReject; }),
305
+ resolve: value => resolve(value), reject: error => reject(error),
306
+ };
307
+ active = current;
308
+ tools.start();
309
+ startHealthMonitor(current);
310
+ void (async () => {
311
+ try {
312
+ const messageId = await native.send({ prompt, mode: "enqueue" });
313
+ if (current.settled || closed || active !== current) {
314
+ current.buffered.length = 0;
315
+ return;
316
+ }
317
+ current.messageId = messageId;
318
+ for (const event of current.buffered.splice(0))
319
+ handleEvent(event);
320
+ }
321
+ catch {
322
+ current.buffered.length = 0;
323
+ if (current.settled)
324
+ return;
325
+ current.settled = true;
326
+ current.stopHealth?.();
327
+ tools.stopAdmission();
328
+ permissions.clear();
329
+ await tools.drain();
330
+ current.reject(new AgentError("HARNESS_ERROR", "Copilot could not submit the native turn."));
331
+ if (active === current)
332
+ active = undefined;
333
+ }
334
+ })();
335
+ return await current.completion;
336
+ },
337
+ async steer() {
338
+ throw new AgentError("UNSUPPORTED_DELIVERY", "Copilot does not expose verified active steering.");
339
+ },
340
+ async interrupt() {
341
+ const current = active;
342
+ if (!current)
343
+ return;
344
+ current.interrupted = true;
345
+ try {
346
+ await ensureStopped(current);
347
+ }
348
+ catch {
349
+ await failUnconfirmedAffectedWork(current);
350
+ throw cleanupError();
351
+ }
352
+ },
353
+ async resume() {
354
+ throw new AgentError("RECOVERY_UNSUPPORTED", "Copilot cannot retry failed execution without replaying input.");
355
+ },
356
+ async close() {
357
+ if (closing)
358
+ return await closing;
359
+ closed = true;
360
+ if (active)
361
+ active.closing = true;
362
+ active?.stopHealth?.();
363
+ tools.stopAdmission();
364
+ permissions.clear();
365
+ closing = (async () => {
366
+ runtimeEventHandler = () => { };
367
+ let activeStopFailed = false;
368
+ if (active) {
369
+ try {
370
+ await ensureStopped(active);
371
+ }
372
+ catch {
373
+ activeStopFailed = true;
374
+ }
375
+ }
376
+ const cleanupFailed = await cleanup(client, native, stateDirectory, cleanupTimeoutMs);
377
+ await tools.drain();
378
+ if (active && !active.settled)
379
+ await finish(active);
380
+ if (cleanupFailed || activeStopFailed)
381
+ throw cleanupError();
382
+ })();
383
+ return await closing;
384
+ },
385
+ };
386
+ };
387
+ }
388
+ function validateOptions(options) {
389
+ const harness = options.harness;
390
+ if (harness.kind !== "copilot")
391
+ throw new AgentError("INVALID_CONFIG", "Copilot received configuration for a different harness.");
392
+ if (typeof harness.model !== "string" || !harness.model.trim() || /\s/u.test(harness.model)) {
393
+ throw new AgentError("INVALID_CONFIG", "Copilot requires an explicit model identifier.");
394
+ }
395
+ if (harness.model === "auto")
396
+ throw new AgentError("INVALID_CONFIG", "Copilot does not support native automatic model routing.");
397
+ const access = options.access;
398
+ if (access.type === "subscription") {
399
+ if (access.provider !== undefined || hasProviderOptions(access))
400
+ unsupportedAccessOption();
401
+ return { kind: "subscription", model: harness.model };
402
+ }
403
+ if (options.access.type === "api-key") {
404
+ const token = requiredToken(access.token);
405
+ if (access.provider === "github") {
406
+ if (hasProviderOptions(access))
407
+ unsupportedAccessOption();
408
+ return { kind: "github", model: harness.model, token };
409
+ }
410
+ if (access.provider !== "openai" && access.provider !== "anthropic" && access.provider !== "azure") {
411
+ throw new AgentError("UNSUPPORTED_OPTION", "Copilot requires a supported explicit API-key provider.");
412
+ }
413
+ if (access.provider === "anthropic" && (access.wireApi !== undefined || access.apiVersion !== undefined))
414
+ unsupportedAccessOption();
415
+ if (access.provider === "openai" && access.apiVersion !== undefined)
416
+ unsupportedAccessOption();
417
+ if (access.provider === "azure" && access.baseUrl === undefined) {
418
+ throw new AgentError("INVALID_CONFIG", "Copilot Azure access requires an explicit base URL.");
419
+ }
420
+ return {
421
+ kind: "byok",
422
+ model: harness.model,
423
+ token,
424
+ provider: access.provider,
425
+ baseUrl: access.baseUrl ?? (access.provider === "openai"
426
+ ? "https://api.openai.com/v1"
427
+ : "https://api.anthropic.com"),
428
+ wireApi: access.provider === "anthropic" ? undefined : (access.wireApi ?? "completions"),
429
+ apiVersion: access.apiVersion,
430
+ wireModel: access.wireModel,
431
+ };
432
+ }
433
+ if (access.type !== "vercel-api-key" && access.type !== "vercel-oidc") {
434
+ throw new AgentError("UNSUPPORTED_OPTION", "Copilot received an unsupported access route.");
435
+ }
436
+ if (access.provider !== undefined || hasProviderOptions(access))
437
+ unsupportedAccessOption();
438
+ if (!/^[^\s/]+\/[^\s/]+$/u.test(harness.model)) {
439
+ throw new AgentError("INVALID_CONFIG", "Copilot Gateway access requires an explicit creator/model identifier.");
440
+ }
441
+ return {
442
+ kind: "gateway",
443
+ model: harness.model,
444
+ token: requiredToken(access.token),
445
+ bearer: access.type === "vercel-oidc",
446
+ };
447
+ }
448
+ function hasProviderOptions(access) {
449
+ return access.baseUrl !== undefined || access.wireApi !== undefined
450
+ || access.apiVersion !== undefined || access.wireModel !== undefined;
451
+ }
452
+ function unsupportedAccessOption() {
453
+ throw new AgentError("UNSUPPORTED_OPTION", "Copilot received an option that is not supported for the selected access route.");
454
+ }
455
+ function requiredToken(token) {
456
+ if (!token?.trim())
457
+ throw new AgentError("ACCESS_UNAVAILABLE", "The selected Copilot credential is unavailable.");
458
+ return token;
459
+ }
460
+ async function privateStateDirectory() {
461
+ try {
462
+ const directory = await mkdtemp(join(tmpdir(), "subharness-copilot-"));
463
+ await chmod(directory, 0o700);
464
+ return directory;
465
+ }
466
+ catch {
467
+ throw new AgentError("HARNESS_UNAVAILABLE", "Copilot could not create private native state.");
468
+ }
469
+ }
470
+ function isolatedEnvironment(source, preserveNativeHome) {
471
+ const environment = {};
472
+ for (const [name, value] of Object.entries(source))
473
+ if (value !== undefined)
474
+ environment[name] = value;
475
+ for (const name of Object.keys(environment)) {
476
+ if (/^COPILOT_PROVIDER_/u.test(name) || [
477
+ "COPILOT_CLI_PATH", "COPILOT_MODEL", "COPILOT_API_URL", "COPILOT_SDK_AUTH_TOKEN", "COPILOT_GITHUB_TOKEN",
478
+ "GITHUB_COPILOT_API_TOKEN", "GH_TOKEN", "GITHUB_TOKEN", "OPENAI_API_KEY", "OPENAI_BASE_URL",
479
+ "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "GOOGLE_API_KEY", "GEMINI_API_KEY",
480
+ "AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT", "AI_GATEWAY_API_KEY", "VERCEL_OIDC_TOKEN",
481
+ ].includes(name))
482
+ delete environment[name];
483
+ }
484
+ if (!preserveNativeHome || !environment.COPILOT_HOME?.trim())
485
+ delete environment.COPILOT_HOME;
486
+ environment.COPILOT_ALLOW_GET_PROVIDER_ENDPOINT = "true";
487
+ return environment;
488
+ }
489
+ function providerConfiguration(route) {
490
+ if (route.kind === "gateway") {
491
+ return {
492
+ type: "openai",
493
+ wireApi: "completions",
494
+ baseUrl: GATEWAY_URL,
495
+ ...(route.bearer ? { bearerToken: route.token } : { apiKey: route.token }),
496
+ };
497
+ }
498
+ if (route.kind === "byok") {
499
+ return {
500
+ type: route.provider,
501
+ ...(route.wireApi === undefined ? {} : { wireApi: route.wireApi }),
502
+ transport: "http",
503
+ baseUrl: route.baseUrl,
504
+ apiKey: route.token,
505
+ ...(route.provider === "azure" && route.apiVersion !== undefined
506
+ ? { azure: { apiVersion: route.apiVersion } }
507
+ : {}),
508
+ modelId: route.model,
509
+ ...(route.wireModel === undefined ? {} : { wireModel: route.wireModel }),
510
+ };
511
+ }
512
+ return undefined;
513
+ }
514
+ async function verifyClientAccess(client, route) {
515
+ if (route.kind !== "subscription" && route.kind !== "github")
516
+ return;
517
+ await verifyAuthStatus(client, route);
518
+ let models;
519
+ try {
520
+ models = await client.listModels();
521
+ }
522
+ catch {
523
+ throw new AgentError("HARNESS_ERROR", "Copilot could not verify the native model catalog.");
524
+ }
525
+ if (!Array.isArray(models) || models.some(model => !plainObject(model) || typeof model.id !== "string" || !model.id)) {
526
+ throw new AgentError("PROTOCOL_ERROR", "Copilot returned malformed native model metadata.");
527
+ }
528
+ const matches = models.filter(model => model.id === route.model);
529
+ if (matches.length === 0)
530
+ throw selectionError();
531
+ if (matches.length !== 1)
532
+ throw new AgentError("PROTOCOL_ERROR", "Copilot returned ambiguous native model metadata.");
533
+ }
534
+ async function verifyAuthStatus(client, route) {
535
+ let status;
536
+ try {
537
+ status = await client.getAuthStatus();
538
+ }
539
+ catch {
540
+ throw new AgentError("HARNESS_ERROR", "Copilot could not verify native authentication.");
541
+ }
542
+ if (!plainObject(status) || typeof status.isAuthenticated !== "boolean"
543
+ || (status.authType !== undefined && typeof status.authType !== "string")) {
544
+ throw new AgentError("PROTOCOL_ERROR", "Copilot returned malformed native authentication metadata.");
545
+ }
546
+ if (status.authType !== undefined && !new Set(["user", "env", "gh-cli", "hmac", "api-key", "token"]).has(status.authType)) {
547
+ throw new AgentError("PROTOCOL_ERROR", "Copilot returned unknown native authentication metadata.");
548
+ }
549
+ if (!status.isAuthenticated)
550
+ throw new AgentError("ACCESS_UNAVAILABLE", "The selected Copilot authentication is unavailable.");
551
+ const expected = route.kind === "subscription"
552
+ ? status.authType === "user" || status.authType === "gh-cli"
553
+ : status.authType === "token";
554
+ if (!expected)
555
+ throw new AgentError("INVALID_CONFIG", "Copilot native authentication does not match the selected access route.");
556
+ }
557
+ function plainObject(value) {
558
+ if (typeof value !== "object" || value === null)
559
+ return false;
560
+ const prototype = Object.getPrototypeOf(value);
561
+ return prototype === Object.prototype || prototype === null;
562
+ }
563
+ async function verifyNativeSelection(client, native, route) {
564
+ try {
565
+ const mode = await native.rpc.permissions.setMode({ mode: "manual" });
566
+ if (!mode.success || mode.mode !== "manual")
567
+ throw selectionError();
568
+ const permissionEvents = await native.rpc.permissions.setRequired({ required: true });
569
+ if (!permissionEvents.success)
570
+ throw selectionError();
571
+ const before = await native.rpc.model.getCurrent();
572
+ if (before.modelId !== route.model)
573
+ throw selectionError();
574
+ const policy = await native.rpc.model.setAllowedModels({ allowedModels: [route.model] });
575
+ if (!exactModels(policy.allowedModels, route.model) || !exactModels(policy.effectiveAllowedModels, route.model)
576
+ || policy.modelId !== route.model || policy.fallbackModel !== route.model)
577
+ throw selectionError();
578
+ const after = await native.rpc.model.getCurrent();
579
+ if (after.modelId !== route.model)
580
+ throw selectionError();
581
+ await verifyPinnedSelection(client, native, route);
582
+ const [processing, activity] = await Promise.all([
583
+ native.rpc.metadata.isProcessing(),
584
+ native.rpc.metadata.activity(),
585
+ ]);
586
+ if (processing.processing || activity.hasActiveWork)
587
+ throw selectionError();
588
+ }
589
+ catch (error) {
590
+ if (error instanceof AgentError)
591
+ throw error;
592
+ throw selectionError();
593
+ }
594
+ }
595
+ async function verifyPinnedSelection(client, native, route) {
596
+ try {
597
+ const current = await native.rpc.model.getCurrent();
598
+ if (current.modelId !== route.model)
599
+ throw selectionError();
600
+ if (route.kind === "subscription" || route.kind === "github") {
601
+ await verifyAuthStatus(client, route);
602
+ return;
603
+ }
604
+ const endpoint = await native.rpc.provider.getEndpoint({ modelId: route.model });
605
+ if (!plainObject(endpoint) || !plainObject(endpoint.headers)
606
+ || Object.values(endpoint.headers).some(value => value !== undefined && typeof value !== "string")) {
607
+ throw new AgentError("PROTOCOL_ERROR", "Copilot returned malformed native provider metadata.");
608
+ }
609
+ const expectedType = route.kind === "gateway" ? "openai" : route.provider;
610
+ const expectedWireApi = route.kind === "gateway" ? "completions" : route.wireApi;
611
+ const expectedBaseUrl = route.kind === "gateway" ? GATEWAY_URL : route.baseUrl;
612
+ if (endpoint.type !== expectedType || endpoint.wireApi !== expectedWireApi
613
+ || (endpoint.transport !== undefined && endpoint.transport !== "http")
614
+ || endpoint.baseUrl !== expectedBaseUrl || endpoint.apiKey !== route.token
615
+ || !authorizationMatches(endpoint.headers, route.token)
616
+ || endpoint.sessionToken !== undefined)
617
+ throw selectionError();
618
+ }
619
+ catch (error) {
620
+ if (error instanceof AgentError)
621
+ throw error;
622
+ if (isMethodUnavailable(error)) {
623
+ throw new AgentError("HARNESS_UNAVAILABLE", "The Copilot native runtime lacks required protocol capabilities.");
624
+ }
625
+ throw new AgentError("HARNESS_ERROR", "Copilot could not verify the pinned native selection.");
626
+ }
627
+ }
628
+ async function verifyNativeTools(native, declaredNames) {
629
+ if (declaredNames.length === 0)
630
+ return;
631
+ try {
632
+ const builtins = await native.rpc.tools.getBuiltinDescriptors({});
633
+ const builtinNames = new Set(builtins.tools.map(tool => tool.name));
634
+ if (declaredNames.some(name => builtinNames.has(name))) {
635
+ throw new AgentError("INVALID_CONFIG", "A declared Copilot tool conflicts with a native built-in tool.");
636
+ }
637
+ await native.rpc.tools.initializeAndValidate();
638
+ const current = await native.rpc.tools.getCurrentMetadata();
639
+ const currentNames = new Set(current.tools?.map(tool => tool.name) ?? []);
640
+ if (declaredNames.some(name => !currentNames.has(name))) {
641
+ throw new AgentError("HARNESS_ERROR", "Copilot could not verify the declared native tools.");
642
+ }
643
+ }
644
+ catch (error) {
645
+ if (error instanceof AgentError)
646
+ throw error;
647
+ if (isMethodUnavailable(error)) {
648
+ throw new AgentError("HARNESS_UNAVAILABLE", "The Copilot native runtime lacks required tool protocol capabilities.");
649
+ }
650
+ throw new AgentError("HARNESS_ERROR", "Copilot could not initialize the declared native tools.");
651
+ }
652
+ }
653
+ function exactModels(models, selected) {
654
+ return models?.length === 1 && models[0] === selected;
655
+ }
656
+ function selectionError() {
657
+ return new AgentError("INVALID_CONFIG", "Copilot could not verify the requested model and provider selection.");
658
+ }
659
+ function authorizationMatches(headers, token) {
660
+ const authorizations = Object.entries(headers)
661
+ .filter(([name]) => name.toLowerCase() === "authorization")
662
+ .map(([, value]) => value);
663
+ return authorizations.length <= 1
664
+ && (authorizations.length === 0 || authorizations[0] === `Bearer ${token}`);
665
+ }
666
+ function classifySessionStartup(error) {
667
+ if (error instanceof AgentError)
668
+ return error;
669
+ if (isMethodUnavailable(error)) {
670
+ return new AgentError("HARNESS_UNAVAILABLE", "The Copilot native runtime lacks required session capabilities.");
671
+ }
672
+ if (typeof error === "object" && error !== null && error.code === -32602) {
673
+ return new AgentError("INVALID_CONFIG", "Copilot rejected the explicit native session configuration.");
674
+ }
675
+ return new AgentError("HARNESS_ERROR", "Copilot could not create the explicitly configured native session.");
676
+ }
677
+ function isMethodUnavailable(error) {
678
+ if (typeof error !== "object" || error === null)
679
+ return false;
680
+ const code = error.code;
681
+ return code === -32601 || code === "METHOD_NOT_FOUND" || code === "UNSUPPORTED_METHOD";
682
+ }
683
+ async function cleanup(client, native, directory, timeoutMs) {
684
+ let failed = false;
685
+ if (native) {
686
+ try {
687
+ await boundedNativeCall(native.disconnect(), timeoutMs);
688
+ }
689
+ catch {
690
+ failed = true;
691
+ }
692
+ if (client) {
693
+ try {
694
+ await boundedNativeCall(client.deleteSession(native.sessionId), timeoutMs);
695
+ }
696
+ catch {
697
+ failed = true;
698
+ }
699
+ }
700
+ }
701
+ if (client) {
702
+ let force = false;
703
+ try {
704
+ const errors = await boundedNativeCall(client.stop(), timeoutMs);
705
+ if (errors.length > 0) {
706
+ failed = true;
707
+ force = true;
708
+ }
709
+ }
710
+ catch {
711
+ failed = true;
712
+ force = true;
713
+ }
714
+ if (force) {
715
+ try {
716
+ await boundedNativeCall(client.forceStop(), timeoutMs);
717
+ }
718
+ catch {
719
+ failed = true;
720
+ }
721
+ }
722
+ }
723
+ try {
724
+ await rm(directory, { recursive: true });
725
+ }
726
+ catch {
727
+ failed = true;
728
+ }
729
+ return failed;
730
+ }
731
+ function cleanupError() {
732
+ return new AgentError("CANCELLATION_FAILED", "Copilot native process cleanup could not be confirmed.");
733
+ }
734
+ function inputRequired() {
735
+ return new AgentError("INPUT_REQUIRED", "Copilot requested unsupported interactive input.");
736
+ }
737
+ function requiresInput(event) {
738
+ if (event.type === "permission.requested")
739
+ return event.data.resolvedByHook !== true;
740
+ return new Set([
741
+ "user_input.requested",
742
+ "elicitation.requested",
743
+ "sampling.requested",
744
+ "mcp.oauth_required",
745
+ "mcp.headers_refresh_required",
746
+ "external_tool.requested",
747
+ "ui.ephemeral_query",
748
+ "auto_mode_switch.requested",
749
+ "session_limits_exhausted.requested",
750
+ "exit_plan_mode.requested",
751
+ ]).has(event.type);
752
+ }
753
+ async function waitForNativeIdle(native, timeoutMs) {
754
+ const deadline = Date.now() + timeoutMs;
755
+ while (true) {
756
+ const remaining = deadline - Date.now();
757
+ if (remaining <= 0)
758
+ throw cleanupError();
759
+ try {
760
+ const [processing, activity] = await boundedNativeCall(Promise.all([
761
+ native.rpc.metadata.isProcessing(),
762
+ native.rpc.metadata.activity(),
763
+ ]), remaining);
764
+ if (!processing.processing && !activity.hasActiveWork)
765
+ return;
766
+ }
767
+ catch {
768
+ throw cleanupError();
769
+ }
770
+ const pause = Math.min(10, deadline - Date.now());
771
+ if (pause <= 0)
772
+ throw cleanupError();
773
+ await new Promise(resolve => setTimeout(resolve, pause));
774
+ }
775
+ }
776
+ async function boundedNativeCall(call, timeoutMs, signal) {
777
+ let timer;
778
+ let abort;
779
+ try {
780
+ const pending = [
781
+ call,
782
+ new Promise((_resolve, reject) => {
783
+ timer = setTimeout(() => reject(cleanupError()), timeoutMs);
784
+ }),
785
+ ];
786
+ if (signal) {
787
+ pending.push(new Promise((_resolve, reject) => {
788
+ abort = () => reject(new Error("native-call-monitor-stopped"));
789
+ if (signal.aborted)
790
+ abort();
791
+ else
792
+ signal.addEventListener("abort", abort, { once: true });
793
+ }));
794
+ }
795
+ return await Promise.race(pending);
796
+ }
797
+ finally {
798
+ if (timer)
799
+ clearTimeout(timer);
800
+ if (signal && abort)
801
+ signal.removeEventListener("abort", abort);
802
+ }
803
+ }
804
+ async function monitorDelay(delayMs, signal) {
805
+ if (signal.aborted)
806
+ return false;
807
+ return await new Promise(resolve => {
808
+ const timer = setTimeout(() => {
809
+ signal.removeEventListener("abort", stop);
810
+ resolve(true);
811
+ }, delayMs);
812
+ const stop = () => {
813
+ clearTimeout(timer);
814
+ resolve(false);
815
+ };
816
+ signal.addEventListener("abort", stop, { once: true });
817
+ });
818
+ }
819
+ //# sourceMappingURL=copilot.js.map