dsh-codex-subscription 2.0.1 → 2.1.0-beta.1
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 +261 -8
- package/lib/sketch-psd-worker.js +11 -0
- package/package.json +3 -2
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";
|
|
@@ -1615,7 +1834,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1615
1834
|
}
|
|
1616
1835
|
//#endregion
|
|
1617
1836
|
//#region src/version.js
|
|
1618
|
-
const PACKAGE_VERSION = "2.0.1";
|
|
1837
|
+
const PACKAGE_VERSION = "2.1.0-beta.1";
|
|
1619
1838
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1620
1839
|
//#endregion
|
|
1621
1840
|
//#region src/model-catalog.js
|
|
@@ -4011,7 +4230,27 @@ function apply(ctx) {
|
|
|
4011
4230
|
usageReader,
|
|
4012
4231
|
fetch: (input, init) => network.fetch("quota-reset", input, init)
|
|
4013
4232
|
});
|
|
4014
|
-
const
|
|
4233
|
+
const sketchBridge = createSketchAgentBridge({ enabled: () => settings.get().imageSketchAgent && settings.get().imageSketch && settings.get().imageEditing });
|
|
4234
|
+
ctx.effect(() => {
|
|
4235
|
+
let dispose;
|
|
4236
|
+
const sync = () => {
|
|
4237
|
+
const value = settings.get();
|
|
4238
|
+
if (value.imageSketchAgent && value.imageSketch && value.imageEditing) dispose ??= ctx.tools.register(createSketchAgentTool(sketchBridge, ctx.attachments));
|
|
4239
|
+
else {
|
|
4240
|
+
dispose?.();
|
|
4241
|
+
dispose = void 0;
|
|
4242
|
+
sketchBridge.dispose();
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
sync();
|
|
4246
|
+
const unwatch = settings.watch(sync);
|
|
4247
|
+
return () => {
|
|
4248
|
+
unwatch();
|
|
4249
|
+
dispose?.();
|
|
4250
|
+
sketchBridge.dispose();
|
|
4251
|
+
};
|
|
4252
|
+
}, "codex-subscription: native sketch tool");
|
|
4253
|
+
const subscriptionHandler = createSubscriptionRpcHandler({
|
|
4015
4254
|
authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
|
|
4016
4255
|
usageReader,
|
|
4017
4256
|
resetCreditService,
|
|
@@ -4027,10 +4266,24 @@ function apply(ctx) {
|
|
|
4027
4266
|
originalImages,
|
|
4028
4267
|
resolveInheritedOriginal: (sessionId, assetId) => inheritedOriginalImageRef(ctx.get?.("sessions")?.get?.(sessionId), assetId)
|
|
4029
4268
|
});
|
|
4269
|
+
const handler = (endpoint, payload, signal) => endpoint.startsWith("sketch/") ? sketchBridge.rpc(endpoint, payload) : subscriptionHandler(endpoint, payload, signal);
|
|
4030
4270
|
ctx.effect(() => {
|
|
4031
4271
|
modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
|
|
4032
4272
|
}, "codex-subscription: official model catalog");
|
|
4033
|
-
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() =>
|
|
4273
|
+
ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() => {
|
|
4274
|
+
const transport = registerSubscriptionTransport(connectionContext.connection, handler);
|
|
4275
|
+
let codec;
|
|
4276
|
+
try {
|
|
4277
|
+
codec = registerSketchCodec(connectionContext.connection);
|
|
4278
|
+
} catch (error) {
|
|
4279
|
+
transport();
|
|
4280
|
+
throw error;
|
|
4281
|
+
}
|
|
4282
|
+
return () => {
|
|
4283
|
+
codec();
|
|
4284
|
+
transport();
|
|
4285
|
+
};
|
|
4286
|
+
}, "codex-subscription: DSH-trusted account RPC"));
|
|
4034
4287
|
}
|
|
4035
4288
|
//#endregion
|
|
4036
4289
|
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 };
|