@charzhu/openjaw-agent 0.2.7 → 0.2.9
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 +528 -78
- 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,77 @@ var init_copilot = __esm({
|
|
|
2228
2393
|
}
|
|
2229
2394
|
async chat(options) {
|
|
2230
2395
|
const modelInfo = await this.resolveModelInfo(this.config.model);
|
|
2231
|
-
if (
|
|
2232
|
-
|
|
2396
|
+
if (hasImageContent(options.messages) && modelInfo?.supportsVision === false) {
|
|
2397
|
+
throw new Error(`model ${this.config.model} does not support image input; switch to a vision-capable model before submitting an image`);
|
|
2233
2398
|
}
|
|
2234
2399
|
if (this.shouldRouteToResponses(modelInfo)) {
|
|
2235
2400
|
return this.chatResponses(options, modelInfo);
|
|
2236
2401
|
}
|
|
2402
|
+
if (this.shouldRouteToAnthropicMessages(modelInfo)) {
|
|
2403
|
+
return this.chatAnthropicMessages(options);
|
|
2404
|
+
}
|
|
2237
2405
|
return this.chatCompletions(options);
|
|
2238
2406
|
}
|
|
2239
2407
|
shouldRouteToResponses(modelInfo) {
|
|
2240
2408
|
if (this.config.use_responses_api === false) return false;
|
|
2241
2409
|
if (modelInfo?.supportedEndpoints.length) {
|
|
2242
|
-
return modelInfo.supportedEndpoints
|
|
2410
|
+
return endpointSupported(modelInfo.supportedEndpoints, "/responses");
|
|
2243
2411
|
}
|
|
2244
2412
|
return shouldUseResponsesApi(this.config.model);
|
|
2245
2413
|
}
|
|
2414
|
+
shouldRouteToAnthropicMessages(modelInfo) {
|
|
2415
|
+
if (modelInfo?.supportedEndpoints.length) return false;
|
|
2416
|
+
return this.config.model.toLowerCase().startsWith("claude-");
|
|
2417
|
+
}
|
|
2418
|
+
copilotBaseUrl(credential) {
|
|
2419
|
+
if (this.config.base_url) return this.config.base_url.replace(/\/+$/, "");
|
|
2420
|
+
if (credential?.type === "oauth" && credential.copilotApiBaseUrl) return credential.copilotApiBaseUrl.replace(/\/+$/, "");
|
|
2421
|
+
const enterpriseUrl = this.config.copilot_enterprise_url || (credential?.type === "oauth" ? credential.enterpriseUrl : void 0);
|
|
2422
|
+
return copilotApiBase(enterpriseUrl);
|
|
2423
|
+
}
|
|
2424
|
+
async resolveCopilotAuth(signal) {
|
|
2425
|
+
if (this.config.api_key && this.config.api_key !== "proxy-token") {
|
|
2426
|
+
return { token: this.config.api_key, baseUrl: this.copilotBaseUrl() };
|
|
2427
|
+
}
|
|
2428
|
+
const credential = getProviderCredential(COPILOT_PROVIDER);
|
|
2429
|
+
if (credential?.type !== "oauth") {
|
|
2430
|
+
throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
|
|
2431
|
+
}
|
|
2432
|
+
if (credential.copilotAccess && credential.copilotExpires && credential.copilotExpires > Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS) {
|
|
2433
|
+
return { token: credential.copilotAccess, baseUrl: this.copilotBaseUrl(credential) };
|
|
2434
|
+
}
|
|
2435
|
+
const githubAccess = credential.githubAccess || credential.refresh || credential.access;
|
|
2436
|
+
const enterpriseUrl = this.config.copilot_enterprise_url || credential.enterpriseUrl;
|
|
2437
|
+
const exchanged = await exchangeGitHubTokenForCopilotToken(githubAccess, enterpriseUrl, signal);
|
|
2438
|
+
saveProviderCredential({
|
|
2439
|
+
...credential,
|
|
2440
|
+
access: exchanged.copilotAccess,
|
|
2441
|
+
refresh: exchanged.githubAccess,
|
|
2442
|
+
expires: exchanged.copilotExpires,
|
|
2443
|
+
githubAccess: exchanged.githubAccess,
|
|
2444
|
+
copilotAccess: exchanged.copilotAccess,
|
|
2445
|
+
copilotExpires: exchanged.copilotExpires,
|
|
2446
|
+
copilotApiBaseUrl: exchanged.copilotApiBaseUrl,
|
|
2447
|
+
enterpriseUrl
|
|
2448
|
+
});
|
|
2449
|
+
return { token: exchanged.copilotAccess, baseUrl: this.config.base_url?.replace(/\/+$/, "") ?? exchanged.copilotApiBaseUrl };
|
|
2450
|
+
}
|
|
2246
2451
|
resolveGitHubToken() {
|
|
2247
2452
|
if (this.config.api_key && this.config.api_key !== "proxy-token") return this.config.api_key;
|
|
2248
2453
|
const credential = getProviderCredential(COPILOT_PROVIDER);
|
|
2249
|
-
if (credential?.type === "oauth") return credential.
|
|
2454
|
+
if (credential?.type === "oauth") return credential.githubAccess || credential.refresh || credential.access;
|
|
2250
2455
|
throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
|
|
2251
2456
|
}
|
|
2252
|
-
resolveCopilotToken() {
|
|
2253
|
-
return this.
|
|
2457
|
+
async resolveCopilotToken(signal) {
|
|
2458
|
+
return (await this.resolveCopilotAuth(signal)).token;
|
|
2254
2459
|
}
|
|
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);
|
|
2460
|
+
async baseUrl(signal) {
|
|
2461
|
+
return (await this.resolveCopilotAuth(signal)).baseUrl;
|
|
2260
2462
|
}
|
|
2261
|
-
async headers(options, extra) {
|
|
2463
|
+
async headers(options, extra, auth) {
|
|
2464
|
+
const resolvedAuth = auth ?? await this.resolveCopilotAuth(options.signal);
|
|
2262
2465
|
const headers = {
|
|
2263
|
-
"Authorization": `Bearer ${
|
|
2466
|
+
"Authorization": `Bearer ${resolvedAuth.token}`,
|
|
2264
2467
|
"Content-Type": "application/json",
|
|
2265
2468
|
"User-Agent": USER_AGENT,
|
|
2266
2469
|
"Editor-Version": "OpenJaw/0.1.0",
|
|
@@ -2274,15 +2477,17 @@ var init_copilot = __esm({
|
|
|
2274
2477
|
return headers;
|
|
2275
2478
|
}
|
|
2276
2479
|
async fetchModelInfo() {
|
|
2277
|
-
const
|
|
2480
|
+
const signal = AbortSignal.timeout(1e4);
|
|
2481
|
+
const auth = await this.resolveCopilotAuth(signal);
|
|
2482
|
+
const res = await fetch(`${auth.baseUrl}/models`, {
|
|
2278
2483
|
headers: {
|
|
2279
|
-
"Authorization": `Bearer ${
|
|
2484
|
+
"Authorization": `Bearer ${auth.token}`,
|
|
2280
2485
|
"User-Agent": USER_AGENT,
|
|
2281
2486
|
"Editor-Version": "OpenJaw/0.1.0",
|
|
2282
2487
|
"Editor-Plugin-Version": "OpenJaw/0.1.0",
|
|
2283
2488
|
"Copilot-Integration-Id": "vscode-chat"
|
|
2284
2489
|
},
|
|
2285
|
-
signal
|
|
2490
|
+
signal
|
|
2286
2491
|
});
|
|
2287
2492
|
if (!res.ok) {
|
|
2288
2493
|
const detail = await res.text().catch(() => "");
|
|
@@ -2304,6 +2509,7 @@ var init_copilot = __esm({
|
|
|
2304
2509
|
id: modelID,
|
|
2305
2510
|
name: modelID,
|
|
2306
2511
|
supportedEndpoints: ["/v1/messages"],
|
|
2512
|
+
supportsResponsesWebSocket: false,
|
|
2307
2513
|
supportsVision: true,
|
|
2308
2514
|
supportsToolCalls: true
|
|
2309
2515
|
};
|
|
@@ -2311,6 +2517,102 @@ var init_copilot = __esm({
|
|
|
2311
2517
|
return null;
|
|
2312
2518
|
}
|
|
2313
2519
|
}
|
|
2520
|
+
shouldUseResponsesWebSocket(modelInfo) {
|
|
2521
|
+
return !this.responsesWebSocketDisabled && copilotSupportsResponsesWebSocket(modelInfo);
|
|
2522
|
+
}
|
|
2523
|
+
disableResponsesWebSocket() {
|
|
2524
|
+
this.responsesWebSocketDisabled = true;
|
|
2525
|
+
}
|
|
2526
|
+
buildResponsesRequestBody(options, modelInfo) {
|
|
2527
|
+
const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
|
|
2528
|
+
return {
|
|
2529
|
+
model: this.config.model,
|
|
2530
|
+
input: this.buildResponsesInput(options),
|
|
2531
|
+
instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
|
|
2532
|
+
tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
|
|
2533
|
+
...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
buildResponsesWebSocketRequest(requestBody) {
|
|
2537
|
+
return {
|
|
2538
|
+
type: "response.create",
|
|
2539
|
+
...requestBody,
|
|
2540
|
+
tools: requestBody.tools ?? [],
|
|
2541
|
+
tool_choice: requestBody.tools?.length ? "auto" : "none",
|
|
2542
|
+
parallel_tool_calls: true,
|
|
2543
|
+
store: false,
|
|
2544
|
+
stream: true,
|
|
2545
|
+
include: requestBody.include ?? []
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
async chatResponsesWebSocket(options, requestBody) {
|
|
2549
|
+
const auth = await this.resolveCopilotAuth(options.signal);
|
|
2550
|
+
const headers = await this.headers(options, void 0, auth);
|
|
2551
|
+
const WebSocket2 = await loadWebSocket();
|
|
2552
|
+
const ws = new WebSocket2(responsesWebSocketUrl(auth.baseUrl), {
|
|
2553
|
+
headers,
|
|
2554
|
+
handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
|
|
2555
|
+
});
|
|
2556
|
+
const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
|
|
2557
|
+
return new Promise((resolve5, reject) => {
|
|
2558
|
+
const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
|
|
2559
|
+
let settled = false;
|
|
2560
|
+
const timeout = setTimeout(() => {
|
|
2561
|
+
if (settled) return;
|
|
2562
|
+
settled = true;
|
|
2563
|
+
ws.close();
|
|
2564
|
+
reject(new Error("Responses WebSocket idle timeout"));
|
|
2565
|
+
}, RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS);
|
|
2566
|
+
const finish = /* @__PURE__ */ __name((value) => {
|
|
2567
|
+
if (settled) return;
|
|
2568
|
+
settled = true;
|
|
2569
|
+
clearTimeout(timeout);
|
|
2570
|
+
ws.close();
|
|
2571
|
+
resolve5(value);
|
|
2572
|
+
}, "finish");
|
|
2573
|
+
const fail = /* @__PURE__ */ __name((error) => {
|
|
2574
|
+
if (settled) return;
|
|
2575
|
+
settled = true;
|
|
2576
|
+
clearTimeout(timeout);
|
|
2577
|
+
ws.close();
|
|
2578
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2579
|
+
}, "fail");
|
|
2580
|
+
options.signal?.addEventListener("abort", () => fail(options.signal?.reason ?? new DOMException("Aborted", "AbortError")), { once: true });
|
|
2581
|
+
ws.addEventListener("open", () => ws.send(request));
|
|
2582
|
+
ws.addEventListener("error", (event) => fail(event.error ?? event.message ?? "Responses WebSocket error"));
|
|
2583
|
+
ws.addEventListener("close", (event) => {
|
|
2584
|
+
if (!settled) fail(new Error(`Responses WebSocket closed before completion (${event.code ?? "unknown"})`));
|
|
2585
|
+
});
|
|
2586
|
+
ws.addEventListener("message", (event) => {
|
|
2587
|
+
try {
|
|
2588
|
+
const text = typeof event.data === "string" ? event.data : Buffer.isBuffer(event.data) ? event.data.toString("utf8") : String(event.data);
|
|
2589
|
+
const parsed = JSON.parse(text);
|
|
2590
|
+
const type = String(parsed.type ?? "");
|
|
2591
|
+
if (type === "response.output_text.delta" && typeof parsed.delta === "string") {
|
|
2592
|
+
accumulator.sawTextDelta = true;
|
|
2593
|
+
accumulator.text = (accumulator.text ?? "") + parsed.delta;
|
|
2594
|
+
} else if (type === "response.output_item.done" && parsed.item && typeof parsed.item === "object") {
|
|
2595
|
+
collectResponsesOutputItem(parsed.item, accumulator);
|
|
2596
|
+
} else if (type === "response.failed") {
|
|
2597
|
+
fail(new Error(JSON.stringify(parsed.response ?? parsed)));
|
|
2598
|
+
} else if (type === "response.incomplete") {
|
|
2599
|
+
fail(new Error("Responses WebSocket returned incomplete response"));
|
|
2600
|
+
} else if (type === "response.completed") {
|
|
2601
|
+
const response = parsed.response && typeof parsed.response === "object" ? parsed.response : {};
|
|
2602
|
+
const usage2 = response.usage && typeof response.usage === "object" ? response.usage : void 0;
|
|
2603
|
+
finish({
|
|
2604
|
+
text: accumulator.text,
|
|
2605
|
+
toolCalls: accumulator.toolCalls,
|
|
2606
|
+
stopReason: accumulator.toolCalls.length > 0 ? "tool_use" : "end",
|
|
2607
|
+
usage: this.usage(usage2?.input_tokens, usage2?.output_tokens)
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
} catch (error) {
|
|
2611
|
+
fail(error);
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2314
2616
|
buildChatMessages(options) {
|
|
2315
2617
|
const messages = [
|
|
2316
2618
|
{
|
|
@@ -2362,7 +2664,7 @@ var init_copilot = __esm({
|
|
|
2362
2664
|
tool_choice: options.tools.length > 0 ? "auto" : void 0,
|
|
2363
2665
|
temperature: this.config.temperature
|
|
2364
2666
|
};
|
|
2365
|
-
const res = await fetch(`${this.baseUrl()}/chat/completions`, {
|
|
2667
|
+
const res = await fetch(`${await this.baseUrl(options.signal)}/chat/completions`, {
|
|
2366
2668
|
method: "POST",
|
|
2367
2669
|
headers: await this.headers(options),
|
|
2368
2670
|
body: JSON.stringify(requestBody),
|
|
@@ -2406,15 +2708,16 @@ var init_copilot = __esm({
|
|
|
2406
2708
|
return input;
|
|
2407
2709
|
}
|
|
2408
2710
|
async chatResponses(options, modelInfo) {
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2711
|
+
const requestBody = this.buildResponsesRequestBody(options, modelInfo);
|
|
2712
|
+
if (this.shouldUseResponsesWebSocket(modelInfo)) {
|
|
2713
|
+
try {
|
|
2714
|
+
return await this.chatResponsesWebSocket(options, requestBody);
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
if (options.signal?.aborted) throw err;
|
|
2717
|
+
this.disableResponsesWebSocket();
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
const res = await fetch(`${await this.baseUrl(options.signal)}/responses`, {
|
|
2418
2721
|
method: "POST",
|
|
2419
2722
|
headers: await this.headers(options),
|
|
2420
2723
|
body: JSON.stringify(requestBody),
|
|
@@ -2493,7 +2796,7 @@ var init_copilot = __esm({
|
|
|
2493
2796
|
messages: this.buildAnthropicMessages(options),
|
|
2494
2797
|
tools: options.tools.length > 0 ? options.tools.map(toAnthropicTool) : void 0
|
|
2495
2798
|
};
|
|
2496
|
-
const res = await fetch(`${this.baseUrl()}/v1/messages`, {
|
|
2799
|
+
const res = await fetch(`${await this.baseUrl(options.signal)}/v1/messages`, {
|
|
2497
2800
|
method: "POST",
|
|
2498
2801
|
headers: await this.headers(options, {
|
|
2499
2802
|
"anthropic-version": "2023-06-01",
|
|
@@ -3825,13 +4128,18 @@ async function completeCopilotDeviceFlow(flow, signal) {
|
|
|
3825
4128
|
}
|
|
3826
4129
|
const body = await res.json();
|
|
3827
4130
|
if (body.access_token) {
|
|
4131
|
+
const copilotToken = await exchangeGitHubTokenForCopilotToken(body.access_token, flow.enterpriseUrl, signal);
|
|
3828
4132
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
3829
4133
|
saveProviderCredential({
|
|
3830
4134
|
type: "oauth",
|
|
3831
4135
|
provider: "github-copilot",
|
|
3832
|
-
access:
|
|
3833
|
-
refresh:
|
|
3834
|
-
expires:
|
|
4136
|
+
access: copilotToken.copilotAccess,
|
|
4137
|
+
refresh: copilotToken.githubAccess,
|
|
4138
|
+
expires: copilotToken.copilotExpires,
|
|
4139
|
+
githubAccess: copilotToken.githubAccess,
|
|
4140
|
+
copilotAccess: copilotToken.copilotAccess,
|
|
4141
|
+
copilotExpires: copilotToken.copilotExpires,
|
|
4142
|
+
copilotApiBaseUrl: copilotToken.copilotApiBaseUrl,
|
|
3835
4143
|
enterpriseUrl: flow.enterpriseUrl,
|
|
3836
4144
|
createdAt: now2,
|
|
3837
4145
|
updatedAt: now2
|
|
@@ -3839,7 +4147,7 @@ async function completeCopilotDeviceFlow(flow, signal) {
|
|
|
3839
4147
|
return {
|
|
3840
4148
|
provider: "github-copilot",
|
|
3841
4149
|
enterpriseUrl: flow.enterpriseUrl,
|
|
3842
|
-
baseUrl:
|
|
4150
|
+
baseUrl: copilotToken.copilotApiBaseUrl
|
|
3843
4151
|
};
|
|
3844
4152
|
}
|
|
3845
4153
|
if (body.error === "authorization_pending") continue;
|
|
@@ -3855,7 +4163,7 @@ var init_copilot_auth = __esm({
|
|
|
3855
4163
|
"src/copilot-auth.ts"() {
|
|
3856
4164
|
"use strict";
|
|
3857
4165
|
init_provider_auth();
|
|
3858
|
-
|
|
4166
|
+
init_copilot_token();
|
|
3859
4167
|
OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
3860
4168
|
__name(isOAuthAbortError, "isOAuthAbortError");
|
|
3861
4169
|
__name(oauthDomain, "oauthDomain");
|
|
@@ -4786,7 +5094,9 @@ ${summary}
|
|
|
4786
5094
|
if (imageData) {
|
|
4787
5095
|
const contentBlocks = [
|
|
4788
5096
|
{ type: "image", source: { type: "base64", media_type: imageData.mimeType, data: imageData.base64 } },
|
|
4789
|
-
{ type: "text", text: taskText
|
|
5097
|
+
{ type: "text", text: `${taskText}
|
|
5098
|
+
|
|
5099
|
+
[ATTACHED IMAGE]: The image is attached as visual input in this message. Inspect it directly; do not search the filesystem for an uploaded image path unless the user explicitly asks for a file.` }
|
|
4790
5100
|
];
|
|
4791
5101
|
this.conversationHistory.push({ role: "user", content: contentBlocks });
|
|
4792
5102
|
} else {
|
|
@@ -21921,11 +22231,31 @@ async function runCopilotResponsesSearch(input, llm, startedAt) {
|
|
|
21921
22231
|
if (!credential || credential.type !== "oauth") {
|
|
21922
22232
|
throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
|
|
21923
22233
|
}
|
|
21924
|
-
|
|
22234
|
+
let copilotAccess = credential.copilotAccess;
|
|
22235
|
+
let copilotApiBaseUrl = credential.copilotApiBaseUrl;
|
|
22236
|
+
if (!copilotAccess || !credential.copilotExpires || credential.copilotExpires <= Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2) {
|
|
22237
|
+
const exchanged = await exchangeGitHubTokenForCopilotToken(
|
|
22238
|
+
credential.githubAccess || credential.refresh || credential.access,
|
|
22239
|
+
credential.enterpriseUrl
|
|
22240
|
+
);
|
|
22241
|
+
copilotAccess = exchanged.copilotAccess;
|
|
22242
|
+
copilotApiBaseUrl = exchanged.copilotApiBaseUrl;
|
|
22243
|
+
saveProviderCredential({
|
|
22244
|
+
...credential,
|
|
22245
|
+
access: exchanged.copilotAccess,
|
|
22246
|
+
refresh: exchanged.githubAccess,
|
|
22247
|
+
expires: exchanged.copilotExpires,
|
|
22248
|
+
githubAccess: exchanged.githubAccess,
|
|
22249
|
+
copilotAccess: exchanged.copilotAccess,
|
|
22250
|
+
copilotExpires: exchanged.copilotExpires,
|
|
22251
|
+
copilotApiBaseUrl: exchanged.copilotApiBaseUrl
|
|
22252
|
+
});
|
|
22253
|
+
}
|
|
22254
|
+
const baseUrl = llm.base_url?.replace(/\/+$/, "") ?? copilotApiBaseUrl?.replace(/\/+$/, "") ?? copilotApiBase(credential.enterpriseUrl);
|
|
21925
22255
|
const headers = {
|
|
21926
|
-
"Authorization": `Bearer ${
|
|
22256
|
+
"Authorization": `Bearer ${copilotAccess}`,
|
|
21927
22257
|
"Content-Type": "application/json",
|
|
21928
|
-
"User-Agent":
|
|
22258
|
+
"User-Agent": COPILOT_USER_AGENT2,
|
|
21929
22259
|
"Editor-Version": "OpenJaw/0.1.0",
|
|
21930
22260
|
"Editor-Plugin-Version": "OpenJaw/0.1.0",
|
|
21931
22261
|
"Copilot-Integration-Id": "vscode-chat",
|
|
@@ -22152,15 +22482,16 @@ function limitAndDedupe(results, max) {
|
|
|
22152
22482
|
function truncate(text) {
|
|
22153
22483
|
return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
22154
22484
|
}
|
|
22155
|
-
var
|
|
22485
|
+
var COPILOT_USER_AGENT2, DEFAULT_TIMEOUT_MS, COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2;
|
|
22156
22486
|
var init_web_search = __esm({
|
|
22157
22487
|
"src/web-search.ts"() {
|
|
22158
22488
|
"use strict";
|
|
22159
22489
|
init_web_search_types();
|
|
22490
|
+
init_copilot_token();
|
|
22160
22491
|
init_provider_auth();
|
|
22161
|
-
|
|
22162
|
-
COPILOT_USER_AGENT = "openjaw-agent/0.1.0";
|
|
22492
|
+
COPILOT_USER_AGENT2 = "openjaw-agent/0.1.0";
|
|
22163
22493
|
DEFAULT_TIMEOUT_MS = 45e3;
|
|
22494
|
+
COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2 = 6e4;
|
|
22164
22495
|
__name(createWebSearchExecutor, "createWebSearchExecutor");
|
|
22165
22496
|
__name(runCopilotResponsesSearch, "runCopilotResponsesSearch");
|
|
22166
22497
|
__name(runOpenAIResponsesSearch, "runOpenAIResponsesSearch");
|
|
@@ -48335,14 +48666,26 @@ ${helpMessage}` : field.label;
|
|
|
48335
48666
|
bus.log("info", `session.steer ${String(params.text ?? "").slice(0, 200)}`);
|
|
48336
48667
|
return { status: "queued", text: String(params.text ?? "") };
|
|
48337
48668
|
});
|
|
48669
|
+
let pendingImageSeq = 0;
|
|
48338
48670
|
let pendingImage = null;
|
|
48671
|
+
const nextImageAttachmentId = /* @__PURE__ */ __name(() => `img-${Date.now().toString(36)}-${++pendingImageSeq}`, "nextImageAttachmentId");
|
|
48339
48672
|
bus.registerRpc("prompt.submit", async (params) => {
|
|
48340
48673
|
const text = String(params.text ?? "");
|
|
48341
48674
|
if (!text) return { ok: false };
|
|
48342
48675
|
const systemPromptArr = await systemPromptFn();
|
|
48343
48676
|
const systemPrompt = systemPromptArr.join("\n\n");
|
|
48344
|
-
const
|
|
48345
|
-
|
|
48677
|
+
const requestedImageId = String(params.image_attachment_id ?? "");
|
|
48678
|
+
const imageForSubmit = pendingImage && (!requestedImageId || pendingImage.attachmentId === requestedImageId) ? pendingImage : null;
|
|
48679
|
+
if (imageForSubmit) {
|
|
48680
|
+
const modelInfo = agentLoop.getActiveModelMetadata() ?? await agentLoop.listModels().then(() => agentLoop.getActiveModelMetadata()).catch(() => void 0);
|
|
48681
|
+
if (modelInfo?.supportsVision === false) {
|
|
48682
|
+
throw new Error(`model ${agentLoop.model} does not support image input; switch to a vision-capable model before submitting an image`);
|
|
48683
|
+
}
|
|
48684
|
+
}
|
|
48685
|
+
const imageData = imageForSubmit ? { base64: imageForSubmit.base64, mimeType: imageForSubmit.mimeType } : void 0;
|
|
48686
|
+
if (imageForSubmit) {
|
|
48687
|
+
pendingImage = null;
|
|
48688
|
+
}
|
|
48346
48689
|
currentRun = { abort: /* @__PURE__ */ __name(() => agentLoop.abort(), "abort") };
|
|
48347
48690
|
void streamAgentRun({ agentLoop, bus, systemPrompt, text, imageData }).finally(() => {
|
|
48348
48691
|
currentRun = null;
|
|
@@ -48418,6 +48761,32 @@ ${helpMessage}` : field.label;
|
|
|
48418
48761
|
});
|
|
48419
48762
|
});
|
|
48420
48763
|
bus.registerRpc("clipboard.paste", async () => {
|
|
48764
|
+
try {
|
|
48765
|
+
if (clipboardHasImage()) {
|
|
48766
|
+
const img = readClipboardImage();
|
|
48767
|
+
if (img) {
|
|
48768
|
+
const attachmentId = nextImageAttachmentId();
|
|
48769
|
+
pendingImage = {
|
|
48770
|
+
attachmentId,
|
|
48771
|
+
base64: img.base64,
|
|
48772
|
+
mimeType: img.mimeType,
|
|
48773
|
+
name: "clipboard.png"
|
|
48774
|
+
};
|
|
48775
|
+
const byteLength = Math.ceil(img.base64.length * 3 / 4);
|
|
48776
|
+
const tokenEstimate = Math.max(1, Math.ceil(byteLength / 750));
|
|
48777
|
+
return {
|
|
48778
|
+
attached: true,
|
|
48779
|
+
attachment_id: attachmentId,
|
|
48780
|
+
count: 1,
|
|
48781
|
+
height: img.height,
|
|
48782
|
+
name: "clipboard.png",
|
|
48783
|
+
token_estimate: tokenEstimate,
|
|
48784
|
+
width: img.width
|
|
48785
|
+
};
|
|
48786
|
+
}
|
|
48787
|
+
}
|
|
48788
|
+
} catch {
|
|
48789
|
+
}
|
|
48421
48790
|
try {
|
|
48422
48791
|
const text = await readClipboardText();
|
|
48423
48792
|
return { attached: false, message: text ?? "" };
|
|
@@ -49042,13 +49411,16 @@ ${helpMessage}` : field.label;
|
|
|
49042
49411
|
}
|
|
49043
49412
|
const ext = extname4(path3).toLowerCase().replace(/^\./, "");
|
|
49044
49413
|
const mimeType = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/png";
|
|
49414
|
+
const attachmentId = nextImageAttachmentId();
|
|
49045
49415
|
pendingImage = {
|
|
49416
|
+
attachmentId,
|
|
49046
49417
|
base64: buffer.toString("base64"),
|
|
49047
49418
|
mimeType,
|
|
49048
49419
|
name: basename3(path3)
|
|
49049
49420
|
};
|
|
49050
49421
|
const tokenEstimate = Math.max(1, Math.ceil(buffer.byteLength / 750));
|
|
49051
49422
|
return {
|
|
49423
|
+
attachment_id: attachmentId,
|
|
49052
49424
|
height: 0,
|
|
49053
49425
|
name: basename3(path3),
|
|
49054
49426
|
remainder,
|
|
@@ -49056,6 +49428,13 @@ ${helpMessage}` : field.label;
|
|
|
49056
49428
|
width: 0
|
|
49057
49429
|
};
|
|
49058
49430
|
});
|
|
49431
|
+
bus.registerRpc("image.clear", (params) => {
|
|
49432
|
+
const attachmentId = String(params.attachment_id ?? "");
|
|
49433
|
+
if (!attachmentId || pendingImage?.attachmentId === attachmentId) {
|
|
49434
|
+
pendingImage = null;
|
|
49435
|
+
}
|
|
49436
|
+
return { ok: true };
|
|
49437
|
+
});
|
|
49059
49438
|
bus.registerRpc("process.stop", () => {
|
|
49060
49439
|
const wasRunning = agentLoop.isRunning;
|
|
49061
49440
|
agentLoop.abort();
|
|
@@ -49255,6 +49634,7 @@ var init_rpcHandlers = __esm({
|
|
|
49255
49634
|
init_uiStore();
|
|
49256
49635
|
init_catalog();
|
|
49257
49636
|
init_clipboard();
|
|
49637
|
+
init_clipboard_image();
|
|
49258
49638
|
init_models_static();
|
|
49259
49639
|
init_providers();
|
|
49260
49640
|
init_types();
|
|
@@ -52516,6 +52896,7 @@ function useComposerState({
|
|
|
52516
52896
|
}) {
|
|
52517
52897
|
const [input, setInput] = useState12("");
|
|
52518
52898
|
const [inputBuf, setInputBuf] = useState12([]);
|
|
52899
|
+
const [attachedImage, setAttachedImage] = useState12(null);
|
|
52519
52900
|
const [pasteSnips, setPasteSnips] = useState12([]);
|
|
52520
52901
|
const isBlocked = useStore($isBlocked);
|
|
52521
52902
|
const { querier } = use_stdin_default();
|
|
@@ -52533,14 +52914,25 @@ function useComposerState({
|
|
|
52533
52914
|
} = useQueue();
|
|
52534
52915
|
const { historyRef, historyIdx, setHistoryIdx, historyDraftRef, pushHistory } = useInputHistory();
|
|
52535
52916
|
const { completions, compIdx, setCompIdx, compReplace } = useCompletion(input, isBlocked, gw);
|
|
52536
|
-
const
|
|
52917
|
+
const clearAttachedImage = useCallback7(() => {
|
|
52918
|
+
const attachmentId = attachedImage?.attachment_id;
|
|
52919
|
+
setAttachedImage(null);
|
|
52920
|
+
if (attachmentId) {
|
|
52921
|
+
void gw.request("image.clear", { attachment_id: attachmentId }).catch(() => {
|
|
52922
|
+
});
|
|
52923
|
+
}
|
|
52924
|
+
}, [attachedImage?.attachment_id, gw]);
|
|
52925
|
+
const clearIn = useCallback7((options = {}) => {
|
|
52537
52926
|
setInput("");
|
|
52538
52927
|
setInputBuf([]);
|
|
52539
52928
|
setPasteSnips([]);
|
|
52929
|
+
if (!options.keepAttachedImage) {
|
|
52930
|
+
clearAttachedImage();
|
|
52931
|
+
}
|
|
52540
52932
|
setQueueEdit(null);
|
|
52541
52933
|
setHistoryIdx(null);
|
|
52542
52934
|
historyDraftRef.current = "";
|
|
52543
|
-
}, [historyDraftRef, setQueueEdit, setHistoryIdx]);
|
|
52935
|
+
}, [clearAttachedImage, historyDraftRef, setQueueEdit, setHistoryIdx]);
|
|
52544
52936
|
const handleResolvedPaste = useCallback7(
|
|
52545
52937
|
async ({
|
|
52546
52938
|
bracketed,
|
|
@@ -52563,6 +52955,13 @@ function useComposerState({
|
|
|
52563
52955
|
session_id: sid
|
|
52564
52956
|
});
|
|
52565
52957
|
if (attached?.name) {
|
|
52958
|
+
setAttachedImage({
|
|
52959
|
+
attachment_id: attached.attachment_id,
|
|
52960
|
+
height: attached.height,
|
|
52961
|
+
name: attached.name,
|
|
52962
|
+
token_estimate: attached.token_estimate,
|
|
52963
|
+
width: attached.width
|
|
52964
|
+
});
|
|
52566
52965
|
onImageAttached?.(attached);
|
|
52567
52966
|
const remainder = attached.remainder?.trim() ?? "";
|
|
52568
52967
|
if (!remainder) {
|
|
@@ -52615,7 +53014,7 @@ function useComposerState({
|
|
|
52615
53014
|
}) => {
|
|
52616
53015
|
if (hotkey) {
|
|
52617
53016
|
const preferOsc52 = isRemoteShellSession(process.env);
|
|
52618
|
-
const readPreferredText = preferOsc52 ? readOsc52Clipboard(querier).then(async (osc52Text) => {
|
|
53017
|
+
const readPreferredText = /* @__PURE__ */ __name(() => preferOsc52 ? readOsc52Clipboard(querier).then(async (osc52Text) => {
|
|
52619
53018
|
if (isUsableClipboardText(osc52Text)) {
|
|
52620
53019
|
return osc52Text;
|
|
52621
53020
|
}
|
|
@@ -52625,8 +53024,12 @@ function useComposerState({
|
|
|
52625
53024
|
return clipText;
|
|
52626
53025
|
}
|
|
52627
53026
|
return readOsc52Clipboard(querier);
|
|
52628
|
-
});
|
|
52629
|
-
return
|
|
53027
|
+
}), "readPreferredText");
|
|
53028
|
+
return Promise.resolve(onClipboardPaste(true)).then(async (imageAttached) => {
|
|
53029
|
+
if (imageAttached) {
|
|
53030
|
+
return null;
|
|
53031
|
+
}
|
|
53032
|
+
const preferredText = await readPreferredText();
|
|
52630
53033
|
if (isUsableClipboardText(preferredText)) {
|
|
52631
53034
|
return handleResolvedPaste({ bracketed: false, cursor, text: preferredText, value });
|
|
52632
53035
|
}
|
|
@@ -52664,6 +53067,7 @@ function useComposerState({
|
|
|
52664
53067
|
}, [input, inputBuf, submitRef]);
|
|
52665
53068
|
const actions = useMemo6(
|
|
52666
53069
|
() => ({
|
|
53070
|
+
clearAttachedImage,
|
|
52667
53071
|
clearIn,
|
|
52668
53072
|
dequeue,
|
|
52669
53073
|
enqueue,
|
|
@@ -52674,6 +53078,7 @@ function useComposerState({
|
|
|
52674
53078
|
replaceQueue: replaceQ,
|
|
52675
53079
|
setCompIdx,
|
|
52676
53080
|
setHistoryIdx,
|
|
53081
|
+
setAttachedImage,
|
|
52677
53082
|
setInput,
|
|
52678
53083
|
setInputBuf,
|
|
52679
53084
|
setPasteSnips,
|
|
@@ -52681,6 +53086,7 @@ function useComposerState({
|
|
|
52681
53086
|
syncQueue
|
|
52682
53087
|
}),
|
|
52683
53088
|
[
|
|
53089
|
+
clearAttachedImage,
|
|
52684
53090
|
clearIn,
|
|
52685
53091
|
dequeue,
|
|
52686
53092
|
enqueue,
|
|
@@ -52707,6 +53113,7 @@ function useComposerState({
|
|
|
52707
53113
|
);
|
|
52708
53114
|
const state = useMemo6(
|
|
52709
53115
|
() => ({
|
|
53116
|
+
attachedImage,
|
|
52710
53117
|
compIdx,
|
|
52711
53118
|
compReplace,
|
|
52712
53119
|
completions,
|
|
@@ -52717,7 +53124,7 @@ function useComposerState({
|
|
|
52717
53124
|
queueEditIdx,
|
|
52718
53125
|
queuedDisplay
|
|
52719
53126
|
}),
|
|
52720
|
-
[compIdx, compReplace, completions, historyIdx, input, inputBuf, pasteSnips, queueEditIdx, queuedDisplay]
|
|
53127
|
+
[attachedImage, compIdx, compReplace, completions, historyIdx, input, inputBuf, pasteSnips, queueEditIdx, queuedDisplay]
|
|
52721
53128
|
);
|
|
52722
53129
|
return {
|
|
52723
53130
|
actions,
|
|
@@ -53306,6 +53713,9 @@ function useInputHandlers(ctx) {
|
|
|
53306
53713
|
if (key.escape && terminal.hasSelection) {
|
|
53307
53714
|
return clearSelection2();
|
|
53308
53715
|
}
|
|
53716
|
+
if (key.escape && cState.attachedImage) {
|
|
53717
|
+
return cActions.clearAttachedImage();
|
|
53718
|
+
}
|
|
53309
53719
|
if (key.escape && live.focusedPane === "transcript") {
|
|
53310
53720
|
patchUiState({ focusedPane: "composer" });
|
|
53311
53721
|
return;
|
|
@@ -53354,7 +53764,7 @@ function useInputHandlers(ctx) {
|
|
|
53354
53764
|
sys: actions.sys
|
|
53355
53765
|
});
|
|
53356
53766
|
}
|
|
53357
|
-
if (cState.input || cState.inputBuf.length) {
|
|
53767
|
+
if (cState.input || cState.inputBuf.length || cState.attachedImage) {
|
|
53358
53768
|
return cActions.clearIn();
|
|
53359
53769
|
}
|
|
53360
53770
|
return actions.die();
|
|
@@ -53544,6 +53954,7 @@ function useSessionLifecycle(opts) {
|
|
|
53544
53954
|
setHistoryItems([]);
|
|
53545
53955
|
setLastUserMsg("");
|
|
53546
53956
|
setStickyPrompt("");
|
|
53957
|
+
composerActions.clearAttachedImage();
|
|
53547
53958
|
composerActions.setPasteSnips([]);
|
|
53548
53959
|
evictInkCaches("half");
|
|
53549
53960
|
}, [composerActions, setHistoryItems, setLastUserMsg, setStickyPrompt, setVoiceProcessing, setVoiceRecording]);
|
|
@@ -53556,6 +53967,7 @@ function useSessionLifecycle(opts) {
|
|
|
53556
53967
|
setHistoryItems(info ? [introMsg(info)] : []);
|
|
53557
53968
|
setStickyPrompt("");
|
|
53558
53969
|
setLastUserMsg("");
|
|
53970
|
+
composerActions.clearAttachedImage();
|
|
53559
53971
|
composerActions.setPasteSnips([]);
|
|
53560
53972
|
patchTurnState({ activity: [] });
|
|
53561
53973
|
patchUiState({ info, usage: usageFrom(info) });
|
|
@@ -53786,6 +54198,7 @@ function useSubmission(opts) {
|
|
|
53786
54198
|
const expand = expandSnips(composerState.pasteSnips);
|
|
53787
54199
|
const startSubmit = /* @__PURE__ */ __name((displayText, submitText, showUserMessage2 = true) => {
|
|
53788
54200
|
const sid2 = getUiState().sid;
|
|
54201
|
+
const imageAttachment = composerState.attachedImage;
|
|
53789
54202
|
if (!sid2) {
|
|
53790
54203
|
return sys("session not ready yet");
|
|
53791
54204
|
}
|
|
@@ -53798,7 +54211,20 @@ function useSubmission(opts) {
|
|
|
53798
54211
|
patchUiState({ busy: true, status: "running\u2026" });
|
|
53799
54212
|
turnController.bufRef = "";
|
|
53800
54213
|
turnController.interrupted = false;
|
|
53801
|
-
|
|
54214
|
+
const submitParams = { session_id: sid2, text: submitText };
|
|
54215
|
+
if (imageAttachment?.attachment_id) {
|
|
54216
|
+
submitParams.image_attachment_id = imageAttachment.attachment_id;
|
|
54217
|
+
}
|
|
54218
|
+
if (imageAttachment) {
|
|
54219
|
+
composerActions.setAttachedImage(null);
|
|
54220
|
+
}
|
|
54221
|
+
gw.request("prompt.submit", submitParams).catch((e) => {
|
|
54222
|
+
if (imageAttachment) {
|
|
54223
|
+
composerActions.setAttachedImage(imageAttachment);
|
|
54224
|
+
sys(`error: ${e.message}`);
|
|
54225
|
+
patchUiState({ busy: false, status: "ready" });
|
|
54226
|
+
return;
|
|
54227
|
+
}
|
|
53802
54228
|
if (isSessionBusyError(e)) {
|
|
53803
54229
|
composerActions.enqueue(submitText);
|
|
53804
54230
|
patchUiState({ busy: true, status: "queued for next turn" });
|
|
@@ -53824,7 +54250,7 @@ function useSubmission(opts) {
|
|
|
53824
54250
|
startSubmit(r.text || text, expand(r.text || text), showUserMessage);
|
|
53825
54251
|
}).catch(() => startSubmit(text, expand(text), showUserMessage));
|
|
53826
54252
|
},
|
|
53827
|
-
[appendMessage, composerActions, composerState.pasteSnips, gw, maybeGoodVibes, setLastUserMsg, sys]
|
|
54253
|
+
[appendMessage, composerActions, composerState.attachedImage, composerState.pasteSnips, gw, maybeGoodVibes, setLastUserMsg, sys]
|
|
53828
54254
|
);
|
|
53829
54255
|
const shellExec = useCallback9(
|
|
53830
54256
|
(cmd) => {
|
|
@@ -53887,6 +54313,16 @@ function useSubmission(opts) {
|
|
|
53887
54313
|
}
|
|
53888
54314
|
sys(note);
|
|
53889
54315
|
}, "fallback");
|
|
54316
|
+
if (composerState.attachedImage) {
|
|
54317
|
+
if (live.sid) {
|
|
54318
|
+
turnController.interruptTurn({ appendMessage, gw, sid: live.sid, sys });
|
|
54319
|
+
}
|
|
54320
|
+
if (hasInterpolation(full)) {
|
|
54321
|
+
patchUiState({ busy: true });
|
|
54322
|
+
return interpolate(full, send);
|
|
54323
|
+
}
|
|
54324
|
+
return send(full);
|
|
54325
|
+
}
|
|
53890
54326
|
if (mode === "queue") {
|
|
53891
54327
|
return composerActions.enqueue(full);
|
|
53892
54328
|
}
|
|
@@ -53908,7 +54344,7 @@ function useSubmission(opts) {
|
|
|
53908
54344
|
}
|
|
53909
54345
|
send(full);
|
|
53910
54346
|
},
|
|
53911
|
-
[appendMessage, composerActions, composerRefs, gw, interpolate, send, sys]
|
|
54347
|
+
[appendMessage, composerActions, composerRefs, composerState.attachedImage, gw, interpolate, send, sys]
|
|
53912
54348
|
);
|
|
53913
54349
|
const dispatchSubmission = useCallback9(
|
|
53914
54350
|
(full) => {
|
|
@@ -53933,8 +54369,8 @@ function useSubmission(opts) {
|
|
|
53933
54369
|
return;
|
|
53934
54370
|
}
|
|
53935
54371
|
const editIdx = composerRefs.queueEditRef.current;
|
|
53936
|
-
composerActions.clearIn();
|
|
53937
54372
|
if (editIdx !== null) {
|
|
54373
|
+
composerActions.clearIn();
|
|
53938
54374
|
composerActions.replaceQueue(editIdx, full);
|
|
53939
54375
|
const picked = composerRefs.queueRef.current.splice(editIdx, 1)[0];
|
|
53940
54376
|
composerActions.syncQueue();
|
|
@@ -53951,6 +54387,7 @@ function useSubmission(opts) {
|
|
|
53951
54387
|
}
|
|
53952
54388
|
return sendQueued(picked);
|
|
53953
54389
|
}
|
|
54390
|
+
composerActions.clearIn({ keepAttachedImage: !!composerState.attachedImage });
|
|
53954
54391
|
composerActions.pushHistory(full);
|
|
53955
54392
|
if (getUiState().busy) {
|
|
53956
54393
|
return handleBusyInput(full);
|
|
@@ -53961,7 +54398,7 @@ function useSubmission(opts) {
|
|
|
53961
54398
|
}
|
|
53962
54399
|
send(full);
|
|
53963
54400
|
},
|
|
53964
|
-
[appendMessage, composerActions, composerRefs, handleBusyInput, interpolate, send, sendQueued, shellExec, slashRef]
|
|
54401
|
+
[appendMessage, composerActions, composerRefs, composerState.attachedImage, handleBusyInput, interpolate, send, sendQueued, shellExec, slashRef]
|
|
53965
54402
|
);
|
|
53966
54403
|
const submit = useCallback9(
|
|
53967
54404
|
(value) => {
|
|
@@ -54079,8 +54516,7 @@ function useMainApp(gw) {
|
|
|
54079
54516
|
const scrollRef = useRef13(null);
|
|
54080
54517
|
const onEventRef = useRef13(() => {
|
|
54081
54518
|
});
|
|
54082
|
-
const clipboardPasteRef = useRef13(() =>
|
|
54083
|
-
});
|
|
54519
|
+
const clipboardPasteRef = useRef13(() => false);
|
|
54084
54520
|
const submitRef = useRef13(() => {
|
|
54085
54521
|
});
|
|
54086
54522
|
const terminalHintsShownRef = useRef13(/* @__PURE__ */ new Set());
|
|
@@ -54133,9 +54569,7 @@ function useMainApp(gw) {
|
|
|
54133
54569
|
const composer = useComposerState({
|
|
54134
54570
|
gw,
|
|
54135
54571
|
onClipboardPaste: /* @__PURE__ */ __name((quiet) => clipboardPasteRef.current(quiet), "onClipboardPaste"),
|
|
54136
|
-
onImageAttached: /* @__PURE__ */ __name((
|
|
54137
|
-
sys(attachedImageNotice(info));
|
|
54138
|
-
}, "onImageAttached"),
|
|
54572
|
+
onImageAttached: /* @__PURE__ */ __name(() => patchUiState({ status: "image attached" }), "onImageAttached"),
|
|
54139
54573
|
submitRef
|
|
54140
54574
|
});
|
|
54141
54575
|
const { actions: composerActions, refs: composerRefs, state: composerState } = composer;
|
|
@@ -54343,17 +54777,25 @@ function useMainApp(gw) {
|
|
|
54343
54777
|
const paste2 = useCallback10(
|
|
54344
54778
|
(quiet = false) => rpc("clipboard.paste", { session_id: getUiState().sid }).then((r) => {
|
|
54345
54779
|
if (!r) {
|
|
54346
|
-
return;
|
|
54780
|
+
return false;
|
|
54347
54781
|
}
|
|
54348
54782
|
if (r.attached) {
|
|
54349
|
-
|
|
54350
|
-
|
|
54783
|
+
composerActions.setAttachedImage({
|
|
54784
|
+
attachment_id: r.attachment_id,
|
|
54785
|
+
height: r.height,
|
|
54786
|
+
name: r.name ?? `Image #${r.count ?? 1}`,
|
|
54787
|
+
token_estimate: r.token_estimate,
|
|
54788
|
+
width: r.width
|
|
54789
|
+
});
|
|
54790
|
+
patchUiState({ status: "image attached" });
|
|
54791
|
+
return true;
|
|
54351
54792
|
}
|
|
54352
54793
|
if (!quiet) {
|
|
54353
54794
|
sys(r.message || "No image found in clipboard");
|
|
54354
54795
|
}
|
|
54796
|
+
return false;
|
|
54355
54797
|
}),
|
|
54356
|
-
[rpc, sys]
|
|
54798
|
+
[composerActions, rpc, sys]
|
|
54357
54799
|
);
|
|
54358
54800
|
clipboardPasteRef.current = paste2;
|
|
54359
54801
|
const { dispatchSubmission, send, sendQueued, submit } = useSubmission({
|
|
@@ -54593,6 +55035,7 @@ function useMainApp(gw) {
|
|
|
54593
55035
|
cols,
|
|
54594
55036
|
compIdx: composerState.compIdx,
|
|
54595
55037
|
completions: composerState.completions,
|
|
55038
|
+
attachedImage: composerState.attachedImage,
|
|
54596
55039
|
empty,
|
|
54597
55040
|
handleTextPaste: composerActions.handleTextPaste,
|
|
54598
55041
|
input: composerState.input,
|
|
@@ -54649,7 +55092,6 @@ var init_useMainApp = __esm({
|
|
|
54649
55092
|
init_env();
|
|
54650
55093
|
init_limits();
|
|
54651
55094
|
init_details();
|
|
54652
|
-
init_messages();
|
|
54653
55095
|
init_paths();
|
|
54654
55096
|
init_useGitBranch();
|
|
54655
55097
|
init_useVirtualHistory();
|
|
@@ -61954,6 +62396,7 @@ var init_appLayout = __esm({
|
|
|
61954
62396
|
init_env();
|
|
61955
62397
|
init_limits();
|
|
61956
62398
|
init_placeholders();
|
|
62399
|
+
init_messages();
|
|
61957
62400
|
init_inputMetrics();
|
|
61958
62401
|
init_perfPane();
|
|
61959
62402
|
init_agentsOverlay();
|
|
@@ -62073,6 +62516,7 @@ var init_appLayout = __esm({
|
|
|
62073
62516
|
const inputColumns = stableComposerColumns(composer.cols, promptWidth);
|
|
62074
62517
|
const inputHeight = inputVisualHeight(composer.input, inputColumns);
|
|
62075
62518
|
const inputMouseRef = useRef19(null);
|
|
62519
|
+
const attachedImageMeta = composer.attachedImage ? imageTokenMeta(composer.attachedImage) : "";
|
|
62076
62520
|
const captureInputDrag = /* @__PURE__ */ __name((e) => {
|
|
62077
62521
|
if (e.button !== 0) {
|
|
62078
62522
|
return;
|
|
@@ -62143,6 +62587,12 @@ var init_appLayout = __esm({
|
|
|
62143
62587
|
),
|
|
62144
62588
|
composer.input === "?" && !composer.inputBuf.length && /* @__PURE__ */ jsx41(HelpHint, { t: ui.theme }),
|
|
62145
62589
|
!isBlocked && /* @__PURE__ */ jsxs28(Fragment11, { children: [
|
|
62590
|
+
composer.attachedImage && /* @__PURE__ */ jsx41(Box_default, { width: Math.max(1, composer.cols - 2), children: /* @__PURE__ */ jsxs28(Text9, { color: ui.theme.color.systemNote, wrap: "truncate-end", children: [
|
|
62591
|
+
"\u{1F4CE} ",
|
|
62592
|
+
composer.attachedImage.name,
|
|
62593
|
+
attachedImageMeta ? ` \xB7 ${attachedImageMeta}` : "",
|
|
62594
|
+
/* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.muted, children: " \xB7 Esc to remove" })
|
|
62595
|
+
] }) }),
|
|
62146
62596
|
composer.inputBuf.map((line, i) => /* @__PURE__ */ jsxs28(Box_default, { children: [
|
|
62147
62597
|
/* @__PURE__ */ jsx41(Box_default, { width: promptWidth, children: i === 0 ? /* @__PURE__ */ jsx41(PromptPrefix, { color: ui.theme.color.muted, promptText, width: promptWidth }) : /* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.muted, children: promptBlank }) }),
|
|
62148
62598
|
/* @__PURE__ */ jsx41(Text9, { color: ui.theme.color.composeText, children: line || " " })
|