@love-moon/ai-sdk 0.8.0 → 0.10.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 +14 -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 +149 -0
- package/dist/manager/types.d.ts +25 -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 +19 -21
- package/dist/providers/codex-app-server-session.js +124 -12
- 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 +6 -1
- package/dist/providers/kimi-cli-session.js +13 -3
- 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
|
@@ -83,6 +83,11 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
83
83
|
command: any;
|
|
84
84
|
} | null;
|
|
85
85
|
currentTurnStatus: any;
|
|
86
|
+
capabilities: {
|
|
87
|
+
media: Readonly<{
|
|
88
|
+
image: "native";
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
86
91
|
pid: any;
|
|
87
92
|
};
|
|
88
93
|
getSessionInfo(): {
|
|
@@ -149,7 +154,7 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
149
154
|
handleTransportFailure(error: any): void;
|
|
150
155
|
handleTransportExit(payload: any): void;
|
|
151
156
|
interruptCurrentTurn(): Promise<boolean>;
|
|
152
|
-
runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
|
|
157
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
153
158
|
useInitialImages?: boolean | undefined;
|
|
154
159
|
onProgress?: null | undefined;
|
|
155
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;
|
|
@@ -230,6 +233,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
230
233
|
}
|
|
231
234
|
: null,
|
|
232
235
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
236
|
+
capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[KIMI_PROVIDER_VARIANT] },
|
|
233
237
|
pid: this.transport.pid || undefined,
|
|
234
238
|
};
|
|
235
239
|
}
|
|
@@ -873,11 +877,15 @@ export class KimiCliSession extends EventEmitter {
|
|
|
873
877
|
}
|
|
874
878
|
return false;
|
|
875
879
|
}
|
|
876
|
-
async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
|
|
880
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
|
|
877
881
|
if (this.closeRequested || this.closed) {
|
|
878
882
|
throw this.createSessionClosedError();
|
|
879
883
|
}
|
|
880
|
-
|
|
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;
|
|
881
889
|
if (jsonSchema && typeof jsonSchema === "object" && effectivePrompt) {
|
|
882
890
|
effectivePrompt = injectJsonSchemaPrompt(effectivePrompt, jsonSchema);
|
|
883
891
|
}
|
|
@@ -930,7 +938,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
930
938
|
}, onProgress);
|
|
931
939
|
const promptResult = await Promise.race([
|
|
932
940
|
this.transport.request("prompt", {
|
|
933
|
-
user_input:
|
|
941
|
+
user_input: media.length === 0
|
|
942
|
+
? effectivePrompt
|
|
943
|
+
: buildKimiContent(effectivePrompt, media),
|
|
934
944
|
}),
|
|
935
945
|
turnFailurePromise,
|
|
936
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 };
|
package/dsh/cordis.yml
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Conductor-owned DeepSeek Harness runtime composition for the ai-sdk `dsh`
|
|
2
|
+
# backend. Launched by src/providers/dsh-sdk-session.js through
|
|
3
|
+
# @deepseek-ai/dsh-sdk-client (stdio JSON-RPC); stdout is reserved for
|
|
4
|
+
# JSON-RPC, so no console logger or terminal UI may be mounted here.
|
|
5
|
+
#
|
|
6
|
+
# Modeled on deepseek-harness's examples/jsonrpc-agent/cordis.yml (the
|
|
7
|
+
# upstream unattended coding-agent reference) and the Python SDK's bundled
|
|
8
|
+
# zero-config composition. Plugin versions are pinned in package.json; bare
|
|
9
|
+
# plugin names resolve from this file's directory up into the ai-sdk
|
|
10
|
+
# node_modules tree.
|
|
11
|
+
#
|
|
12
|
+
# Environment contract (set by DshSdkSession when spawning the runtime):
|
|
13
|
+
# DEEPSEEK_API_KEY required; resolved by the llm-deepseek adapter
|
|
14
|
+
# DEEPSEEK_BASE_URL optional API endpoint override
|
|
15
|
+
# DSH_CWD workspace directory for bash/fs tools
|
|
16
|
+
# DSH_SESSION_ROOT JSONL session persistence root
|
|
17
|
+
# DSH_SYSTEM_PROMPT optional persona override
|
|
18
|
+
|
|
19
|
+
# Stdio JSON-RPC server entry; without it the agent has no SDK client.
|
|
20
|
+
- id: sdk-jsonrpc-server
|
|
21
|
+
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
|
|
22
|
+
|
|
23
|
+
# Stock DeepSeek adapter. Resolves DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL from
|
|
24
|
+
# the launching environment; nothing secret is inlined here.
|
|
25
|
+
- id: llm-deepseek
|
|
26
|
+
name: '@deepseek-ai/dsh-llm-deepseek'
|
|
27
|
+
|
|
28
|
+
# Managed child-process groups for the bash executor.
|
|
29
|
+
- id: subprocess
|
|
30
|
+
name: '@deepseek-ai/dsh-subprocess-local'
|
|
31
|
+
|
|
32
|
+
- id: bash
|
|
33
|
+
name: '@deepseek-ai/dsh-bash-local'
|
|
34
|
+
config:
|
|
35
|
+
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
|
36
|
+
timeoutMs: 60000
|
|
37
|
+
|
|
38
|
+
# Agent spine: agent loop, system prompt, core coding tools.
|
|
39
|
+
- id: agent-spine
|
|
40
|
+
name: '@deepseek-ai/dsh-agent-spine-demo'
|
|
41
|
+
config:
|
|
42
|
+
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a coding agent.'
|
|
43
|
+
workspaceContext:
|
|
44
|
+
maxBytes: 65536
|
|
45
|
+
|
|
46
|
+
# JSONL persistence. Compression stays off so the resume dispatcher
|
|
47
|
+
# (src/resume/dsh.js) can read the header line back for cwd recovery.
|
|
48
|
+
- id: sessions
|
|
49
|
+
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
50
|
+
config:
|
|
51
|
+
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
|
|
52
|
+
compression: none
|
|
53
|
+
|
|
54
|
+
- id: session-checkpoints
|
|
55
|
+
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
|
56
|
+
|
|
57
|
+
# Subagent delegation (in-process spawn provider).
|
|
58
|
+
- id: subagent
|
|
59
|
+
name: '@deepseek-ai/dsh-subagent'
|
|
60
|
+
|
|
61
|
+
- id: subagent-spawn-in-process
|
|
62
|
+
name: '@deepseek-ai/dsh-subagent-spawn-in-process'
|
|
63
|
+
config:
|
|
64
|
+
providerName: spawn
|
|
65
|
+
|
|
66
|
+
- id: tool-subagent
|
|
67
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
68
|
+
config:
|
|
69
|
+
provider: spawn
|
|
70
|
+
toolName: subagent
|
|
71
|
+
enableRunInBackground: false
|
|
72
|
+
|
|
73
|
+
- id: tool-todo
|
|
74
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
75
|
+
config:
|
|
76
|
+
allowParallelInProgress: true
|
|
77
|
+
|
|
78
|
+
# Filesystem tools.
|
|
79
|
+
- id: fs-local
|
|
80
|
+
name: '@deepseek-ai/dsh-fs-local'
|
|
81
|
+
config:
|
|
82
|
+
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
|
83
|
+
|
|
84
|
+
- id: fs-observation-policy
|
|
85
|
+
name: '@deepseek-ai/dsh-fs-observation-policy'
|
|
86
|
+
|
|
87
|
+
- id: tool-fs
|
|
88
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
89
|
+
|
|
90
|
+
# Token accounting + context compaction.
|
|
91
|
+
- id: token-meter
|
|
92
|
+
name: '@deepseek-ai/dsh-token-meter'
|
|
93
|
+
|
|
94
|
+
- id: compaction-basic
|
|
95
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
96
|
+
config:
|
|
97
|
+
thresholdRatio: 0.8
|
|
98
|
+
retainRatio: 0.16
|
|
99
|
+
maxTokens: 8192
|
|
100
|
+
compactionRetries: 1
|