@love-moon/ai-sdk 0.7.7 → 0.9.0
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/CHANGELOG.md +8 -0
- package/dist/built-in-backends.d.ts +1 -0
- package/dist/built-in-backends.js +6 -0
- package/dist/context-files.d.ts +16 -0
- package/dist/context-files.js +86 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/manager/index.d.ts +1 -0
- package/dist/manager/index.js +1 -0
- package/dist/manager/install.js +29 -2
- package/dist/manager/manager.d.ts +5 -1
- package/dist/manager/manager.js +8 -0
- package/dist/manager/network.js +4 -2
- package/dist/manager/quota/dsh.d.ts +55 -0
- package/dist/manager/quota/dsh.js +146 -0
- package/dist/manager/types.d.ts +26 -1
- package/dist/media-adapters.d.ts +34 -0
- package/dist/media-adapters.js +57 -0
- package/dist/media-input.d.ts +21 -0
- package/dist/media-input.js +197 -0
- package/dist/providers/chat-web-session.d.ts +7 -1
- package/dist/providers/chat-web-session.js +12 -1
- package/dist/providers/claude-agent-sdk-session.d.ts +10 -1
- package/dist/providers/claude-agent-sdk-session.js +24 -4
- package/dist/providers/codex-app-server-session.d.ts +12 -1
- package/dist/providers/codex-app-server-session.js +49 -20
- package/dist/providers/codex-exec-session.d.ts +8 -3
- package/dist/providers/codex-exec-session.js +13 -13
- package/dist/providers/copilot-sdk-session.d.ts +6 -1
- package/dist/providers/copilot-sdk-session.js +10 -3
- package/dist/providers/dsh-sdk-session.d.ts +194 -0
- package/dist/providers/dsh-sdk-session.js +951 -0
- package/dist/providers/kimi-cli-session.d.ts +11 -1
- package/dist/providers/kimi-cli-session.js +58 -4
- package/dist/providers/kimi-print-session.d.ts +10 -3
- package/dist/providers/kimi-print-session.js +23 -53
- package/dist/providers/opencode-sdk-session.d.ts +6 -1
- package/dist/providers/opencode-sdk-session.js +11 -9
- package/dist/resume/dsh.d.ts +23 -0
- package/dist/resume/dsh.js +122 -0
- package/dist/resume/index.js +2 -0
- package/dist/session-factory.d.ts +3 -1
- package/dist/session-factory.js +5 -1
- package/dsh/cordis.yml +100 -0
- package/package.json +24 -3
|
@@ -22,6 +22,9 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
22
22
|
currentTurn: {
|
|
23
23
|
fullText: string;
|
|
24
24
|
bufferedAssistantText: string;
|
|
25
|
+
thinkText: string;
|
|
26
|
+
thinkStatusLine: string;
|
|
27
|
+
thinkStatusLineAt: number;
|
|
25
28
|
items: never[];
|
|
26
29
|
toolCalls: Map<any, any>;
|
|
27
30
|
onProgress: null;
|
|
@@ -80,6 +83,11 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
80
83
|
command: any;
|
|
81
84
|
} | null;
|
|
82
85
|
currentTurnStatus: any;
|
|
86
|
+
capabilities: {
|
|
87
|
+
media: Readonly<{
|
|
88
|
+
image: "native";
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
83
91
|
pid: any;
|
|
84
92
|
};
|
|
85
93
|
getSessionInfo(): {
|
|
@@ -136,6 +144,8 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
136
144
|
bootInternal(): Promise<void>;
|
|
137
145
|
updateUsageFromStatus(payload: any): void;
|
|
138
146
|
appendAssistantText(currentTurn: any, text: any): void;
|
|
147
|
+
appendThinkText(currentTurn: any, text: any): void;
|
|
148
|
+
thinkStatusLine(currentTurn: any): any;
|
|
139
149
|
finalizeAssistantMessage(currentTurn: any): Promise<boolean>;
|
|
140
150
|
maybeEmitAuthRequired(error: any): void;
|
|
141
151
|
failUnexpectedInteractiveRequest(id: any, requestType: any, payload: any): Promise<void>;
|
|
@@ -144,7 +154,7 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
144
154
|
handleTransportFailure(error: any): void;
|
|
145
155
|
handleTransportExit(payload: any): void;
|
|
146
156
|
interruptCurrentTurn(): Promise<boolean>;
|
|
147
|
-
runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
|
|
157
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
148
158
|
useInitialImages?: boolean | undefined;
|
|
149
159
|
onProgress?: null | undefined;
|
|
150
160
|
jsonSchema?: null | undefined;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import { KIMI_CLI_WIRE_VARIANT as KIMI_PROVIDER_VARIANT } from "../built-in-backends.js";
|
|
4
|
+
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
5
|
+
import { PROVIDER_MEDIA_CAPABILITIES, buildKimiContent } from "../media-adapters.js";
|
|
6
|
+
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
4
7
|
import { KimiWireTransport } from "../transports/kimi-wire-transport.js";
|
|
5
8
|
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
6
9
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
@@ -11,6 +14,8 @@ const DEFAULT_STATUS_THROTTLE_MS = 450;
|
|
|
11
14
|
const DEFAULT_REASONING_STATUS_THROTTLE_MS = 2500;
|
|
12
15
|
const DEFAULT_STATUS_PREVIEW_DELTA_CHARS = 24;
|
|
13
16
|
const MAX_STATUS_TIMING_MS = 10 * 1000;
|
|
17
|
+
const THINK_STATUS_LINE_MAX_CHARS = 120;
|
|
18
|
+
const THINK_TAIL_BUFFER_CHARS = 400;
|
|
14
19
|
function waitForever() {
|
|
15
20
|
return new Promise(() => { });
|
|
16
21
|
}
|
|
@@ -228,6 +233,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
228
233
|
}
|
|
229
234
|
: null,
|
|
230
235
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
236
|
+
capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[KIMI_PROVIDER_VARIANT] },
|
|
231
237
|
pid: this.transport.pid || undefined,
|
|
232
238
|
};
|
|
233
239
|
}
|
|
@@ -603,6 +609,29 @@ export class KimiCliSession extends EventEmitter {
|
|
|
603
609
|
}
|
|
604
610
|
currentTurn.bufferedAssistantText += normalized;
|
|
605
611
|
}
|
|
612
|
+
appendThinkText(currentTurn, text) {
|
|
613
|
+
const normalized = normalizeText(text);
|
|
614
|
+
if (!normalized) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
currentTurn.thinkText = `${currentTurn.thinkText}${normalized}`.slice(-THINK_TAIL_BUFFER_CHARS);
|
|
618
|
+
}
|
|
619
|
+
thinkStatusLine(currentTurn) {
|
|
620
|
+
const now = this.now();
|
|
621
|
+
if (currentTurn.thinkStatusLine &&
|
|
622
|
+
now - currentTurn.thinkStatusLineAt < this.reasoningStatusThrottleMs) {
|
|
623
|
+
return currentTurn.thinkStatusLine;
|
|
624
|
+
}
|
|
625
|
+
const sanitized = sanitizeSummary(currentTurn.thinkText, THINK_TAIL_BUFFER_CHARS);
|
|
626
|
+
const snippet = sanitized.length > THINK_STATUS_LINE_MAX_CHARS
|
|
627
|
+
? `…${sanitized.slice(-THINK_STATUS_LINE_MAX_CHARS)}`
|
|
628
|
+
: sanitized;
|
|
629
|
+
if (snippet && snippet !== currentTurn.thinkStatusLine) {
|
|
630
|
+
currentTurn.thinkStatusLine = snippet;
|
|
631
|
+
currentTurn.thinkStatusLineAt = now;
|
|
632
|
+
}
|
|
633
|
+
return currentTurn.thinkStatusLine || statusLineForPhase("reasoning");
|
|
634
|
+
}
|
|
606
635
|
async finalizeAssistantMessage(currentTurn) {
|
|
607
636
|
if (!currentTurn) {
|
|
608
637
|
return false;
|
|
@@ -707,8 +736,23 @@ export class KimiCliSession extends EventEmitter {
|
|
|
707
736
|
return;
|
|
708
737
|
case "TurnEnd":
|
|
709
738
|
currentTurn.seenTurnEnd = true;
|
|
739
|
+
if (currentTurn.bufferedAssistantText) {
|
|
740
|
+
await this.finalizeAssistantMessage(currentTurn);
|
|
741
|
+
}
|
|
742
|
+
// Clear reply_in_progress as soon as the wire reports the turn ended,
|
|
743
|
+
// decoupled from the `prompt` JSON-RPC promise. Without this, a delayed
|
|
744
|
+
// or dropped prompt RPC response leaves the web UI pinned on
|
|
745
|
+
// "Kimi is writing the reply" until reload. emitTerminalWorkingStatus is
|
|
746
|
+
// idempotent, so the later runTurn call becomes a harmless no-op.
|
|
747
|
+
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
748
|
+
phase: "turn_completed",
|
|
749
|
+
status_done_line: "Kimi finished",
|
|
750
|
+
}, currentTurn.onProgress);
|
|
710
751
|
return;
|
|
711
752
|
case "StepBegin":
|
|
753
|
+
if (currentTurn.bufferedAssistantText) {
|
|
754
|
+
await this.finalizeAssistantMessage(currentTurn);
|
|
755
|
+
}
|
|
712
756
|
await this.emitWorkingStatus({
|
|
713
757
|
phase: "reasoning",
|
|
714
758
|
reply_in_progress: true,
|
|
@@ -734,10 +778,11 @@ export class KimiCliSession extends EventEmitter {
|
|
|
734
778
|
return;
|
|
735
779
|
case "ContentPart":
|
|
736
780
|
if (normalizedPayload.type === "think") {
|
|
781
|
+
this.appendThinkText(currentTurn, normalizedPayload.think);
|
|
737
782
|
await this.emitWorkingStatus({
|
|
738
783
|
phase: "reasoning",
|
|
739
784
|
reply_in_progress: true,
|
|
740
|
-
status_line:
|
|
785
|
+
status_line: this.thinkStatusLine(currentTurn),
|
|
741
786
|
}, currentTurn.onProgress);
|
|
742
787
|
return;
|
|
743
788
|
}
|
|
@@ -832,11 +877,15 @@ export class KimiCliSession extends EventEmitter {
|
|
|
832
877
|
}
|
|
833
878
|
return false;
|
|
834
879
|
}
|
|
835
|
-
async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
|
|
880
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
|
|
836
881
|
if (this.closeRequested || this.closed) {
|
|
837
882
|
throw this.createSessionClosedError();
|
|
838
883
|
}
|
|
839
|
-
|
|
884
|
+
const media = resolveTurnMedia(this.options, { useInitialImages, media: mediaInput });
|
|
885
|
+
assertMediaCapabilities(media, this.backend, PROVIDER_MEDIA_CAPABILITIES[KIMI_PROVIDER_VARIANT]);
|
|
886
|
+
let effectivePrompt = this.buildPrompt(promptText, { useInitialImages: false }) ||
|
|
887
|
+
(media.length ? defaultPromptForMedia(media) : "");
|
|
888
|
+
effectivePrompt = appendContextFilesToPrompt(effectivePrompt, contextFiles).prompt;
|
|
840
889
|
if (jsonSchema && typeof jsonSchema === "object" && effectivePrompt) {
|
|
841
890
|
effectivePrompt = injectJsonSchemaPrompt(effectivePrompt, jsonSchema);
|
|
842
891
|
}
|
|
@@ -860,6 +909,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
860
909
|
const currentTurn = {
|
|
861
910
|
fullText: "",
|
|
862
911
|
bufferedAssistantText: "",
|
|
912
|
+
thinkText: "",
|
|
913
|
+
thinkStatusLine: "",
|
|
914
|
+
thinkStatusLineAt: 0,
|
|
863
915
|
items: [],
|
|
864
916
|
toolCalls: new Map(),
|
|
865
917
|
onProgress,
|
|
@@ -886,7 +938,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
886
938
|
}, onProgress);
|
|
887
939
|
const promptResult = await Promise.race([
|
|
888
940
|
this.transport.request("prompt", {
|
|
889
|
-
user_input:
|
|
941
|
+
user_input: media.length === 0
|
|
942
|
+
? effectivePrompt
|
|
943
|
+
: buildKimiContent(effectivePrompt, media),
|
|
890
944
|
}),
|
|
891
945
|
turnFailurePromise,
|
|
892
946
|
closeGuard.promise,
|
|
@@ -60,6 +60,13 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
60
60
|
command: string;
|
|
61
61
|
} | null;
|
|
62
62
|
currentTurnStatus: any;
|
|
63
|
+
capabilities: {
|
|
64
|
+
media: Readonly<{
|
|
65
|
+
image: "unsupported";
|
|
66
|
+
}> | Readonly<{
|
|
67
|
+
image: "native";
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
63
70
|
pid: any;
|
|
64
71
|
};
|
|
65
72
|
getSessionInfo(): {
|
|
@@ -100,15 +107,15 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
100
107
|
emitAssistantMessage(text: any): Promise<void>;
|
|
101
108
|
buildPrompt(promptText: any): string;
|
|
102
109
|
buildPrintArgs(promptText?: string): string[];
|
|
103
|
-
buildUserMessage(promptText: any, {
|
|
104
|
-
|
|
110
|
+
buildUserMessage(promptText: any, { media }?: {
|
|
111
|
+
media?: never[] | undefined;
|
|
105
112
|
}): {
|
|
106
113
|
role: string;
|
|
107
114
|
content: any;
|
|
108
115
|
};
|
|
109
116
|
maybeEmitAuthRequired(stderrTail: any): void;
|
|
110
117
|
interruptCurrentTurn(): Promise<boolean>;
|
|
111
|
-
runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
|
|
118
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
112
119
|
useInitialImages?: boolean | undefined;
|
|
113
120
|
onProgress?: null | undefined;
|
|
114
121
|
jsonSchema?: null | undefined;
|
|
@@ -5,6 +5,9 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { EventEmitter } from "node:events";
|
|
6
6
|
import readline from "node:readline";
|
|
7
7
|
import { KIMI_CLI_PRINT_VARIANT as KIMI_PRINT_PROVIDER_VARIANT } from "../built-in-backends.js";
|
|
8
|
+
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
9
|
+
import { PROVIDER_MEDIA_CAPABILITIES, UNSUPPORTED_MEDIA_CAPABILITIES, buildKimiContent, } from "../media-adapters.js";
|
|
10
|
+
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
8
11
|
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
9
12
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
10
13
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
@@ -165,31 +168,6 @@ function normalizeTextContent(content) {
|
|
|
165
168
|
}
|
|
166
169
|
return "";
|
|
167
170
|
}
|
|
168
|
-
function guessMimeType(filePath) {
|
|
169
|
-
const ext = path.extname(String(filePath || "")).toLowerCase();
|
|
170
|
-
switch (ext) {
|
|
171
|
-
case ".png":
|
|
172
|
-
return "image/png";
|
|
173
|
-
case ".jpg":
|
|
174
|
-
case ".jpeg":
|
|
175
|
-
return "image/jpeg";
|
|
176
|
-
case ".gif":
|
|
177
|
-
return "image/gif";
|
|
178
|
-
case ".webp":
|
|
179
|
-
return "image/webp";
|
|
180
|
-
case ".bmp":
|
|
181
|
-
return "image/bmp";
|
|
182
|
-
case ".svg":
|
|
183
|
-
return "image/svg+xml";
|
|
184
|
-
default:
|
|
185
|
-
return "application/octet-stream";
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function filePathToDataUri(filePath) {
|
|
189
|
-
const buffer = fs.readFileSync(filePath);
|
|
190
|
-
const mimeType = guessMimeType(filePath);
|
|
191
|
-
return `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
192
|
-
}
|
|
193
171
|
export class KimiPrintSession extends EventEmitter {
|
|
194
172
|
constructor(backend, options = {}) {
|
|
195
173
|
super();
|
|
@@ -291,6 +269,11 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
291
269
|
}
|
|
292
270
|
: null,
|
|
293
271
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
272
|
+
capabilities: {
|
|
273
|
+
media: this.cliMode === "prompt"
|
|
274
|
+
? UNSUPPORTED_MEDIA_CAPABILITIES
|
|
275
|
+
: PROVIDER_MEDIA_CAPABILITIES[KIMI_PRINT_PROVIDER_VARIANT],
|
|
276
|
+
},
|
|
294
277
|
pid: this.currentTurn?.child?.pid || undefined,
|
|
295
278
|
};
|
|
296
279
|
}
|
|
@@ -444,31 +427,16 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
444
427
|
}
|
|
445
428
|
return args;
|
|
446
429
|
}
|
|
447
|
-
buildUserMessage(promptText, {
|
|
448
|
-
|
|
449
|
-
? this.options.initialImages.filter((item) => typeof item === "string" && item.trim())
|
|
450
|
-
: [];
|
|
451
|
-
if (images.length === 0) {
|
|
430
|
+
buildUserMessage(promptText, { media = [] } = {}) {
|
|
431
|
+
if (media.length === 0) {
|
|
452
432
|
return {
|
|
453
433
|
role: "user",
|
|
454
434
|
content: promptText,
|
|
455
435
|
};
|
|
456
436
|
}
|
|
457
|
-
const content = [];
|
|
458
|
-
if (promptText) {
|
|
459
|
-
content.push({ type: "text", text: promptText });
|
|
460
|
-
}
|
|
461
|
-
for (const imagePath of images) {
|
|
462
|
-
content.push({
|
|
463
|
-
type: "image_url",
|
|
464
|
-
image_url: {
|
|
465
|
-
url: filePathToDataUri(imagePath),
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
437
|
return {
|
|
470
438
|
role: "user",
|
|
471
|
-
content,
|
|
439
|
+
content: buildKimiContent(promptText, media),
|
|
472
440
|
};
|
|
473
441
|
}
|
|
474
442
|
maybeEmitAuthRequired(stderrTail) {
|
|
@@ -500,7 +468,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
500
468
|
return false;
|
|
501
469
|
}
|
|
502
470
|
}
|
|
503
|
-
async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
|
|
471
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
|
|
504
472
|
if (this.closeRequested || this.closed) {
|
|
505
473
|
throw this.createSessionClosedError();
|
|
506
474
|
}
|
|
@@ -509,21 +477,23 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
509
477
|
reason: "turn_already_running",
|
|
510
478
|
});
|
|
511
479
|
}
|
|
480
|
+
const media = resolveTurnMedia(this.options, { useInitialImages, media: mediaInput });
|
|
481
|
+
assertMediaCapabilities(media, this.backend, PROVIDER_MEDIA_CAPABILITIES[KIMI_PRINT_PROVIDER_VARIANT]);
|
|
512
482
|
let effectivePrompt = this.buildPrompt(promptText);
|
|
513
|
-
|
|
514
|
-
? this.options.initialImages.filter((item) => typeof item === "string" && item.trim())
|
|
515
|
-
: [];
|
|
483
|
+
effectivePrompt = appendContextFilesToPrompt(effectivePrompt, contextFiles).prompt;
|
|
516
484
|
if (jsonSchema && typeof jsonSchema === "object") {
|
|
517
|
-
const promptWithSchema = effectivePrompt || (
|
|
485
|
+
const promptWithSchema = effectivePrompt || (media.length > 0 ? defaultPromptForMedia(media) : "");
|
|
518
486
|
if (promptWithSchema) {
|
|
519
487
|
effectivePrompt = injectJsonSchemaPrompt(promptWithSchema, jsonSchema);
|
|
520
488
|
}
|
|
521
489
|
}
|
|
522
|
-
if (this.cliMode === "prompt" &&
|
|
523
|
-
|
|
524
|
-
|
|
490
|
+
if (this.cliMode === "prompt" && media.length > 0) {
|
|
491
|
+
throw createTurnError("Kimi prompt mode does not support media input", {
|
|
492
|
+
reason: "unsupported_media",
|
|
493
|
+
backend: this.backend,
|
|
494
|
+
});
|
|
525
495
|
}
|
|
526
|
-
if (!effectivePrompt &&
|
|
496
|
+
if (!effectivePrompt && media.length === 0) {
|
|
527
497
|
return buildEmptyTurnResult();
|
|
528
498
|
}
|
|
529
499
|
this.announceSession();
|
|
@@ -685,7 +655,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
685
655
|
});
|
|
686
656
|
});
|
|
687
657
|
if (this.cliMode === "legacy-print") {
|
|
688
|
-
const userMessage = this.buildUserMessage(effectivePrompt, {
|
|
658
|
+
const userMessage = this.buildUserMessage(effectivePrompt || defaultPromptForMedia(media), { media });
|
|
689
659
|
child.stdin.write(`${JSON.stringify(userMessage)}\n`);
|
|
690
660
|
}
|
|
691
661
|
child.stdin.end();
|
|
@@ -78,6 +78,11 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
78
78
|
resumeReady: boolean;
|
|
79
79
|
manualResume: null;
|
|
80
80
|
currentTurnStatus: any;
|
|
81
|
+
capabilities: {
|
|
82
|
+
media: Readonly<{
|
|
83
|
+
image: "native";
|
|
84
|
+
}>;
|
|
85
|
+
};
|
|
81
86
|
pid: any;
|
|
82
87
|
};
|
|
83
88
|
getSessionInfo(): {
|
|
@@ -164,7 +169,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
164
169
|
handleTransportFailure(error: any): void;
|
|
165
170
|
handleTransportExit(payload: any): void;
|
|
166
171
|
interruptCurrentTurn(): Promise<boolean>;
|
|
167
|
-
runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
|
|
172
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
168
173
|
useInitialImages?: boolean | undefined;
|
|
169
174
|
onProgress?: null | undefined;
|
|
170
175
|
jsonSchema?: null | undefined;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { OPENCODE_SDK_VARIANT as OPENCODE_PROVIDER_VARIANT } from "../built-in-backends.js";
|
|
3
|
+
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
4
|
+
import { PROVIDER_MEDIA_CAPABILITIES, buildOpencodeParts } from "../media-adapters.js";
|
|
5
|
+
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
3
6
|
import { OpencodeServerTransport } from "../transports/opencode-server-transport.js";
|
|
4
7
|
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
5
8
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
@@ -223,6 +226,7 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
223
226
|
resumeReady: Boolean(this.sessionId),
|
|
224
227
|
manualResume: null,
|
|
225
228
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
229
|
+
capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT] },
|
|
226
230
|
pid: this.transport.pid || undefined,
|
|
227
231
|
};
|
|
228
232
|
}
|
|
@@ -1136,12 +1140,15 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1136
1140
|
}
|
|
1137
1141
|
return true;
|
|
1138
1142
|
}
|
|
1139
|
-
async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
|
|
1143
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
|
|
1140
1144
|
if (this.closeRequested) {
|
|
1141
1145
|
throw this.createSessionClosedError();
|
|
1142
1146
|
}
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1147
|
+
const media = resolveTurnMedia(this.options, { useInitialImages, media: mediaInput });
|
|
1148
|
+
assertMediaCapabilities(media, this.backend, PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT]);
|
|
1149
|
+
const effectivePrompt = appendContextFilesToPrompt(this.buildPrompt(promptText, { useInitialImages: false }) ||
|
|
1150
|
+
(media.length ? defaultPromptForMedia(media) : ""), contextFiles).prompt;
|
|
1151
|
+
if (!effectivePrompt && media.length === 0) {
|
|
1145
1152
|
return buildEmptyTurnResult();
|
|
1146
1153
|
}
|
|
1147
1154
|
if (this.currentTurn) {
|
|
@@ -1216,12 +1223,7 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1216
1223
|
format: jsonSchema && typeof jsonSchema === "object"
|
|
1217
1224
|
? { type: "json_schema", schema: jsonSchema }
|
|
1218
1225
|
: undefined,
|
|
1219
|
-
parts:
|
|
1220
|
-
{
|
|
1221
|
-
type: "text",
|
|
1222
|
-
text: effectivePrompt,
|
|
1223
|
-
},
|
|
1224
|
-
],
|
|
1226
|
+
parts: buildOpencodeParts(effectivePrompt, media),
|
|
1225
1227
|
}, {
|
|
1226
1228
|
throwOnError: true,
|
|
1227
1229
|
responseStyle: "data",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh sessions persist as JSONL logs written by the runtime's
|
|
3
|
+
* `@deepseek-ai/dsh-session-persistence-jsonl` plugin under the session root
|
|
4
|
+
* DshSdkSession sets via DSH_SESSION_ROOT:
|
|
5
|
+
*
|
|
6
|
+
* <root>/<projectKey(cwd)>/<sessionId>/session.jsonl
|
|
7
|
+
*
|
|
8
|
+
* The first line of session.jsonl is a `{"type":"session",...}` header that
|
|
9
|
+
* carries the workspace `cwd`, which is what resume needs back.
|
|
10
|
+
*/
|
|
11
|
+
export function buildCliArgs(sessionId: any): string[];
|
|
12
|
+
export function findSessionPath(sessionId: any, options?: {}): Promise<string | null>;
|
|
13
|
+
export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
|
|
14
|
+
provider: any;
|
|
15
|
+
sessionId: any;
|
|
16
|
+
sessionPath: null;
|
|
17
|
+
cwd: any;
|
|
18
|
+
debugMetadata: {
|
|
19
|
+
cwdSource: any;
|
|
20
|
+
sessionPath: null;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
export const BACKEND: "dsh";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import { defaultDshSessionRoot } from "../providers/dsh-sdk-session.js";
|
|
6
|
+
import { buildResumeContext, isExistingDirectory, normalizeSessionId, } from "./shared.js";
|
|
7
|
+
export const BACKEND = "dsh";
|
|
8
|
+
/**
|
|
9
|
+
* dsh sessions persist as JSONL logs written by the runtime's
|
|
10
|
+
* `@deepseek-ai/dsh-session-persistence-jsonl` plugin under the session root
|
|
11
|
+
* DshSdkSession sets via DSH_SESSION_ROOT:
|
|
12
|
+
*
|
|
13
|
+
* <root>/<projectKey(cwd)>/<sessionId>/session.jsonl
|
|
14
|
+
*
|
|
15
|
+
* The first line of session.jsonl is a `{"type":"session",...}` header that
|
|
16
|
+
* carries the workspace `cwd`, which is what resume needs back.
|
|
17
|
+
*/
|
|
18
|
+
export function buildCliArgs(sessionId) {
|
|
19
|
+
// dsh has no standalone CLI resume command inside conductor (the runtime is
|
|
20
|
+
// relaunched by DshSdkSession through the SDK client), but the resume
|
|
21
|
+
// contract requires a stable arg vector; consumers ignore it for dsh.
|
|
22
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
23
|
+
if (!normalizedSessionId) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
return [`--resume-session-id=${normalizedSessionId}`];
|
|
27
|
+
}
|
|
28
|
+
function resolveSessionRoot(options = {}) {
|
|
29
|
+
if (typeof options.dshSessionRoot === "string" && options.dshSessionRoot.trim()) {
|
|
30
|
+
return options.dshSessionRoot.trim();
|
|
31
|
+
}
|
|
32
|
+
return defaultDshSessionRoot();
|
|
33
|
+
}
|
|
34
|
+
export async function findSessionPath(sessionId, options = {}) {
|
|
35
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
36
|
+
if (!normalizedSessionId) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const root = resolveSessionRoot(options);
|
|
40
|
+
let projectEntries = [];
|
|
41
|
+
try {
|
|
42
|
+
projectEntries = await fsp.readdir(root, { withFileTypes: true });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
for (const projectEntry of projectEntries) {
|
|
48
|
+
if (!projectEntry.isDirectory()) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const sessionDir = path.join(root, projectEntry.name, normalizedSessionId);
|
|
52
|
+
for (const filename of ["session.jsonl", "session.jsonl.zstd"]) {
|
|
53
|
+
const candidate = path.join(sessionDir, filename);
|
|
54
|
+
try {
|
|
55
|
+
const stats = await fsp.stat(candidate);
|
|
56
|
+
if (stats.isFile()) {
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// keep scanning
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function readHeaderLine(sessionPath) {
|
|
68
|
+
if (!sessionPath || !sessionPath.endsWith(".jsonl")) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const rl = readline.createInterface({
|
|
72
|
+
input: fs.createReadStream(sessionPath),
|
|
73
|
+
crlfDelay: Infinity,
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
for await (const line of rl) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (!trimmed) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(trimmed);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
rl.close();
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
export async function resolveResumeContext(sessionId, options = {}) {
|
|
95
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
96
|
+
if (!normalizedSessionId) {
|
|
97
|
+
throw new Error("--resume requires a session id");
|
|
98
|
+
}
|
|
99
|
+
const sessionPath = await findSessionPath(normalizedSessionId, options);
|
|
100
|
+
if (!sessionPath) {
|
|
101
|
+
throw new Error(`Invalid --resume session id for dsh: ${normalizedSessionId}`);
|
|
102
|
+
}
|
|
103
|
+
const header = await readHeaderLine(sessionPath);
|
|
104
|
+
const cwdFromHeader = header && header.type === "session" && typeof header.cwd === "string" && header.cwd.trim()
|
|
105
|
+
? header.cwd.trim()
|
|
106
|
+
: "";
|
|
107
|
+
const cwd = cwdFromHeader ||
|
|
108
|
+
(typeof options?.cwd === "string" && options.cwd.trim() ? options.cwd.trim() : "");
|
|
109
|
+
if (!cwd) {
|
|
110
|
+
throw new Error(`Could not resolve workspace for dsh session ${normalizedSessionId}`);
|
|
111
|
+
}
|
|
112
|
+
if (!(await isExistingDirectory(cwd))) {
|
|
113
|
+
throw new Error(`Resume workspace path does not exist: ${cwd}`);
|
|
114
|
+
}
|
|
115
|
+
return buildResumeContext({
|
|
116
|
+
provider: "dsh",
|
|
117
|
+
sessionId: normalizedSessionId,
|
|
118
|
+
sessionPath,
|
|
119
|
+
cwd,
|
|
120
|
+
cwdSource: cwdFromHeader ? "session" : "options_cwd",
|
|
121
|
+
});
|
|
122
|
+
}
|
package/dist/resume/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as chatWeb from "./chat-web.js";
|
|
|
4
4
|
import * as claude from "./claude.js";
|
|
5
5
|
import * as codex from "./codex.js";
|
|
6
6
|
import * as copilot from "./copilot.js";
|
|
7
|
+
import * as dsh from "./dsh.js";
|
|
7
8
|
import * as kimi from "./kimi.js";
|
|
8
9
|
import * as opencode from "./opencode.js";
|
|
9
10
|
import { buildResumeContext, isExistingDirectory, normalizeBackend, normalizeProjectPathCandidate, normalizeSessionId, resolveSessionRunDirectory, } from "./shared.js";
|
|
@@ -27,6 +28,7 @@ const RESUME_MODULES_BY_BACKEND = new Map([
|
|
|
27
28
|
["kimi", kimi],
|
|
28
29
|
["opencode", opencode],
|
|
29
30
|
["chat-web", chatWeb],
|
|
31
|
+
["dsh", dsh],
|
|
30
32
|
]);
|
|
31
33
|
// Sanity-check at module load:
|
|
32
34
|
// (1) Every registered resume module must point at a real built-in backend.
|
|
@@ -11,6 +11,7 @@ export const KIMI_PROVIDER_VARIANT: "kimi-cli-wire";
|
|
|
11
11
|
export const KIMI_PRINT_PROVIDER_VARIANT: "kimi-cli-print";
|
|
12
12
|
export const OPENCODE_PROVIDER_VARIANT: "opencode-sdk";
|
|
13
13
|
export const CHAT_WEB_PROVIDER_VARIANT: "chat-web-session";
|
|
14
|
+
export const DSH_PROVIDER_VARIANT: "dsh-sdk";
|
|
14
15
|
import { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
15
16
|
import { ChatWebSession } from "./providers/chat-web-session.js";
|
|
16
17
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
@@ -20,4 +21,5 @@ import { CopilotSdkSession } from "./providers/copilot-sdk-session.js";
|
|
|
20
21
|
import { KimiCliSession } from "./providers/kimi-cli-session.js";
|
|
21
22
|
import { KimiPrintSession } from "./providers/kimi-print-session.js";
|
|
22
23
|
import { OpencodeSdkSession } from "./providers/opencode-sdk-session.js";
|
|
23
|
-
|
|
24
|
+
import { DshSdkSession } from "./providers/dsh-sdk-session.js";
|
|
25
|
+
export { BUILT_IN_BACKENDS, ChatWebSession, CodexAppServerSession, CodexExecSession, ClaudeAgentSdkSession, CopilotSdkSession, KimiCliSession, KimiPrintSession, OpencodeSdkSession, DshSdkSession };
|
package/dist/session-factory.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { BUILT_IN_BACKENDS, CHAT_WEB_SESSION_VARIANT, CLAUDE_AGENT_SDK_VARIANT, CODEX_APP_SERVER_VARIANT, CODEX_EXEC_VARIANT, COPILOT_SDK_VARIANT, KIMI_CLI_PRINT_VARIANT, KIMI_CLI_WIRE_VARIANT, OPENCODE_SDK_VARIANT, getBuiltInBackendEntry, isBuiltInBackend, listBuiltInBackends, normalizeBuiltInBackend, } from "./built-in-backends.js";
|
|
1
|
+
import { BUILT_IN_BACKENDS, CHAT_WEB_SESSION_VARIANT, CLAUDE_AGENT_SDK_VARIANT, CODEX_APP_SERVER_VARIANT, CODEX_EXEC_VARIANT, COPILOT_SDK_VARIANT, DSH_SDK_VARIANT, KIMI_CLI_PRINT_VARIANT, KIMI_CLI_WIRE_VARIANT, OPENCODE_SDK_VARIANT, getBuiltInBackendEntry, isBuiltInBackend, listBuiltInBackends, normalizeBuiltInBackend, } from "./built-in-backends.js";
|
|
2
2
|
import { ChatWebSession } from "./providers/chat-web-session.js";
|
|
3
3
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
4
4
|
import { CodexExecSession } from "./providers/codex-exec-session.js";
|
|
5
5
|
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
6
6
|
import { CopilotSdkSession } from "./providers/copilot-sdk-session.js";
|
|
7
|
+
import { DshSdkSession } from "./providers/dsh-sdk-session.js";
|
|
7
8
|
import { KimiCliSession } from "./providers/kimi-cli-session.js";
|
|
8
9
|
import { resolveKimiCliMode } from "./providers/kimi-cli-mode.js";
|
|
9
10
|
import { KimiPrintSession } from "./providers/kimi-print-session.js";
|
|
@@ -17,6 +18,7 @@ export const KIMI_PROVIDER_VARIANT = KIMI_CLI_WIRE_VARIANT;
|
|
|
17
18
|
export const KIMI_PRINT_PROVIDER_VARIANT = KIMI_CLI_PRINT_VARIANT;
|
|
18
19
|
export const OPENCODE_PROVIDER_VARIANT = OPENCODE_SDK_VARIANT;
|
|
19
20
|
export const CHAT_WEB_PROVIDER_VARIANT = CHAT_WEB_SESSION_VARIANT;
|
|
21
|
+
export const DSH_PROVIDER_VARIANT = DSH_SDK_VARIANT;
|
|
20
22
|
const SESSION_FACTORIES_BY_BACKEND = new Map([
|
|
21
23
|
[
|
|
22
24
|
"codex",
|
|
@@ -47,6 +49,7 @@ const SESSION_FACTORIES_BY_BACKEND = new Map([
|
|
|
47
49
|
],
|
|
48
50
|
["opencode", (backend, options) => new OpencodeSdkSession(backend, options)],
|
|
49
51
|
["chat-web", (backend, options) => new ChatWebSession(backend, options)],
|
|
52
|
+
["dsh", (backend, options) => new DshSdkSession(backend, options)],
|
|
50
53
|
]);
|
|
51
54
|
function hasStructuredOutputPreference(options = {}) {
|
|
52
55
|
if (!options || typeof options !== "object") {
|
|
@@ -131,3 +134,4 @@ export { CopilotSdkSession };
|
|
|
131
134
|
export { KimiCliSession };
|
|
132
135
|
export { KimiPrintSession };
|
|
133
136
|
export { OpencodeSdkSession };
|
|
137
|
+
export { DshSdkSession };
|