@charzhu/openjaw-agent 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -204,7 +204,7 @@ var init_config = __esm({
204
204
  "src/config.ts"() {
205
205
  "use strict";
206
206
  init_packageRoot();
207
- DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Ov23li8tweQw6odWQebz";
207
+ DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98";
208
208
  DEFAULT_CONFIG = {
209
209
  llm: {
210
210
  provider: "anthropic",
@@ -1884,6 +1884,85 @@ var init_openai = __esm({
1884
1884
  }
1885
1885
  });
1886
1886
 
1887
+ // src/copilot-token.ts
1888
+ function normalizeCopilotEnterpriseDomain(value) {
1889
+ return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
1890
+ }
1891
+ function copilotApiBase(enterpriseUrl) {
1892
+ return enterpriseUrl ? `https://copilot-api.${normalizeCopilotEnterpriseDomain(enterpriseUrl)}` : "https://api.githubcopilot.com";
1893
+ }
1894
+ function copilotTokenEndpoint(enterpriseUrl) {
1895
+ if (!enterpriseUrl) return "https://api.github.com/copilot_internal/v2/token";
1896
+ const domain = normalizeCopilotEnterpriseDomain(enterpriseUrl);
1897
+ const apiDomain = domain.startsWith("api.") ? domain : `api.${domain}`;
1898
+ return `https://${apiDomain}/copilot_internal/v2/token`;
1899
+ }
1900
+ function decodeQueryComponent(value) {
1901
+ return decodeURIComponent(value.replace(/\+/g, "%20"));
1902
+ }
1903
+ function copilotApiBaseFromToken(token) {
1904
+ for (const part of token.split(";")) {
1905
+ const rawProxyEndpoint = part.startsWith("proxy-ep=") ? part.slice("proxy-ep=".length) : void 0;
1906
+ if (!rawProxyEndpoint) continue;
1907
+ const proxyEndpoint = decodeQueryComponent(rawProxyEndpoint).trim().replace(/\/+$/, "");
1908
+ if (!proxyEndpoint) return void 0;
1909
+ if (proxyEndpoint.startsWith("http://") || proxyEndpoint.startsWith("https://")) return proxyEndpoint;
1910
+ const host = proxyEndpoint.startsWith("proxy.") ? `api.${proxyEndpoint.slice("proxy.".length)}` : proxyEndpoint.startsWith("api.") ? proxyEndpoint : `api.${proxyEndpoint}`;
1911
+ return `https://${host}`;
1912
+ }
1913
+ return void 0;
1914
+ }
1915
+ async function exchangeGitHubTokenForCopilotToken(githubAccess, enterpriseUrl, signal) {
1916
+ const res = await fetch(copilotTokenEndpoint(enterpriseUrl), {
1917
+ headers: {
1918
+ "Accept": "application/json",
1919
+ "Authorization": `Bearer ${githubAccess}`,
1920
+ "User-Agent": COPILOT_USER_AGENT,
1921
+ "Editor-Version": COPILOT_EDITOR_VERSION,
1922
+ "Editor-Plugin-Version": COPILOT_EDITOR_PLUGIN_VERSION,
1923
+ "Copilot-Integration-Id": COPILOT_INTEGRATION_ID
1924
+ },
1925
+ signal
1926
+ });
1927
+ const text = await res.text();
1928
+ if (!res.ok) {
1929
+ let message = text;
1930
+ try {
1931
+ const parsed = JSON.parse(text);
1932
+ message = parsed.error_description || parsed.message || text;
1933
+ } catch {
1934
+ }
1935
+ throw new Error(`GitHub Copilot token request failed: ${res.status}${message ? ` ${message.slice(0, 160)}` : ""}`);
1936
+ }
1937
+ const body = JSON.parse(text);
1938
+ if (!body.token || typeof body.expires_at !== "number") {
1939
+ throw new Error("GitHub Copilot token response was missing token or expires_at.");
1940
+ }
1941
+ return {
1942
+ githubAccess,
1943
+ copilotAccess: body.token,
1944
+ copilotExpires: body.expires_at * 1e3,
1945
+ copilotApiBaseUrl: copilotApiBaseFromToken(body.token) ?? copilotApiBase(enterpriseUrl),
1946
+ enterpriseUrl
1947
+ };
1948
+ }
1949
+ var COPILOT_USER_AGENT, COPILOT_EDITOR_VERSION, COPILOT_EDITOR_PLUGIN_VERSION, COPILOT_INTEGRATION_ID;
1950
+ var init_copilot_token = __esm({
1951
+ "src/copilot-token.ts"() {
1952
+ "use strict";
1953
+ COPILOT_USER_AGENT = "GitHubCopilotChat/0.35.0";
1954
+ COPILOT_EDITOR_VERSION = "vscode/1.107.0";
1955
+ COPILOT_EDITOR_PLUGIN_VERSION = "copilot-chat/0.35.0";
1956
+ COPILOT_INTEGRATION_ID = "vscode-chat";
1957
+ __name(normalizeCopilotEnterpriseDomain, "normalizeCopilotEnterpriseDomain");
1958
+ __name(copilotApiBase, "copilotApiBase");
1959
+ __name(copilotTokenEndpoint, "copilotTokenEndpoint");
1960
+ __name(decodeQueryComponent, "decodeQueryComponent");
1961
+ __name(copilotApiBaseFromToken, "copilotApiBaseFromToken");
1962
+ __name(exchangeGitHubTokenForCopilotToken, "exchangeGitHubTokenForCopilotToken");
1963
+ }
1964
+ });
1965
+
1887
1966
  // src/provider-auth.ts
1888
1967
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1889
1968
  import { join as join5 } from "node:path";
@@ -2024,12 +2103,6 @@ var init_types = __esm({
2024
2103
  });
2025
2104
 
2026
2105
  // src/providers/copilot.ts
2027
- function normalizeCopilotEnterpriseDomain(value) {
2028
- return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
2029
- }
2030
- function copilotApiBase(enterpriseUrl) {
2031
- return enterpriseUrl ? `https://copilot-api.${normalizeCopilotEnterpriseDomain(enterpriseUrl)}` : "https://api.githubcopilot.com";
2032
- }
2033
2106
  function shouldUseResponsesApi(modelID) {
2034
2107
  const normalized = modelID.toLowerCase();
2035
2108
  const match = /^gpt-(\d+)/.exec(normalized);
@@ -2079,6 +2152,12 @@ function toResponsesTool(tool) {
2079
2152
  parameters: normalizeObjectSchema2(tool.parameters)
2080
2153
  };
2081
2154
  }
2155
+ function endpointSupported(endpoints, target) {
2156
+ return endpoints?.some((endpoint) => endpoint.trim().toLowerCase() === target) ?? false;
2157
+ }
2158
+ function copilotSupportsResponsesWebSocket(modelInfo) {
2159
+ return endpointSupported(modelInfo?.supportedEndpoints, "ws:/responses");
2160
+ }
2082
2161
  function toAnthropicTool(tool) {
2083
2162
  assertToolName(tool.name);
2084
2163
  return {
@@ -2113,6 +2192,16 @@ function safeJsonParse(value) {
2113
2192
  return {};
2114
2193
  }
2115
2194
  }
2195
+ async function loadWebSocket() {
2196
+ const { createRequire } = await import("node:module");
2197
+ return createRequire(import.meta.url)("ws");
2198
+ }
2199
+ function responsesWebSocketUrl(baseUrl) {
2200
+ const url = new URL(`${baseUrl.replace(/\/+$/, "")}/responses`);
2201
+ if (url.protocol === "https:") url.protocol = "wss:";
2202
+ else if (url.protocol === "http:") url.protocol = "ws:";
2203
+ return url.toString();
2204
+ }
2116
2205
  function valueAtPath(value, path3) {
2117
2206
  let current = value;
2118
2207
  for (const key of path3) {
@@ -2121,6 +2210,24 @@ function valueAtPath(value, path3) {
2121
2210
  }
2122
2211
  return current;
2123
2212
  }
2213
+ function firstStringAtAny(value, paths2) {
2214
+ for (const path3 of paths2) {
2215
+ const candidate = valueAtPath(value, path3);
2216
+ if (typeof candidate === "string" && candidate.trim()) return candidate;
2217
+ }
2218
+ return void 0;
2219
+ }
2220
+ function valueContainsAny(value, needles) {
2221
+ if (typeof value === "string") {
2222
+ const normalized = value.toLowerCase();
2223
+ return needles.some((needle) => normalized.includes(needle));
2224
+ }
2225
+ if (Array.isArray(value)) return value.some((item) => valueContainsAny(item, needles));
2226
+ if (value && typeof value === "object") {
2227
+ return Object.values(value).some((item) => valueContainsAny(item, needles));
2228
+ }
2229
+ return false;
2230
+ }
2124
2231
  function firstPositiveIntegerAtAny(value, paths2) {
2125
2232
  for (const path3 of paths2) {
2126
2233
  const candidate = valueAtPath(value, path3);
@@ -2151,25 +2258,68 @@ function readCopilotOutputTokens(capabilities) {
2151
2258
  function readCopilotReasoningEfforts(capabilities) {
2152
2259
  return normalizeReasoningEfforts(valueAtPath(capabilities, ["supports", "reasoning_effort"]));
2153
2260
  }
2261
+ function copilotModelItems(body) {
2262
+ if (Array.isArray(body.data) && body.data.length > 0) return body.data;
2263
+ if (Array.isArray(body.models)) return body.models;
2264
+ return [];
2265
+ }
2266
+ function isDisabledPolicyValue(value) {
2267
+ return ["disabled", "blocked", "denied", "unavailable", "off"].includes(value.toLowerCase());
2268
+ }
2269
+ function policyAllows(policy) {
2270
+ if (policy === void 0 || policy === null) return true;
2271
+ if (typeof policy === "boolean") return policy;
2272
+ if (typeof policy === "string") return !isDisabledPolicyValue(policy);
2273
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) return true;
2274
+ const record = policy;
2275
+ if (record.enabled === false) return false;
2276
+ return !["state", "status", "result"].some((key) => {
2277
+ const value = record[key];
2278
+ return typeof value === "string" && isDisabledPolicyValue(value);
2279
+ });
2280
+ }
2281
+ function isChatCapable(model) {
2282
+ if (model.id?.toLowerCase().includes("embedding")) return false;
2283
+ if (!model.capabilities) return true;
2284
+ if (valueContainsAny(model.capabilities, ["embedding", "embeddings"])) return false;
2285
+ const kind = firstStringAtAny(model.capabilities, [["type"], ["kind"], ["family"]]);
2286
+ return !kind || !["embedding", "embeddings", "completion", "completions"].includes(kind.toLowerCase());
2287
+ }
2154
2288
  function buildReasoning(effort, modelInfo) {
2155
2289
  const supported = modelInfo?.supportedReasoningEfforts?.map((option) => option.effort) ?? [];
2156
2290
  const selected = effort && supported.includes(effort) ? effort : void 0;
2157
2291
  const effective = selected ?? modelInfo?.defaultReasoningEffort;
2158
2292
  return effective ? { effort: effective } : void 0;
2159
2293
  }
2294
+ function collectResponsesOutputItem(item, accumulator) {
2295
+ if (item.type === "message" && !accumulator.sawTextDelta) {
2296
+ for (const block of item.content ?? []) {
2297
+ if ((block.type === "output_text" || block.type === "text") && block.text) {
2298
+ accumulator.text = (accumulator.text ?? "") + block.text;
2299
+ }
2300
+ }
2301
+ } else if (item.type === "function_call" && item.name) {
2302
+ accumulator.toolCalls.push({
2303
+ id: item.call_id || item.id || `call_${accumulator.toolCalls.length}`,
2304
+ name: item.name,
2305
+ input: safeJsonParse(item.arguments)
2306
+ });
2307
+ }
2308
+ }
2160
2309
  function parseCopilotModels(body) {
2161
- const items = Array.isArray(body.data) ? body.data : [];
2310
+ const items = copilotModelItems(body);
2162
2311
  const models = [];
2163
2312
  for (const item of items) {
2164
- if (!item.id || item.model_picker_enabled === false || item.policy?.state === "disabled") continue;
2165
- const visionMedia = item.capabilities?.limits?.vision?.supported_media_types ?? [];
2313
+ if (!item.id || item.model_picker_enabled === false || !policyAllows(item.policy) || !isChatCapable(item)) continue;
2314
+ const visionMedia = valueAtPath(item.capabilities, ["limits", "vision", "supported_media_types"]);
2166
2315
  const supportedReasoning = readCopilotReasoningEfforts(item.capabilities);
2167
2316
  models.push({
2168
2317
  id: item.id,
2169
2318
  name: item.name,
2170
2319
  supportedEndpoints: item.supported_endpoints ?? [],
2171
- supportsVision: item.capabilities?.supports?.vision === true || visionMedia.some((media) => media.startsWith("image/")),
2172
- supportsToolCalls: item.capabilities?.supports?.tool_calls !== false,
2320
+ supportsResponsesWebSocket: copilotSupportsResponsesWebSocket({ supportedEndpoints: item.supported_endpoints ?? [] }),
2321
+ supportsVision: valueAtPath(item.capabilities, ["supports", "vision"]) === true || Array.isArray(visionMedia) && visionMedia.some((media) => typeof media === "string" && media.startsWith("image/")),
2322
+ supportsToolCalls: valueAtPath(item.capabilities, ["supports", "tool_calls"]) !== false,
2173
2323
  contextWindow: readCopilotContextWindow(item.capabilities),
2174
2324
  outputTokens: readCopilotOutputTokens(item.capabilities),
2175
2325
  supportedReasoningEfforts: supportedReasoning.map((effort) => ({ effort, description: effort })),
@@ -2178,32 +2328,46 @@ function parseCopilotModels(body) {
2178
2328
  }
2179
2329
  return models;
2180
2330
  }
2181
- var COPILOT_PROVIDER, USER_AGENT, OPENAI_TOOL_NAME2, CopilotProvider;
2331
+ var COPILOT_PROVIDER, USER_AGENT, OPENAI_TOOL_NAME2, COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS, RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS, RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS, CopilotProvider;
2182
2332
  var init_copilot = __esm({
2183
2333
  "src/providers/copilot.ts"() {
2184
2334
  "use strict";
2335
+ init_copilot_token();
2185
2336
  init_provider_auth();
2186
2337
  init_types();
2338
+ init_copilot_token();
2187
2339
  COPILOT_PROVIDER = "github-copilot";
2188
2340
  USER_AGENT = "openjaw-agent/0.1.0";
2189
2341
  OPENAI_TOOL_NAME2 = /^[A-Za-z0-9_-]{1,64}$/;
2190
- __name(normalizeCopilotEnterpriseDomain, "normalizeCopilotEnterpriseDomain");
2191
- __name(copilotApiBase, "copilotApiBase");
2342
+ COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS = 6e4;
2343
+ RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS = 1e4;
2344
+ RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS = 12e4;
2192
2345
  __name(shouldUseResponsesApi, "shouldUseResponsesApi");
2193
2346
  __name(normalizeObjectSchema2, "normalizeObjectSchema");
2194
2347
  __name(assertToolName, "assertToolName");
2195
2348
  __name(toChatTool, "toChatTool");
2196
2349
  __name(toResponsesTool, "toResponsesTool");
2350
+ __name(endpointSupported, "endpointSupported");
2351
+ __name(copilotSupportsResponsesWebSocket, "copilotSupportsResponsesWebSocket");
2197
2352
  __name(toAnthropicTool, "toAnthropicTool");
2198
2353
  __name(hasImageContent, "hasImageContent");
2199
2354
  __name(openAIUserContent, "openAIUserContent");
2200
2355
  __name(safeJsonParse, "safeJsonParse");
2356
+ __name(loadWebSocket, "loadWebSocket");
2357
+ __name(responsesWebSocketUrl, "responsesWebSocketUrl");
2201
2358
  __name(valueAtPath, "valueAtPath");
2359
+ __name(firstStringAtAny, "firstStringAtAny");
2360
+ __name(valueContainsAny, "valueContainsAny");
2202
2361
  __name(firstPositiveIntegerAtAny, "firstPositiveIntegerAtAny");
2203
2362
  __name(readCopilotContextWindow, "readCopilotContextWindow");
2204
2363
  __name(readCopilotOutputTokens, "readCopilotOutputTokens");
2205
2364
  __name(readCopilotReasoningEfforts, "readCopilotReasoningEfforts");
2365
+ __name(copilotModelItems, "copilotModelItems");
2366
+ __name(isDisabledPolicyValue, "isDisabledPolicyValue");
2367
+ __name(policyAllows, "policyAllows");
2368
+ __name(isChatCapable, "isChatCapable");
2206
2369
  __name(buildReasoning, "buildReasoning");
2370
+ __name(collectResponsesOutputItem, "collectResponsesOutputItem");
2207
2371
  __name(parseCopilotModels, "parseCopilotModels");
2208
2372
  CopilotProvider = class {
2209
2373
  static {
@@ -2212,6 +2376,7 @@ var init_copilot = __esm({
2212
2376
  name = COPILOT_PROVIDER;
2213
2377
  config;
2214
2378
  modelCache = null;
2379
+ responsesWebSocketDisabled = false;
2215
2380
  constructor(config) {
2216
2381
  this.config = config;
2217
2382
  }
@@ -2228,39 +2393,74 @@ var init_copilot = __esm({
2228
2393
  }
2229
2394
  async chat(options) {
2230
2395
  const modelInfo = await this.resolveModelInfo(this.config.model);
2231
- if (modelInfo?.supportedEndpoints.includes("/v1/messages") || this.config.model.toLowerCase().startsWith("claude-")) {
2232
- return this.chatAnthropicMessages(options);
2233
- }
2234
2396
  if (this.shouldRouteToResponses(modelInfo)) {
2235
2397
  return this.chatResponses(options, modelInfo);
2236
2398
  }
2399
+ if (this.shouldRouteToAnthropicMessages(modelInfo)) {
2400
+ return this.chatAnthropicMessages(options);
2401
+ }
2237
2402
  return this.chatCompletions(options);
2238
2403
  }
2239
2404
  shouldRouteToResponses(modelInfo) {
2240
2405
  if (this.config.use_responses_api === false) return false;
2241
2406
  if (modelInfo?.supportedEndpoints.length) {
2242
- return modelInfo.supportedEndpoints.includes("/responses");
2407
+ return endpointSupported(modelInfo.supportedEndpoints, "/responses");
2243
2408
  }
2244
2409
  return shouldUseResponsesApi(this.config.model);
2245
2410
  }
2411
+ shouldRouteToAnthropicMessages(modelInfo) {
2412
+ if (modelInfo?.supportedEndpoints.length) return false;
2413
+ return this.config.model.toLowerCase().startsWith("claude-");
2414
+ }
2415
+ copilotBaseUrl(credential) {
2416
+ if (this.config.base_url) return this.config.base_url.replace(/\/+$/, "");
2417
+ if (credential?.type === "oauth" && credential.copilotApiBaseUrl) return credential.copilotApiBaseUrl.replace(/\/+$/, "");
2418
+ const enterpriseUrl = this.config.copilot_enterprise_url || (credential?.type === "oauth" ? credential.enterpriseUrl : void 0);
2419
+ return copilotApiBase(enterpriseUrl);
2420
+ }
2421
+ async resolveCopilotAuth(signal) {
2422
+ if (this.config.api_key && this.config.api_key !== "proxy-token") {
2423
+ return { token: this.config.api_key, baseUrl: this.copilotBaseUrl() };
2424
+ }
2425
+ const credential = getProviderCredential(COPILOT_PROVIDER);
2426
+ if (credential?.type !== "oauth") {
2427
+ throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
2428
+ }
2429
+ if (credential.copilotAccess && credential.copilotExpires && credential.copilotExpires > Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS) {
2430
+ return { token: credential.copilotAccess, baseUrl: this.copilotBaseUrl(credential) };
2431
+ }
2432
+ const githubAccess = credential.githubAccess || credential.refresh || credential.access;
2433
+ const enterpriseUrl = this.config.copilot_enterprise_url || credential.enterpriseUrl;
2434
+ const exchanged = await exchangeGitHubTokenForCopilotToken(githubAccess, enterpriseUrl, signal);
2435
+ saveProviderCredential({
2436
+ ...credential,
2437
+ access: exchanged.copilotAccess,
2438
+ refresh: exchanged.githubAccess,
2439
+ expires: exchanged.copilotExpires,
2440
+ githubAccess: exchanged.githubAccess,
2441
+ copilotAccess: exchanged.copilotAccess,
2442
+ copilotExpires: exchanged.copilotExpires,
2443
+ copilotApiBaseUrl: exchanged.copilotApiBaseUrl,
2444
+ enterpriseUrl
2445
+ });
2446
+ return { token: exchanged.copilotAccess, baseUrl: this.config.base_url?.replace(/\/+$/, "") ?? exchanged.copilotApiBaseUrl };
2447
+ }
2246
2448
  resolveGitHubToken() {
2247
2449
  if (this.config.api_key && this.config.api_key !== "proxy-token") return this.config.api_key;
2248
2450
  const credential = getProviderCredential(COPILOT_PROVIDER);
2249
- if (credential?.type === "oauth") return credential.access || credential.refresh;
2451
+ if (credential?.type === "oauth") return credential.githubAccess || credential.refresh || credential.access;
2250
2452
  throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
2251
2453
  }
2252
- resolveCopilotToken() {
2253
- return this.resolveGitHubToken();
2454
+ async resolveCopilotToken(signal) {
2455
+ return (await this.resolveCopilotAuth(signal)).token;
2254
2456
  }
2255
- baseUrl() {
2256
- if (this.config.base_url) return this.config.base_url.replace(/\/+$/, "");
2257
- const credential = getProviderCredential(COPILOT_PROVIDER);
2258
- const enterpriseUrl = this.config.copilot_enterprise_url || (credential?.type === "oauth" ? credential.enterpriseUrl : void 0);
2259
- return copilotApiBase(enterpriseUrl);
2457
+ async baseUrl(signal) {
2458
+ return (await this.resolveCopilotAuth(signal)).baseUrl;
2260
2459
  }
2261
- async headers(options, extra) {
2460
+ async headers(options, extra, auth) {
2461
+ const resolvedAuth = auth ?? await this.resolveCopilotAuth(options.signal);
2262
2462
  const headers = {
2263
- "Authorization": `Bearer ${await this.resolveCopilotToken()}`,
2463
+ "Authorization": `Bearer ${resolvedAuth.token}`,
2264
2464
  "Content-Type": "application/json",
2265
2465
  "User-Agent": USER_AGENT,
2266
2466
  "Editor-Version": "OpenJaw/0.1.0",
@@ -2274,15 +2474,17 @@ var init_copilot = __esm({
2274
2474
  return headers;
2275
2475
  }
2276
2476
  async fetchModelInfo() {
2277
- const res = await fetch(`${this.baseUrl()}/models`, {
2477
+ const signal = AbortSignal.timeout(1e4);
2478
+ const auth = await this.resolveCopilotAuth(signal);
2479
+ const res = await fetch(`${auth.baseUrl}/models`, {
2278
2480
  headers: {
2279
- "Authorization": `Bearer ${await this.resolveCopilotToken()}`,
2481
+ "Authorization": `Bearer ${auth.token}`,
2280
2482
  "User-Agent": USER_AGENT,
2281
2483
  "Editor-Version": "OpenJaw/0.1.0",
2282
2484
  "Editor-Plugin-Version": "OpenJaw/0.1.0",
2283
2485
  "Copilot-Integration-Id": "vscode-chat"
2284
2486
  },
2285
- signal: AbortSignal.timeout(1e4)
2487
+ signal
2286
2488
  });
2287
2489
  if (!res.ok) {
2288
2490
  const detail = await res.text().catch(() => "");
@@ -2304,6 +2506,7 @@ var init_copilot = __esm({
2304
2506
  id: modelID,
2305
2507
  name: modelID,
2306
2508
  supportedEndpoints: ["/v1/messages"],
2509
+ supportsResponsesWebSocket: false,
2307
2510
  supportsVision: true,
2308
2511
  supportsToolCalls: true
2309
2512
  };
@@ -2311,6 +2514,102 @@ var init_copilot = __esm({
2311
2514
  return null;
2312
2515
  }
2313
2516
  }
2517
+ shouldUseResponsesWebSocket(modelInfo) {
2518
+ return !this.responsesWebSocketDisabled && copilotSupportsResponsesWebSocket(modelInfo);
2519
+ }
2520
+ disableResponsesWebSocket() {
2521
+ this.responsesWebSocketDisabled = true;
2522
+ }
2523
+ buildResponsesRequestBody(options, modelInfo) {
2524
+ const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
2525
+ return {
2526
+ model: this.config.model,
2527
+ input: this.buildResponsesInput(options),
2528
+ instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
2529
+ tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
2530
+ ...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
2531
+ };
2532
+ }
2533
+ buildResponsesWebSocketRequest(requestBody) {
2534
+ return {
2535
+ type: "response.create",
2536
+ ...requestBody,
2537
+ tools: requestBody.tools ?? [],
2538
+ tool_choice: requestBody.tools?.length ? "auto" : "none",
2539
+ parallel_tool_calls: true,
2540
+ store: false,
2541
+ stream: true,
2542
+ include: requestBody.include ?? []
2543
+ };
2544
+ }
2545
+ async chatResponsesWebSocket(options, requestBody) {
2546
+ const auth = await this.resolveCopilotAuth(options.signal);
2547
+ const headers = await this.headers(options, void 0, auth);
2548
+ const WebSocket2 = await loadWebSocket();
2549
+ const ws = new WebSocket2(responsesWebSocketUrl(auth.baseUrl), {
2550
+ headers,
2551
+ handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
2552
+ });
2553
+ const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
2554
+ return new Promise((resolve5, reject) => {
2555
+ const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
2556
+ let settled = false;
2557
+ const timeout = setTimeout(() => {
2558
+ if (settled) return;
2559
+ settled = true;
2560
+ ws.close();
2561
+ reject(new Error("Responses WebSocket idle timeout"));
2562
+ }, RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS);
2563
+ const finish = /* @__PURE__ */ __name((value) => {
2564
+ if (settled) return;
2565
+ settled = true;
2566
+ clearTimeout(timeout);
2567
+ ws.close();
2568
+ resolve5(value);
2569
+ }, "finish");
2570
+ const fail = /* @__PURE__ */ __name((error) => {
2571
+ if (settled) return;
2572
+ settled = true;
2573
+ clearTimeout(timeout);
2574
+ ws.close();
2575
+ reject(error instanceof Error ? error : new Error(String(error)));
2576
+ }, "fail");
2577
+ options.signal?.addEventListener("abort", () => fail(options.signal?.reason ?? new DOMException("Aborted", "AbortError")), { once: true });
2578
+ ws.addEventListener("open", () => ws.send(request));
2579
+ ws.addEventListener("error", (event) => fail(event.error ?? event.message ?? "Responses WebSocket error"));
2580
+ ws.addEventListener("close", (event) => {
2581
+ if (!settled) fail(new Error(`Responses WebSocket closed before completion (${event.code ?? "unknown"})`));
2582
+ });
2583
+ ws.addEventListener("message", (event) => {
2584
+ try {
2585
+ const text = typeof event.data === "string" ? event.data : Buffer.isBuffer(event.data) ? event.data.toString("utf8") : String(event.data);
2586
+ const parsed = JSON.parse(text);
2587
+ const type = String(parsed.type ?? "");
2588
+ if (type === "response.output_text.delta" && typeof parsed.delta === "string") {
2589
+ accumulator.sawTextDelta = true;
2590
+ accumulator.text = (accumulator.text ?? "") + parsed.delta;
2591
+ } else if (type === "response.output_item.done" && parsed.item && typeof parsed.item === "object") {
2592
+ collectResponsesOutputItem(parsed.item, accumulator);
2593
+ } else if (type === "response.failed") {
2594
+ fail(new Error(JSON.stringify(parsed.response ?? parsed)));
2595
+ } else if (type === "response.incomplete") {
2596
+ fail(new Error("Responses WebSocket returned incomplete response"));
2597
+ } else if (type === "response.completed") {
2598
+ const response = parsed.response && typeof parsed.response === "object" ? parsed.response : {};
2599
+ const usage2 = response.usage && typeof response.usage === "object" ? response.usage : void 0;
2600
+ finish({
2601
+ text: accumulator.text,
2602
+ toolCalls: accumulator.toolCalls,
2603
+ stopReason: accumulator.toolCalls.length > 0 ? "tool_use" : "end",
2604
+ usage: this.usage(usage2?.input_tokens, usage2?.output_tokens)
2605
+ });
2606
+ }
2607
+ } catch (error) {
2608
+ fail(error);
2609
+ }
2610
+ });
2611
+ });
2612
+ }
2314
2613
  buildChatMessages(options) {
2315
2614
  const messages = [
2316
2615
  {
@@ -2362,7 +2661,7 @@ var init_copilot = __esm({
2362
2661
  tool_choice: options.tools.length > 0 ? "auto" : void 0,
2363
2662
  temperature: this.config.temperature
2364
2663
  };
2365
- const res = await fetch(`${this.baseUrl()}/chat/completions`, {
2664
+ const res = await fetch(`${await this.baseUrl(options.signal)}/chat/completions`, {
2366
2665
  method: "POST",
2367
2666
  headers: await this.headers(options),
2368
2667
  body: JSON.stringify(requestBody),
@@ -2406,15 +2705,16 @@ var init_copilot = __esm({
2406
2705
  return input;
2407
2706
  }
2408
2707
  async chatResponses(options, modelInfo) {
2409
- const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
2410
- const requestBody = {
2411
- model: this.config.model,
2412
- input: this.buildResponsesInput(options),
2413
- instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
2414
- tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
2415
- ...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
2416
- };
2417
- const res = await fetch(`${this.baseUrl()}/responses`, {
2708
+ const requestBody = this.buildResponsesRequestBody(options, modelInfo);
2709
+ if (this.shouldUseResponsesWebSocket(modelInfo)) {
2710
+ try {
2711
+ return await this.chatResponsesWebSocket(options, requestBody);
2712
+ } catch (err) {
2713
+ if (options.signal?.aborted) throw err;
2714
+ this.disableResponsesWebSocket();
2715
+ }
2716
+ }
2717
+ const res = await fetch(`${await this.baseUrl(options.signal)}/responses`, {
2418
2718
  method: "POST",
2419
2719
  headers: await this.headers(options),
2420
2720
  body: JSON.stringify(requestBody),
@@ -2493,7 +2793,7 @@ var init_copilot = __esm({
2493
2793
  messages: this.buildAnthropicMessages(options),
2494
2794
  tools: options.tools.length > 0 ? options.tools.map(toAnthropicTool) : void 0
2495
2795
  };
2496
- const res = await fetch(`${this.baseUrl()}/v1/messages`, {
2796
+ const res = await fetch(`${await this.baseUrl(options.signal)}/v1/messages`, {
2497
2797
  method: "POST",
2498
2798
  headers: await this.headers(options, {
2499
2799
  "anthropic-version": "2023-06-01",
@@ -3825,13 +4125,18 @@ async function completeCopilotDeviceFlow(flow, signal) {
3825
4125
  }
3826
4126
  const body = await res.json();
3827
4127
  if (body.access_token) {
4128
+ const copilotToken = await exchangeGitHubTokenForCopilotToken(body.access_token, flow.enterpriseUrl, signal);
3828
4129
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
3829
4130
  saveProviderCredential({
3830
4131
  type: "oauth",
3831
4132
  provider: "github-copilot",
3832
- access: body.access_token,
3833
- refresh: body.access_token,
3834
- expires: 0,
4133
+ access: copilotToken.copilotAccess,
4134
+ refresh: copilotToken.githubAccess,
4135
+ expires: copilotToken.copilotExpires,
4136
+ githubAccess: copilotToken.githubAccess,
4137
+ copilotAccess: copilotToken.copilotAccess,
4138
+ copilotExpires: copilotToken.copilotExpires,
4139
+ copilotApiBaseUrl: copilotToken.copilotApiBaseUrl,
3835
4140
  enterpriseUrl: flow.enterpriseUrl,
3836
4141
  createdAt: now2,
3837
4142
  updatedAt: now2
@@ -3839,7 +4144,7 @@ async function completeCopilotDeviceFlow(flow, signal) {
3839
4144
  return {
3840
4145
  provider: "github-copilot",
3841
4146
  enterpriseUrl: flow.enterpriseUrl,
3842
- baseUrl: copilotApiBase(flow.enterpriseUrl)
4147
+ baseUrl: copilotToken.copilotApiBaseUrl
3843
4148
  };
3844
4149
  }
3845
4150
  if (body.error === "authorization_pending") continue;
@@ -3855,7 +4160,7 @@ var init_copilot_auth = __esm({
3855
4160
  "src/copilot-auth.ts"() {
3856
4161
  "use strict";
3857
4162
  init_provider_auth();
3858
- init_copilot();
4163
+ init_copilot_token();
3859
4164
  OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
3860
4165
  __name(isOAuthAbortError, "isOAuthAbortError");
3861
4166
  __name(oauthDomain, "oauthDomain");
@@ -21921,11 +22226,31 @@ async function runCopilotResponsesSearch(input, llm, startedAt) {
21921
22226
  if (!credential || credential.type !== "oauth") {
21922
22227
  throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
21923
22228
  }
21924
- const baseUrl = llm.base_url?.replace(/\/+$/, "") ?? copilotApiBase(credential.enterpriseUrl);
22229
+ let copilotAccess = credential.copilotAccess;
22230
+ let copilotApiBaseUrl = credential.copilotApiBaseUrl;
22231
+ if (!copilotAccess || !credential.copilotExpires || credential.copilotExpires <= Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2) {
22232
+ const exchanged = await exchangeGitHubTokenForCopilotToken(
22233
+ credential.githubAccess || credential.refresh || credential.access,
22234
+ credential.enterpriseUrl
22235
+ );
22236
+ copilotAccess = exchanged.copilotAccess;
22237
+ copilotApiBaseUrl = exchanged.copilotApiBaseUrl;
22238
+ saveProviderCredential({
22239
+ ...credential,
22240
+ access: exchanged.copilotAccess,
22241
+ refresh: exchanged.githubAccess,
22242
+ expires: exchanged.copilotExpires,
22243
+ githubAccess: exchanged.githubAccess,
22244
+ copilotAccess: exchanged.copilotAccess,
22245
+ copilotExpires: exchanged.copilotExpires,
22246
+ copilotApiBaseUrl: exchanged.copilotApiBaseUrl
22247
+ });
22248
+ }
22249
+ const baseUrl = llm.base_url?.replace(/\/+$/, "") ?? copilotApiBaseUrl?.replace(/\/+$/, "") ?? copilotApiBase(credential.enterpriseUrl);
21925
22250
  const headers = {
21926
- "Authorization": `Bearer ${credential.access || credential.refresh}`,
22251
+ "Authorization": `Bearer ${copilotAccess}`,
21927
22252
  "Content-Type": "application/json",
21928
- "User-Agent": COPILOT_USER_AGENT,
22253
+ "User-Agent": COPILOT_USER_AGENT2,
21929
22254
  "Editor-Version": "OpenJaw/0.1.0",
21930
22255
  "Editor-Plugin-Version": "OpenJaw/0.1.0",
21931
22256
  "Copilot-Integration-Id": "vscode-chat",
@@ -22152,15 +22477,16 @@ function limitAndDedupe(results, max) {
22152
22477
  function truncate(text) {
22153
22478
  return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
22154
22479
  }
22155
- var COPILOT_USER_AGENT, DEFAULT_TIMEOUT_MS;
22480
+ var COPILOT_USER_AGENT2, DEFAULT_TIMEOUT_MS, COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2;
22156
22481
  var init_web_search = __esm({
22157
22482
  "src/web-search.ts"() {
22158
22483
  "use strict";
22159
22484
  init_web_search_types();
22485
+ init_copilot_token();
22160
22486
  init_provider_auth();
22161
- init_copilot();
22162
- COPILOT_USER_AGENT = "openjaw-agent/0.1.0";
22487
+ COPILOT_USER_AGENT2 = "openjaw-agent/0.1.0";
22163
22488
  DEFAULT_TIMEOUT_MS = 45e3;
22489
+ COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2 = 6e4;
22164
22490
  __name(createWebSearchExecutor, "createWebSearchExecutor");
22165
22491
  __name(runCopilotResponsesSearch, "runCopilotResponsesSearch");
22166
22492
  __name(runOpenAIResponsesSearch, "runOpenAIResponsesSearch");