dsh-codex-subscription 2.0.1 → 2.1.0-beta.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.
- package/README.en.md +13 -1
- package/README.md +13 -1
- package/THIRD_PARTY_NOTICES.md +79 -0
- package/lib/client.js +710 -33
- package/lib/index.js +262 -8
- package/lib/sketch-psd-worker.js +11 -0
- package/package.json +4 -3
package/lib/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
2
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
2
5
|
import * as dshCredentials from "@deepseek-ai/dsh-credentials";
|
|
3
6
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
4
7
|
import { LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
8
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
6
9
|
import z from "@deepseek-ai/schemastery";
|
|
7
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
10
|
import { execFile, spawn } from "node:child_process";
|
|
9
11
|
import { request } from "node:https";
|
|
10
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -14,9 +16,7 @@ import { HttpsProxyAgent } from "https-proxy-agent";
|
|
|
14
16
|
import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
15
17
|
import { createModels } from "@earendil-works/pi-ai";
|
|
16
18
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
17
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
18
19
|
import { constants } from "node:fs";
|
|
19
|
-
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
20
20
|
import { dirname, join, resolve } from "node:path";
|
|
21
21
|
//#region src/image-models.js
|
|
22
22
|
const DEFAULT_IMAGE_MODEL = "gpt-image-2";
|
|
@@ -59,7 +59,8 @@ const IMAGE_FEATURE_DEFAULTS = Object.freeze({
|
|
|
59
59
|
imageEditing: true,
|
|
60
60
|
imageViewer: true,
|
|
61
61
|
imageAnnotations: true,
|
|
62
|
-
imageSketch:
|
|
62
|
+
imageSketch: false,
|
|
63
|
+
imageSketchAgent: false
|
|
63
64
|
});
|
|
64
65
|
function readImageFeatures(value = {}) {
|
|
65
66
|
return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
|
|
@@ -346,7 +347,11 @@ const RPC_ENDPOINTS = Object.freeze([
|
|
|
346
347
|
"reset-credit/inspect",
|
|
347
348
|
"reset-credit/prepare",
|
|
348
349
|
"reset-credit/consume",
|
|
349
|
-
"image/original/chunk"
|
|
350
|
+
"image/original/chunk",
|
|
351
|
+
"sketch/connect",
|
|
352
|
+
"sketch/poll",
|
|
353
|
+
"sketch/result",
|
|
354
|
+
"sketch/disconnect"
|
|
350
355
|
]);
|
|
351
356
|
//#endregion
|
|
352
357
|
//#region src/subscription-transport.js
|
|
@@ -401,6 +406,220 @@ function registerSubscriptionTransport(connection, handler) {
|
|
|
401
406
|
};
|
|
402
407
|
}
|
|
403
408
|
//#endregion
|
|
409
|
+
//#region src/sketch-agent-bridge.js
|
|
410
|
+
function createSketchAgentBridge({ enabled, now = Date.now, timeoutMs = 2e4 }) {
|
|
411
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
412
|
+
const fail = (entry, message) => {
|
|
413
|
+
for (const task of entry.tasks.values()) task.reject(Error(message));
|
|
414
|
+
entry.tasks.clear();
|
|
415
|
+
};
|
|
416
|
+
const find = (payload) => {
|
|
417
|
+
const entry = sessions.get(payload.sessionId);
|
|
418
|
+
if (!entry || entry.token !== payload.token || now() - entry.seen > 1e4) throw Error("Sketch connection expired");
|
|
419
|
+
entry.seen = now();
|
|
420
|
+
return entry;
|
|
421
|
+
};
|
|
422
|
+
return {
|
|
423
|
+
async rpc(endpoint, payload) {
|
|
424
|
+
try {
|
|
425
|
+
if (!enabled()) throw Error("Sketch is disabled");
|
|
426
|
+
if (!payload || typeof payload.sessionId !== "string" || !payload.sessionId.length || payload.sessionId.length > 200) throw Error("Invalid session");
|
|
427
|
+
if (endpoint === "sketch/connect") {
|
|
428
|
+
for (const [id, entry] of sessions) if (now() - entry.seen >= 1e4) {
|
|
429
|
+
fail(entry, "Sketch connection expired");
|
|
430
|
+
sessions.delete(id);
|
|
431
|
+
}
|
|
432
|
+
const previous = sessions.get(payload.sessionId);
|
|
433
|
+
if (previous && now() - previous.seen < 1e4) throw Error("Another board is connected to this session");
|
|
434
|
+
if (previous) fail(previous, "Sketch connection replaced");
|
|
435
|
+
const entry = {
|
|
436
|
+
token: randomUUID(),
|
|
437
|
+
seen: now(),
|
|
438
|
+
tasks: /* @__PURE__ */ new Map()
|
|
439
|
+
};
|
|
440
|
+
sessions.set(payload.sessionId, entry);
|
|
441
|
+
return {
|
|
442
|
+
ok: true,
|
|
443
|
+
value: { token: entry.token }
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const entry = find(payload);
|
|
447
|
+
if (endpoint === "sketch/poll") return {
|
|
448
|
+
ok: true,
|
|
449
|
+
value: [...entry.tasks].filter(([, t]) => !t.delivered).map(([id, t]) => {
|
|
450
|
+
t.delivered = true;
|
|
451
|
+
return {
|
|
452
|
+
id,
|
|
453
|
+
request: t.request,
|
|
454
|
+
expiresAt: t.expiresAt
|
|
455
|
+
};
|
|
456
|
+
})
|
|
457
|
+
};
|
|
458
|
+
if (endpoint === "sketch/disconnect") {
|
|
459
|
+
fail(entry, "Sketch board closed");
|
|
460
|
+
sessions.delete(payload.sessionId);
|
|
461
|
+
return {
|
|
462
|
+
ok: true,
|
|
463
|
+
value: null
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (endpoint === "sketch/result") {
|
|
467
|
+
const task = entry.tasks.get(payload.id);
|
|
468
|
+
if (task) {
|
|
469
|
+
entry.tasks.delete(payload.id);
|
|
470
|
+
payload.error ? task.reject(Error(String(payload.error).slice(0, 500))) : task.resolve(payload.value);
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
ok: true,
|
|
474
|
+
value: null
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
throw Error("Unknown sketch route");
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
error: {
|
|
482
|
+
code: "invalid-input",
|
|
483
|
+
message: error.message,
|
|
484
|
+
details: { issues: [] }
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
request(sessionId, request, signal) {
|
|
490
|
+
if (!enabled()) return Promise.reject(Error("Sketch is disabled"));
|
|
491
|
+
const entry = sessions.get(sessionId);
|
|
492
|
+
if (!entry || now() - entry.seen > 1e4) return Promise.reject(Error("Switch to this session in DSH with sketch editing enabled"));
|
|
493
|
+
if (entry.tasks.size) return Promise.reject(Error("Another sketch operation is pending"));
|
|
494
|
+
if (JSON.stringify(request).length > 2e6) return Promise.reject(Error("Sketch batch is too large"));
|
|
495
|
+
return new Promise((resolve, reject) => {
|
|
496
|
+
const id = randomUUID();
|
|
497
|
+
const finish = (callback, value) => {
|
|
498
|
+
clearTimeout(timer);
|
|
499
|
+
signal?.removeEventListener("abort", abort);
|
|
500
|
+
entry.tasks.delete(id);
|
|
501
|
+
callback(value);
|
|
502
|
+
};
|
|
503
|
+
const abort = () => finish(reject, Error("Sketch operation interrupted; inspect before retrying"));
|
|
504
|
+
const timer = setTimeout(() => finish(reject, Error("Sketch response timed out; inspect before retrying")), timeoutMs);
|
|
505
|
+
entry.tasks.set(id, {
|
|
506
|
+
request,
|
|
507
|
+
expiresAt: now() + timeoutMs,
|
|
508
|
+
delivered: false,
|
|
509
|
+
resolve: (value) => finish(resolve, value),
|
|
510
|
+
reject: (error) => finish(reject, error)
|
|
511
|
+
});
|
|
512
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
513
|
+
if (signal?.aborted) abort();
|
|
514
|
+
});
|
|
515
|
+
},
|
|
516
|
+
dispose() {
|
|
517
|
+
for (const entry of sessions.values()) fail(entry, "Sketch service stopped");
|
|
518
|
+
sessions.clear();
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/sketch-agent-tool.js
|
|
524
|
+
function createSketchAgentTool(bridge, attachments) {
|
|
525
|
+
return defineTool({
|
|
526
|
+
name: "codex_sketch",
|
|
527
|
+
description: "Edit the sketch board in this session using native editable strokes and layers. Only use when asked to draw or edit a sketch. Start with inspect for the documentId, revision and command reference. Apply atomic batches, preview between stages, and save the finished draft. Never generates AI images, sends messages or attaches images automatically. Inspect automatically opens the board in the currently viewed session. Do not ask the user to open it first. If the session is not visible in DSH, ask them to switch to it. On timeout inspect before retrying; reuse the exact requestId only for the same request.",
|
|
528
|
+
parameters: {
|
|
529
|
+
action: {
|
|
530
|
+
type: "string",
|
|
531
|
+
required: true,
|
|
532
|
+
enum: [
|
|
533
|
+
"inspect",
|
|
534
|
+
"apply",
|
|
535
|
+
"preview",
|
|
536
|
+
"save"
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
documentId: {
|
|
540
|
+
type: "string",
|
|
541
|
+
description: "From inspect; required except for inspect."
|
|
542
|
+
},
|
|
543
|
+
revision: {
|
|
544
|
+
type: "integer",
|
|
545
|
+
description: "From latest response; required for apply/save."
|
|
546
|
+
},
|
|
547
|
+
requestId: {
|
|
548
|
+
type: "string",
|
|
549
|
+
description: "Unique id for apply/save; exact retries are deduplicated."
|
|
550
|
+
},
|
|
551
|
+
commands: {
|
|
552
|
+
type: "string",
|
|
553
|
+
description: "JSON array of native commands described by inspect. Required for apply."
|
|
554
|
+
},
|
|
555
|
+
name: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: "Draft name for save."
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
timeoutMs: 25e3,
|
|
561
|
+
isConcurrencySafe: () => false,
|
|
562
|
+
async execute(args, exec) {
|
|
563
|
+
const sessionId = exec.agent?.id;
|
|
564
|
+
if (typeof sessionId !== "string") throw Error("A session-owned sketch call is required");
|
|
565
|
+
const request = { ...args };
|
|
566
|
+
if (args.action === "apply") try {
|
|
567
|
+
request.commands = JSON.parse(args.commands);
|
|
568
|
+
} catch {
|
|
569
|
+
throw Error("commands must be a JSON array");
|
|
570
|
+
}
|
|
571
|
+
const value = await bridge.request(sessionId, request, exec.signal);
|
|
572
|
+
if (value.png) {
|
|
573
|
+
if (!/^data:image\/png;base64,/.test(value.png) || value.png.length > 8 * 1024 * 1024) throw Error("Invalid sketch preview");
|
|
574
|
+
const image = await attachments.saveImage({
|
|
575
|
+
data: new Uint8Array(Buffer.from(value.png.split(",")[1], "base64")),
|
|
576
|
+
mediaType: "image/png",
|
|
577
|
+
name: "sketch-preview.png"
|
|
578
|
+
});
|
|
579
|
+
const { png, ...snapshot } = value;
|
|
580
|
+
return {
|
|
581
|
+
...snapshot,
|
|
582
|
+
image
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
return value;
|
|
586
|
+
},
|
|
587
|
+
output: {
|
|
588
|
+
schema: {
|
|
589
|
+
type: "object",
|
|
590
|
+
additionalProperties: true
|
|
591
|
+
},
|
|
592
|
+
render: (_args, value) => [{
|
|
593
|
+
type: "text",
|
|
594
|
+
text: JSON.stringify({
|
|
595
|
+
...value,
|
|
596
|
+
image: void 0
|
|
597
|
+
})
|
|
598
|
+
}, ...value.image ? [{
|
|
599
|
+
type: "image",
|
|
600
|
+
attachment: value.image
|
|
601
|
+
}] : []]
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/sketch-codec-route.js
|
|
607
|
+
function registerSketchCodec(connection) {
|
|
608
|
+
let source;
|
|
609
|
+
return connection.fetch.register({
|
|
610
|
+
path: "/api/codex-subscription/sketch-psd-worker",
|
|
611
|
+
methods: ["GET"],
|
|
612
|
+
requestBody: "buffered",
|
|
613
|
+
async fetch() {
|
|
614
|
+
source ??= await readFile(new URL("./sketch-psd-worker.js", import.meta.url));
|
|
615
|
+
return new Response(source, { headers: {
|
|
616
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
617
|
+
"cache-control": "no-store"
|
|
618
|
+
} });
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
404
623
|
//#region src/account-vault.js
|
|
405
624
|
const VERSION = 1;
|
|
406
625
|
const DEFAULT_LABEL = "Account 1";
|
|
@@ -1613,9 +1832,10 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1613
1832
|
streamSimple: (model, context, options) => networkIterable(() => provider.streamSimple(model, context, withPreferences(model, options)))
|
|
1614
1833
|
});
|
|
1615
1834
|
}
|
|
1835
|
+
Object.freeze(["0.82.1", "0.85.1"]);
|
|
1616
1836
|
//#endregion
|
|
1617
1837
|
//#region src/version.js
|
|
1618
|
-
const PACKAGE_VERSION = "2.0.
|
|
1838
|
+
const PACKAGE_VERSION = "2.1.0-beta.2";
|
|
1619
1839
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1620
1840
|
//#endregion
|
|
1621
1841
|
//#region src/model-catalog.js
|
|
@@ -4011,7 +4231,27 @@ function apply(ctx) {
|
|
|
4011
4231
|
usageReader,
|
|
4012
4232
|
fetch: (input, init) => network.fetch("quota-reset", input, init)
|
|
4013
4233
|
});
|
|
4014
|
-
const
|
|
4234
|
+
const sketchBridge = createSketchAgentBridge({ enabled: () => settings.get().imageSketchAgent && settings.get().imageSketch && settings.get().imageEditing });
|
|
4235
|
+
ctx.effect(() => {
|
|
4236
|
+
let dispose;
|
|
4237
|
+
const sync = () => {
|
|
4238
|
+
const value = settings.get();
|
|
4239
|
+
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments));
|
|
4240
|
+
else {
|
|
4241
|
+
dispose?.();
|
|
4242
|
+
dispose = void 0;
|
|
4243
|
+
sketchBridge.dispose();
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
sync();
|
|
4247
|
+
const unwatch = settings.watch(sync);
|
|
4248
|
+
return () => {
|
|
4249
|
+
unwatch();
|
|
4250
|
+
dispose?.();
|
|
4251
|
+
sketchBridge.dispose();
|
|
4252
|
+
};
|
|
4253
|
+
}, "codex-subscription: native sketch tool");
|
|
4254
|
+
const subscriptionHandler = createSubscriptionRpcHandler({
|
|
4015
4255
|
authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
|
|
4016
4256
|
usageReader,
|
|
4017
4257
|
resetCreditService,
|
|
@@ -4027,10 +4267,24 @@ function apply(ctx) {
|
|
|
4027
4267
|
originalImages,
|
|
4028
4268
|
resolveInheritedOriginal: (sessionId, assetId) => inheritedOriginalImageRef(ctx.get?.("sessions")?.get?.(sessionId), assetId)
|
|
4029
4269
|
});
|
|
4270
|
+
const handler = (endpoint, payload, signal) => endpoint.startsWith("sketch/") ? sketchBridge.rpc(endpoint, payload) : subscriptionHandler(endpoint, payload, signal);
|
|
4030
4271
|
ctx.effect(() => {
|
|
4031
4272
|
modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
|
|
4032
4273
|
}, "codex-subscription: official model catalog");
|
|
4033
|
-
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() =>
|
|
4274
|
+
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() => {
|
|
4275
|
+
const transport = registerSubscriptionTransport(connectionContext.connection, handler);
|
|
4276
|
+
let codec;
|
|
4277
|
+
try {
|
|
4278
|
+
codec = registerSketchCodec(connectionContext.connection);
|
|
4279
|
+
} catch (error) {
|
|
4280
|
+
transport();
|
|
4281
|
+
throw error;
|
|
4282
|
+
}
|
|
4283
|
+
return () => {
|
|
4284
|
+
codec();
|
|
4285
|
+
transport();
|
|
4286
|
+
};
|
|
4287
|
+
}, "codex-subscription: DSH-trusted account RPC"));
|
|
4034
4288
|
}
|
|
4035
4289
|
//#endregion
|
|
4036
4290
|
export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, normalizeContextMode, normalizeCustomContextWindow, openCodexAuthUrl, parseCodexUsage };
|