@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/README.md +1 -1
- package/config.yaml +2 -2
- package/dist/main.js +380 -54
- package/dist/main.js.map +4 -4
- package/package.json +1 -1
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 = "
|
|
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 =
|
|
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
|
|
2165
|
-
const visionMedia = item.capabilities
|
|
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
|
-
|
|
2172
|
-
|
|
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
|
-
|
|
2191
|
-
|
|
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
|
|
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.
|
|
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.
|
|
2454
|
+
async resolveCopilotToken(signal) {
|
|
2455
|
+
return (await this.resolveCopilotAuth(signal)).token;
|
|
2254
2456
|
}
|
|
2255
|
-
baseUrl() {
|
|
2256
|
-
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
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:
|
|
3833
|
-
refresh:
|
|
3834
|
-
expires:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 ${
|
|
22251
|
+
"Authorization": `Bearer ${copilotAccess}`,
|
|
21927
22252
|
"Content-Type": "application/json",
|
|
21928
|
-
"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
|
|
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
|
-
|
|
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");
|