xiaodcs-copilot-api 2.3.9-public.3 → 2.5.1-public.2

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.
@@ -0,0 +1,442 @@
1
+ import { c as makeImage, d as BridgeError, f as record, l as validateImageUrl, s as decodeDataImage, u as clientToolServer } from "./protocol-BTFLbI8m.js";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { accessToken, qodercliAuth, query } from "@qoder-ai/qoder-agent-sdk";
6
+ import { lookup } from "node:dns/promises";
7
+ import { request } from "node:https";
8
+ import { BlockList, isIP } from "node:net";
9
+ import { Readable } from "node:stream";
10
+ //#region qoder-bridge/model-policy.ts
11
+ function qoderModelPolicy(input) {
12
+ return (context) => {
13
+ const model = context.availableModels.find((m) => m.value === input.model);
14
+ if (input.contextWindow && !model?.availableContextWindows?.includes(input.contextWindow)) throw new BridgeError("Unsupported Qoder context window");
15
+ const efforts = model?.efforts ?? [];
16
+ let effort = input.reasoningEffort;
17
+ if (!efforts.length || effort === "none") effort = void 0;
18
+ else if (effort && !efforts.includes(effort)) {
19
+ if (![
20
+ "ultra",
21
+ "max",
22
+ "xhigh"
23
+ ].includes(effort)) throw new BridgeError("Unsupported Qoder reasoning effort");
24
+ effort = [
25
+ "max",
26
+ "xhigh",
27
+ "high",
28
+ "medium",
29
+ "low"
30
+ ].find((e) => efforts.includes(e));
31
+ if (!effort) throw new BridgeError("Unsupported Qoder reasoning effort");
32
+ }
33
+ return {
34
+ model: input.model,
35
+ parameters: {
36
+ ...input.contextWindow ? { contextWindow: input.contextWindow } : {},
37
+ ...effort ? { reasoningEffort: effort } : {}
38
+ }
39
+ };
40
+ };
41
+ }
42
+ //#endregion
43
+ //#region qoder-bridge/remote-images.ts
44
+ const denied = new BlockList();
45
+ for (const [address, prefix] of [
46
+ ["0.0.0.0", 8],
47
+ ["10.0.0.0", 8],
48
+ ["100.64.0.0", 10],
49
+ ["127.0.0.0", 8],
50
+ ["169.254.0.0", 16],
51
+ ["172.16.0.0", 12],
52
+ ["192.0.0.0", 24],
53
+ ["192.0.2.0", 24],
54
+ ["192.88.99.0", 24],
55
+ ["192.168.0.0", 16],
56
+ ["198.18.0.0", 15],
57
+ ["198.51.100.0", 24],
58
+ ["203.0.113.0", 24],
59
+ ["224.0.0.0", 4],
60
+ ["240.0.0.0", 4]
61
+ ]) denied.addSubnet(address, prefix, "ipv4");
62
+ for (const [address, prefix] of [
63
+ ["2001::", 23],
64
+ ["2001:db8::", 32],
65
+ ["2002::", 16],
66
+ ["3fff::", 20]
67
+ ]) denied.addSubnet(address, prefix, "ipv6");
68
+ function isPublicAddress(address) {
69
+ const family = isIP(address);
70
+ if (family === 4) return !denied.check(address, "ipv4");
71
+ return family === 6 && /^[23][0-9a-f]{3}:/i.test(address) && !denied.check(address, "ipv6");
72
+ }
73
+ function fetchPinned(url, address, signal, send = request) {
74
+ return new Promise((resolve, reject) => {
75
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
76
+ const req = send({
77
+ hostname: address,
78
+ port: 443,
79
+ servername: isIP(hostname) ? void 0 : hostname,
80
+ path: url.pathname + url.search,
81
+ method: "GET",
82
+ headers: {
83
+ host: url.host,
84
+ accept: "image/png,image/jpeg,image/webp,image/gif"
85
+ },
86
+ agent: false,
87
+ signal
88
+ }, (response) => {
89
+ const headers = new Headers();
90
+ for (const [key, value] of Object.entries(response.headers)) if (value !== void 0) headers.set(key, Array.isArray(value) ? value.join(",") : value);
91
+ try {
92
+ const status = response.statusCode ?? 502;
93
+ const empty = [
94
+ 204,
95
+ 205,
96
+ 304
97
+ ].includes(status);
98
+ if (empty) response.resume();
99
+ resolve(new Response(empty ? null : Readable.toWeb(response), {
100
+ status,
101
+ headers
102
+ }));
103
+ } catch (error) {
104
+ response.destroy();
105
+ reject(error instanceof Error ? error : /* @__PURE__ */ new Error("Invalid image response"));
106
+ }
107
+ });
108
+ req.on("error", reject);
109
+ req.end();
110
+ });
111
+ }
112
+ function resolveImageHost(hostname) {
113
+ return lookup(hostname, { all: true });
114
+ }
115
+ const defaults = {
116
+ resolve: resolveImageHost,
117
+ fetchPinned
118
+ };
119
+ async function loadRemoteImage(raw, signal, deps = defaults) {
120
+ const bounded = AbortSignal.any([signal, AbortSignal.timeout(15e3)]);
121
+ let url = validateImageUrl(raw);
122
+ try {
123
+ for (let redirects = 0; redirects <= 3; redirects++) {
124
+ bounded.throwIfAborted();
125
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
126
+ const addresses = await deps.resolve(hostname);
127
+ bounded.throwIfAborted();
128
+ if (!addresses.length || addresses.some((item) => !isPublicAddress(item.address))) throw new BridgeError("Image URL must resolve exclusively to public IP addresses");
129
+ const response = await deps.fetchPinned(url, addresses[0].address, bounded);
130
+ if ([
131
+ 301,
132
+ 302,
133
+ 303,
134
+ 307,
135
+ 308
136
+ ].includes(response.status)) {
137
+ await response.body?.cancel();
138
+ const location = response.headers.get("location");
139
+ if (!location || redirects === 3) throw new BridgeError("Image URL redirect limit exceeded or missing location");
140
+ url = validateImageUrl(new URL(location, url).href);
141
+ continue;
142
+ }
143
+ if (!response.ok) {
144
+ await response.body?.cancel();
145
+ throw new BridgeError("Image URL returned an unsuccessful HTTP status");
146
+ }
147
+ if (Number(response.headers.get("content-length")) > 5242880) {
148
+ await response.body?.cancel();
149
+ throw new BridgeError("Image exceeds the 5 MiB limit", 413);
150
+ }
151
+ if (!response.body) throw new BridgeError("Image URL returned an empty body");
152
+ const reader = response.body.getReader();
153
+ const chunks = [];
154
+ let size = 0;
155
+ try {
156
+ while (true) {
157
+ bounded.throwIfAborted();
158
+ const { done, value } = await reader.read();
159
+ if (done) break;
160
+ size += value.byteLength;
161
+ if (size > 5242880) throw new BridgeError("Image exceeds the 5 MiB limit", 413);
162
+ chunks.push(value);
163
+ }
164
+ } finally {
165
+ await reader.cancel().catch(() => {});
166
+ reader.releaseLock();
167
+ }
168
+ return makeImage(Buffer.concat(chunks), response.headers.get("content-type")?.split(";", 1)[0].trim() ?? "");
169
+ }
170
+ throw new BridgeError("Image URL redirect limit exceeded");
171
+ } catch (error) {
172
+ if (error instanceof BridgeError) throw error;
173
+ throw new BridgeError(bounded.aborted ? "Image download cancelled or timed out" : "Image download failed");
174
+ }
175
+ }
176
+ //#endregion
177
+ //#region qoder-bridge/image-prompt.ts
178
+ async function buildSdkPrompt(input, signal, download = loadRemoteImage) {
179
+ if (!input.images?.length) return input.prompt;
180
+ const content = [{
181
+ type: "text",
182
+ text: input.prompt
183
+ }];
184
+ let total = 0;
185
+ for (const item of input.images) {
186
+ signal.throwIfAborted();
187
+ const image = item.url.startsWith("data:") ? decodeDataImage(item.url) : await download(item.url, signal);
188
+ total += Buffer.from(image.source.data, "base64").byteLength;
189
+ if (total > 15728640) throw new BridgeError("Combined images exceed the 15 MiB limit", 413);
190
+ content.push({
191
+ type: "text",
192
+ text: `[${item.label}]`
193
+ }, image);
194
+ }
195
+ return (async function* () {
196
+ signal.throwIfAborted();
197
+ yield {
198
+ type: "user",
199
+ message: {
200
+ role: "user",
201
+ content
202
+ },
203
+ parent_tool_use_id: null
204
+ };
205
+ })();
206
+ }
207
+ //#endregion
208
+ //#region qoder-bridge/runtime.ts
209
+ function sdkOptions(cwd, controller, personalAccessToken) {
210
+ const token = (personalAccessToken ?? process.env.QODER_PERSONAL_ACCESS_TOKEN)?.trim();
211
+ return {
212
+ auth: token ? accessToken(token) : qodercliAuth(),
213
+ cwd,
214
+ abortController: controller,
215
+ tools: [],
216
+ allowedTools: [],
217
+ skills: [],
218
+ plugins: [],
219
+ extensions: [],
220
+ agents: {},
221
+ mcpServers: {},
222
+ allowedMcpServerNames: [],
223
+ strictMcpConfig: true,
224
+ settingSources: [],
225
+ persistSession: false,
226
+ includePartialMessages: true,
227
+ maxTurns: 1,
228
+ permissionMode: "default",
229
+ canUseTool: () => Promise.resolve({
230
+ behavior: "deny",
231
+ message: "Tools are disabled in this gateway"
232
+ }),
233
+ memory: {
234
+ mode: "custom",
235
+ projectScope: false,
236
+ userScope: false,
237
+ generation: { enabled: false },
238
+ consumption: { enabled: false }
239
+ },
240
+ evolution: { skill: {
241
+ mode: "custom",
242
+ generation: { enabled: false }
243
+ } },
244
+ proxy: process.env.QODER_PROXY ?? process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY,
245
+ ...process.env.QODERCLI_PATH ? { pathToQoderCLIExecutable: process.env.QODERCLI_PATH } : {}
246
+ };
247
+ }
248
+ var EventTranslator = class {
249
+ clientNames;
250
+ constructor(clientNames = /* @__PURE__ */ new Map()) {
251
+ this.clientNames = clientNames;
252
+ }
253
+ streamed = false;
254
+ completed = false;
255
+ *accept(value) {
256
+ if (this.completed) return;
257
+ const message = record(value);
258
+ if (message.type === "stream_event") {
259
+ const event = record(message.event);
260
+ if (event.type === "message_start") this.streamed = false;
261
+ const delta = record(event.delta);
262
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
263
+ this.streamed = true;
264
+ yield {
265
+ type: "text",
266
+ text: delta.text
267
+ };
268
+ }
269
+ if (record(event.content_block).type === "tool_use" && !this.clientNames.has(String(record(event.content_block).name))) throw new BridgeError("Qoder attempted an unsupported tool call", 502, "unsupported_tool_call");
270
+ }
271
+ if (message.type === "assistant") {
272
+ const content = record(message.message).content;
273
+ const calls = [];
274
+ if (Array.isArray(content)) for (const part of content) {
275
+ const block = record(part);
276
+ if (block.type === "tool_use" && this.clientNames.has(String(block.name))) {
277
+ if (typeof block.id !== "string") throw new BridgeError("Qoder tool call is missing an id", 502);
278
+ calls.push({
279
+ id: block.id,
280
+ type: "function",
281
+ function: {
282
+ name: this.clientNames.get(String(block.name)),
283
+ arguments: JSON.stringify(block.input ?? {})
284
+ }
285
+ });
286
+ continue;
287
+ }
288
+ if (block.type === "tool_use") throw new BridgeError("Qoder attempted an unsupported tool call", 502, "unsupported_tool_call");
289
+ if (!this.streamed && block.type === "text" && typeof block.text === "string") yield {
290
+ type: "text",
291
+ text: block.text
292
+ };
293
+ }
294
+ if (calls.length) yield {
295
+ type: "tool_calls",
296
+ calls
297
+ };
298
+ }
299
+ if (message.type === "result") {
300
+ if (message.subtype !== "success" || message.is_error === true) throw new BridgeError("Qoder did not complete the request successfully", 502, "qoder_result_error");
301
+ this.completed = true;
302
+ const info = { qoder_usage: {} };
303
+ if (typeof message.total_credits === "number" && Number.isFinite(message.total_credits) && message.total_credits >= 0) info.qoder_usage.total_credits = message.total_credits;
304
+ if (message.modelUsage) info.qoder_usage.model_usage = record(message.modelUsage);
305
+ const usage = record(message.usage);
306
+ const counters = [
307
+ usage.input_tokens,
308
+ usage.output_tokens,
309
+ usage.cache_read_input_tokens ?? 0,
310
+ usage.cache_creation_input_tokens ?? 0
311
+ ];
312
+ if (counters.every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0)) {
313
+ const [input, output, cacheRead, cacheWrite] = counters;
314
+ if (input + output + cacheRead + cacheWrite > 0) info.usage = {
315
+ prompt_tokens: input + cacheRead + cacheWrite,
316
+ completion_tokens: output,
317
+ total_tokens: input + output + cacheRead + cacheWrite
318
+ };
319
+ }
320
+ yield {
321
+ type: "complete",
322
+ info
323
+ };
324
+ }
325
+ }
326
+ assertComplete() {
327
+ if (!this.completed) throw new BridgeError("Qoder stream ended without a successful result", 502, "incomplete_stream");
328
+ }
329
+ };
330
+ async function workspace(signal, token) {
331
+ signal.throwIfAborted();
332
+ const cwd = await mkdtemp(join(tmpdir(), "copilot-qoder-"));
333
+ const controller = new AbortController();
334
+ const abort = () => controller.abort(signal.reason);
335
+ signal.addEventListener("abort", abort, { once: true });
336
+ if (signal.aborted) abort();
337
+ return {
338
+ options: sdkOptions(cwd, controller, token),
339
+ cleanup: async () => {
340
+ signal.removeEventListener("abort", abort);
341
+ await rm(cwd, {
342
+ recursive: true,
343
+ force: true
344
+ });
345
+ }
346
+ };
347
+ }
348
+ function createBackend(runQuery = query, token) {
349
+ return {
350
+ async catalog(signal) {
351
+ const session = await workspace(signal, token);
352
+ let release = () => {};
353
+ const idle = new Promise((resolve) => {
354
+ release = resolve;
355
+ });
356
+ let q;
357
+ try {
358
+ q = runQuery({
359
+ prompt: (async function* () {
360
+ await idle;
361
+ })(),
362
+ options: session.options
363
+ });
364
+ await q.initializationResult();
365
+ const models = await q.getAvailableModels({ fetchStrategy: "live" });
366
+ const usage = await q.getUsageInfo();
367
+ return {
368
+ models: models.filter((m) => m.isEnabled !== false).map((m) => ({
369
+ id: m.value,
370
+ name: m.displayName,
371
+ context_window: m.defaultContextWindow ?? Math.min(...m.availableContextWindows?.length ? m.availableContextWindows : [m.maxInputTokens ?? 128e3]),
372
+ max_input_tokens: m.maxInputTokens,
373
+ max_output_tokens: m.maxOutputTokens,
374
+ available_context_windows: m.availableContextWindows,
375
+ input_modalities: m.isVl ? ["text", "image"] : ["text"]
376
+ })),
377
+ usage
378
+ };
379
+ } finally {
380
+ release();
381
+ try {
382
+ await q?.close();
383
+ } finally {
384
+ await session.cleanup();
385
+ }
386
+ }
387
+ },
388
+ async *turn(input, signal) {
389
+ const session = await workspace(signal, token);
390
+ let q;
391
+ const client = clientToolServer(input.tools ?? []);
392
+ const translator = new EventTranslator(client.names);
393
+ try {
394
+ q = runQuery({
395
+ prompt: await buildSdkPrompt(input, signal),
396
+ options: {
397
+ ...session.options,
398
+ model: input.model,
399
+ ...input.tools?.length ? {
400
+ mcpServers: { client: client.config },
401
+ allowedMcpServerNames: ["client"],
402
+ allowedTools: [...client.names.keys()],
403
+ canUseTool: (name, args) => Promise.resolve(client.names.has(name) ? {
404
+ behavior: "allow",
405
+ updatedInput: args
406
+ } : {
407
+ behavior: "deny",
408
+ message: "Only client tools are allowed"
409
+ })
410
+ } : {},
411
+ ...input.contextWindow || input.reasoningEffort ? { resolveModel: qoderModelPolicy(input) } : {},
412
+ systemPrompt: input.systemPrompt
413
+ }
414
+ });
415
+ for await (const message of q) {
416
+ signal.throwIfAborted();
417
+ for (const event of translator.accept(message)) {
418
+ yield event;
419
+ if (event.type === "tool_calls") {
420
+ yield {
421
+ type: "complete",
422
+ info: { qoder_usage: {} }
423
+ };
424
+ return;
425
+ }
426
+ }
427
+ }
428
+ translator.assertComplete();
429
+ } finally {
430
+ try {
431
+ session.options.abortController?.abort();
432
+ await client.close();
433
+ await q?.close();
434
+ } finally {
435
+ await session.cleanup();
436
+ }
437
+ }
438
+ }
439
+ };
440
+ }
441
+ //#endregion
442
+ export { createBackend };