@love-moon/ai-sdk 0.7.2 → 0.7.4
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 +13 -0
- package/dist/manager/paths.d.ts +2 -0
- package/dist/manager/paths.js +3 -1
- package/dist/manager/quota/codex.d.ts +5 -0
- package/dist/manager/quota/codex.js +30 -7
- package/dist/manager/quota/copilot.d.ts +5 -1
- package/dist/manager/quota/copilot.js +9 -3
- package/dist/manager/quota/kimi.js +22 -9
- package/dist/manager/types.d.ts +2 -1
- package/dist/providers/kimi-cli-mode.d.ts +1 -0
- package/dist/providers/kimi-cli-mode.js +115 -0
- package/dist/providers/kimi-print-session.d.ts +9 -1
- package/dist/providers/kimi-print-session.js +128 -16
- package/dist/resume/kimi.d.ts +1 -1
- package/dist/resume/kimi.js +112 -7
- package/dist/session-factory.js +9 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.7.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d5eca1c: Adapt Codex quota parsing to the 0.144 weekly-only limit while retaining compatibility with legacy header layouts and cached snapshots.
|
|
8
|
+
- d5eca1c: Support Kimi Code CLI prompt sessions, current session storage, and credential locations while retaining legacy Kimi wire compatibility.
|
|
9
|
+
|
|
10
|
+
## 0.7.3
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 689fd07: Accept the current Copilot SDK token option spelling in quota helpers.
|
|
15
|
+
|
|
3
16
|
## 0.7.2
|
|
4
17
|
|
|
5
18
|
## 0.7.1
|
package/dist/manager/paths.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ export declare function expandHome(p: string): string;
|
|
|
2
2
|
export declare const DEFAULT_CONDUCTOR_CONFIG: string;
|
|
3
3
|
export declare const DEFAULT_CODEX_AUTH: string;
|
|
4
4
|
export declare const DEFAULT_CODEX_CONFIG: string;
|
|
5
|
+
export declare const DEFAULT_KIMI_CODE_HOME: string;
|
|
5
6
|
export declare const DEFAULT_KIMI_CREDENTIAL: string;
|
|
7
|
+
export declare const LEGACY_KIMI_CREDENTIAL: string;
|
|
6
8
|
export declare const DEFAULT_QUOTA_CACHE_DIR: string;
|
package/dist/manager/paths.js
CHANGED
|
@@ -12,5 +12,7 @@ export function expandHome(p) {
|
|
|
12
12
|
export const DEFAULT_CONDUCTOR_CONFIG = join(homedir(), ".conductor", "config.yaml");
|
|
13
13
|
export const DEFAULT_CODEX_AUTH = join(homedir(), ".codex", "auth.json");
|
|
14
14
|
export const DEFAULT_CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
|
|
15
|
-
export const
|
|
15
|
+
export const DEFAULT_KIMI_CODE_HOME = process.env.KIMI_CODE_HOME?.trim() || join(homedir(), ".kimi-code");
|
|
16
|
+
export const DEFAULT_KIMI_CREDENTIAL = join(DEFAULT_KIMI_CODE_HOME, "credentials", "kimi-code.json");
|
|
17
|
+
export const LEGACY_KIMI_CREDENTIAL = join(homedir(), ".kimi", "credentials", "kimi-code.json");
|
|
16
18
|
export const DEFAULT_QUOTA_CACHE_DIR = join(homedir(), ".conductor", "cache", "ai-manager");
|
|
@@ -14,6 +14,11 @@ export interface GetCodexQuotaOptions {
|
|
|
14
14
|
/** Override cache directory (mainly for tests). */
|
|
15
15
|
cacheDir?: string;
|
|
16
16
|
}
|
|
17
|
+
/** Exported for protocol compatibility tests. */
|
|
18
|
+
export declare function parseCodexHeaders(map: Record<string, string>, extras: {
|
|
19
|
+
email?: string;
|
|
20
|
+
accountId?: string;
|
|
21
|
+
}): Omit<CodexQuota, "tool" | "fetchedAt" | "source">;
|
|
17
22
|
export declare function getCodexQuota(opts?: GetCodexQuotaOptions): Promise<CodexQuota>;
|
|
18
23
|
/**
|
|
19
24
|
* Read the on-disk quota cache for a given codex auth.json without triggering
|
|
@@ -33,6 +33,15 @@ const RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
|
33
33
|
const DEFAULT_TTL = 60; // seconds
|
|
34
34
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
35
35
|
function windowFromHeaders(map, prefix) {
|
|
36
|
+
const suffixes = [
|
|
37
|
+
"used-percent",
|
|
38
|
+
"reset-after-seconds",
|
|
39
|
+
"reset-at",
|
|
40
|
+
"window-minutes",
|
|
41
|
+
];
|
|
42
|
+
if (!suffixes.some((suffix) => map[`${prefix}-${suffix}`] !== undefined)) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
36
45
|
const usedPercent = num(map, `${prefix}-used-percent`) ?? 0;
|
|
37
46
|
return {
|
|
38
47
|
usedPercent,
|
|
@@ -42,12 +51,19 @@ function windowFromHeaders(map, prefix) {
|
|
|
42
51
|
windowMinutes: num(map, `${prefix}-window-minutes`),
|
|
43
52
|
};
|
|
44
53
|
}
|
|
45
|
-
|
|
54
|
+
/** Exported for protocol compatibility tests. */
|
|
55
|
+
export function parseCodexHeaders(map, extras) {
|
|
56
|
+
const primary = windowFromHeaders(map, "x-codex-primary");
|
|
57
|
+
const secondary = windowFromHeaders(map, "x-codex-secondary");
|
|
58
|
+
// Codex CLI 0.144 removed the 5h limit. Its only Codex window is now the
|
|
59
|
+
// 10080-minute weekly window in `primary`, with `secondary` absent. Older
|
|
60
|
+
// responses used primary=5h and secondary=weekly, so prefer an explicitly
|
|
61
|
+
// weekly-sized window and then fall back to the legacy secondary position.
|
|
62
|
+
const weekly = [primary, secondary].find((window) => (window?.windowMinutes ?? 0) >= 7 * 24 * 60) ?? secondary ?? primary ?? { usedPercent: 0, remainingPercent: 0 };
|
|
46
63
|
return {
|
|
47
64
|
plan: str(map, "x-codex-plan-type"),
|
|
48
65
|
activeLimit: str(map, "x-codex-active-limit"),
|
|
49
|
-
|
|
50
|
-
weekly: windowFromHeaders(map, "x-codex-secondary"),
|
|
66
|
+
weekly,
|
|
51
67
|
credits: {
|
|
52
68
|
hasCredits: bool(map, "x-codex-credits-has-credits") ?? false,
|
|
53
69
|
balance: str(map, "x-codex-credits-balance"),
|
|
@@ -71,7 +87,7 @@ export async function getCodexQuota(opts = {}) {
|
|
|
71
87
|
if (!opts.forceRefresh && ttl > 0) {
|
|
72
88
|
const cached = await readCache(file);
|
|
73
89
|
if (isFresh(cached, ttl) && cached) {
|
|
74
|
-
return { ...cached.value, source: "cached" };
|
|
90
|
+
return { ...withoutLegacyFiveHour(cached.value), source: "cached" };
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
const model = opts.model ??
|
|
@@ -140,7 +156,7 @@ async function fallbackFromCache(file, ttl, error) {
|
|
|
140
156
|
const cached = await readCache(file);
|
|
141
157
|
if (cached) {
|
|
142
158
|
return {
|
|
143
|
-
...cached.value,
|
|
159
|
+
...withoutLegacyFiveHour(cached.value),
|
|
144
160
|
source: isFresh(cached, ttl) ? "cached" : "stale",
|
|
145
161
|
error,
|
|
146
162
|
};
|
|
@@ -164,19 +180,26 @@ export async function readCachedCodexQuota(codexAuthPath, opts = {}) {
|
|
|
164
180
|
const entry = await readCache(file);
|
|
165
181
|
if (!entry)
|
|
166
182
|
return null;
|
|
167
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
...withoutLegacyFiveHour(entry.value),
|
|
185
|
+
source: "cached",
|
|
186
|
+
fetchedAt: entry.fetchedAt,
|
|
187
|
+
};
|
|
168
188
|
}
|
|
169
189
|
catch {
|
|
170
190
|
return null;
|
|
171
191
|
}
|
|
172
192
|
}
|
|
193
|
+
function withoutLegacyFiveHour(quota) {
|
|
194
|
+
const { fiveHour: _legacyFiveHour, ...current } = quota;
|
|
195
|
+
return current;
|
|
196
|
+
}
|
|
173
197
|
function emptyQuota(source, error) {
|
|
174
198
|
return {
|
|
175
199
|
tool: "codex",
|
|
176
200
|
source,
|
|
177
201
|
error,
|
|
178
202
|
fetchedAt: Math.floor(Date.now() / 1000),
|
|
179
|
-
fiveHour: { usedPercent: 0, remainingPercent: 0 },
|
|
180
203
|
weekly: { usedPercent: 0, remainingPercent: 0 },
|
|
181
204
|
};
|
|
182
205
|
}
|
|
@@ -30,6 +30,10 @@ interface CopilotClientLike {
|
|
|
30
30
|
interface CopilotSdkModule {
|
|
31
31
|
CopilotClient: new (options?: CopilotClientOptions) => CopilotClientLike;
|
|
32
32
|
}
|
|
33
|
+
type CopilotClientOptionsWithLegacyToken = CopilotClientOptions & {
|
|
34
|
+
githubToken?: string;
|
|
35
|
+
gitHubToken?: string;
|
|
36
|
+
};
|
|
33
37
|
interface ResolveBundledCopilotCliPathOptions {
|
|
34
38
|
platform?: NodeJS.Platform | string;
|
|
35
39
|
arch?: string;
|
|
@@ -47,7 +51,7 @@ export interface GetCopilotQuotaOptions {
|
|
|
47
51
|
/** Explicit token to pass to the Copilot SDK; otherwise SDK auth discovery is used. */
|
|
48
52
|
githubToken?: string;
|
|
49
53
|
/** SDK client options. Useful for enterprise hosts or tests. */
|
|
50
|
-
clientOptions?:
|
|
54
|
+
clientOptions?: CopilotClientOptionsWithLegacyToken;
|
|
51
55
|
/** Override global fetch for GitHub login lookup tests. */
|
|
52
56
|
fetcher?: typeof fetch;
|
|
53
57
|
/** Override SDK module for tests. */
|
|
@@ -371,7 +371,10 @@ function resolveClientOptions(opts) {
|
|
|
371
371
|
useLoggedInUser: explicitGithubToken
|
|
372
372
|
? opts.clientOptions?.useLoggedInUser
|
|
373
373
|
: opts.clientOptions?.useLoggedInUser ?? true,
|
|
374
|
-
...(
|
|
374
|
+
...(explicitGithubToken ? {
|
|
375
|
+
githubToken: explicitGithubToken,
|
|
376
|
+
gitHubToken: explicitGithubToken,
|
|
377
|
+
} : {}),
|
|
375
378
|
};
|
|
376
379
|
if (options.cliPath === undefined &&
|
|
377
380
|
options.cliUrl === undefined &&
|
|
@@ -384,7 +387,7 @@ function resolveClientOptions(opts) {
|
|
|
384
387
|
return options;
|
|
385
388
|
}
|
|
386
389
|
function resolveExplicitGithubToken(opts) {
|
|
387
|
-
return opts.githubToken ?? opts.clientOptions?.gitHubToken;
|
|
390
|
+
return opts.githubToken ?? opts.clientOptions?.githubToken ?? opts.clientOptions?.gitHubToken;
|
|
388
391
|
}
|
|
389
392
|
function hasExplicitCopilotCliPathEnv(env) {
|
|
390
393
|
return Boolean(typeof env?.COPILOT_CLI_PATH === "string" && env.COPILOT_CLI_PATH.trim());
|
|
@@ -472,7 +475,10 @@ function resolveKnownGitHubHost(rawHost) {
|
|
|
472
475
|
return host || envHost || undefined;
|
|
473
476
|
}
|
|
474
477
|
function resolveGitHubLoginToken(opts) {
|
|
475
|
-
return opts.githubToken
|
|
478
|
+
return opts.githubToken
|
|
479
|
+
?? opts.clientOptions?.githubToken
|
|
480
|
+
?? opts.clientOptions?.gitHubToken
|
|
481
|
+
?? process.env.GITHUB_TOKEN;
|
|
476
482
|
}
|
|
477
483
|
async function fetchCopilotUserQuota(authInfo, opts, doFetch, timeoutMs) {
|
|
478
484
|
if (!timeoutMs || timeoutMs <= 0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, writeFile, rename } from "node:fs/promises";
|
|
2
|
-
import { DEFAULT_KIMI_CREDENTIAL } from "../paths.js";
|
|
2
|
+
import { DEFAULT_KIMI_CREDENTIAL, LEGACY_KIMI_CREDENTIAL } from "../paths.js";
|
|
3
3
|
import { cacheFile, fingerprintKey, isFresh, readCache, writeCache } from "./cache.js";
|
|
4
4
|
const USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
5
5
|
const OAUTH_TOKEN_URL = "https://auth.kimi.com/api/oauth/token";
|
|
@@ -8,19 +8,32 @@ const DEFAULT_TTL = 60;
|
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
9
9
|
const REFRESH_LEEWAY_SECONDS = 60;
|
|
10
10
|
export async function getKimiQuota(opts = {}) {
|
|
11
|
-
const
|
|
11
|
+
const credentialPaths = opts.credentialPath
|
|
12
|
+
? [opts.credentialPath]
|
|
13
|
+
: [DEFAULT_KIMI_CREDENTIAL, LEGACY_KIMI_CREDENTIAL];
|
|
12
14
|
const ttl = opts.ttlSeconds ?? DEFAULT_TTL;
|
|
13
15
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14
16
|
const doFetch = opts.fetcher ?? fetch;
|
|
17
|
+
let credentialPath = credentialPaths[0];
|
|
15
18
|
let cred;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
let credentialError = "credential missing access_token";
|
|
20
|
+
for (const candidatePath of credentialPaths) {
|
|
21
|
+
try {
|
|
22
|
+
const candidate = JSON.parse(await readFile(candidatePath, "utf8"));
|
|
23
|
+
if (!candidate.access_token) {
|
|
24
|
+
credentialError = "credential missing access_token";
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
credentialPath = candidatePath;
|
|
28
|
+
cred = candidate;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
credentialError = `credential not readable: ${err?.message ?? err}`;
|
|
33
|
+
}
|
|
21
34
|
}
|
|
22
|
-
if (!cred
|
|
23
|
-
return emptyQuota("unknown",
|
|
35
|
+
if (!cred) {
|
|
36
|
+
return emptyQuota("unknown", `kimi ${credentialError}`);
|
|
24
37
|
}
|
|
25
38
|
const fp = fingerprintKey(["kimi", cred.access_token.slice(-32)]);
|
|
26
39
|
const file = cacheFile("kimi", fp, opts.cacheDir);
|
package/dist/manager/types.d.ts
CHANGED
|
@@ -37,7 +37,8 @@ export interface CodexQuota {
|
|
|
37
37
|
tool: "codex";
|
|
38
38
|
plan?: string;
|
|
39
39
|
activeLimit?: string;
|
|
40
|
-
|
|
40
|
+
/** @deprecated Codex CLI 0.144+ no longer exposes a 5h limit. */
|
|
41
|
+
fiveHour?: QuotaWindow;
|
|
41
42
|
weekly: QuotaWindow;
|
|
42
43
|
credits?: {
|
|
43
44
|
hasCredits: boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveKimiCliMode(options?: {}): Promise<"prompt" | "wire">;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { emitLog, normalizeLogger, parseCommandParts, sanitizeForLog, } from "../shared.js";
|
|
3
|
+
const DEFAULT_KIMI_COMMAND = "kimi";
|
|
4
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 5_000;
|
|
5
|
+
const MAX_PROBE_OUTPUT_CHARS = 128 * 1024;
|
|
6
|
+
function normalizeMode(value) {
|
|
7
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
8
|
+
if (normalized === "prompt" || normalized === "kimi-code" || normalized === "modern") {
|
|
9
|
+
return "prompt";
|
|
10
|
+
}
|
|
11
|
+
if (normalized === "wire" || normalized === "legacy") {
|
|
12
|
+
return "wire";
|
|
13
|
+
}
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
function resolveCommandLine(options = {}) {
|
|
17
|
+
return (process.env.CONDUCTOR_KIMI_COMMAND ||
|
|
18
|
+
options.commandLine ||
|
|
19
|
+
process.env.CONDUCTOR_CLI_COMMAND ||
|
|
20
|
+
DEFAULT_KIMI_COMMAND);
|
|
21
|
+
}
|
|
22
|
+
function appendProbeOutput(current, chunk) {
|
|
23
|
+
if (current.length >= MAX_PROBE_OUTPUT_CHARS) {
|
|
24
|
+
return current;
|
|
25
|
+
}
|
|
26
|
+
return `${current}${String(chunk || "")}`.slice(0, MAX_PROBE_OUTPUT_CHARS);
|
|
27
|
+
}
|
|
28
|
+
async function readKimiHelp(options = {}) {
|
|
29
|
+
const { command, args } = parseCommandParts(resolveCommandLine(options));
|
|
30
|
+
if (!command) {
|
|
31
|
+
throw new Error("Invalid kimi command");
|
|
32
|
+
}
|
|
33
|
+
const cwd = typeof options.cwd === "string" && options.cwd.trim()
|
|
34
|
+
? options.cwd.trim()
|
|
35
|
+
: process.cwd();
|
|
36
|
+
const env = {
|
|
37
|
+
...process.env,
|
|
38
|
+
...(options.env && typeof options.env === "object" ? options.env : {}),
|
|
39
|
+
};
|
|
40
|
+
return await new Promise((resolve, reject) => {
|
|
41
|
+
const child = spawn(command, [...args, "--help"], {
|
|
42
|
+
cwd,
|
|
43
|
+
env,
|
|
44
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
45
|
+
});
|
|
46
|
+
let output = "";
|
|
47
|
+
let settled = false;
|
|
48
|
+
const timeoutMs = Number.isFinite(options.kimiCliProbeTimeoutMs)
|
|
49
|
+
? Math.max(100, Number(options.kimiCliProbeTimeoutMs))
|
|
50
|
+
: DEFAULT_PROBE_TIMEOUT_MS;
|
|
51
|
+
const timeoutId = setTimeout(() => {
|
|
52
|
+
if (settled) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
settled = true;
|
|
56
|
+
try {
|
|
57
|
+
child.kill("SIGTERM");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// best effort
|
|
61
|
+
}
|
|
62
|
+
reject(new Error(`Kimi CLI capability probe timed out after ${timeoutMs}ms`));
|
|
63
|
+
}, timeoutMs);
|
|
64
|
+
child.stdout.on("data", (chunk) => {
|
|
65
|
+
output = appendProbeOutput(output, chunk);
|
|
66
|
+
});
|
|
67
|
+
child.stderr.on("data", (chunk) => {
|
|
68
|
+
output = appendProbeOutput(output, chunk);
|
|
69
|
+
});
|
|
70
|
+
child.once("error", (error) => {
|
|
71
|
+
if (settled) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
settled = true;
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
child.once("close", () => {
|
|
79
|
+
if (settled) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
settled = true;
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
resolve(output);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function resolveKimiCliMode(options = {}) {
|
|
89
|
+
if (options.transport) {
|
|
90
|
+
return "wire";
|
|
91
|
+
}
|
|
92
|
+
const configuredMode = normalizeMode(options.kimiCliMode ||
|
|
93
|
+
options.env?.CONDUCTOR_KIMI_CLI_MODE ||
|
|
94
|
+
process.env.CONDUCTOR_KIMI_CLI_MODE);
|
|
95
|
+
if (configuredMode) {
|
|
96
|
+
return configuredMode;
|
|
97
|
+
}
|
|
98
|
+
const logger = normalizeLogger(options.logger);
|
|
99
|
+
try {
|
|
100
|
+
const helpText = await readKimiHelp(options);
|
|
101
|
+
if (/(^|\s)--wire(?:\s|$)/m.test(helpText)) {
|
|
102
|
+
emitLog(logger, "[kimi-detect] using legacy wire mode");
|
|
103
|
+
return "wire";
|
|
104
|
+
}
|
|
105
|
+
if (/(^|\s)--prompt(?:\s|,|$)/m.test(helpText) && helpText.includes("--output-format")) {
|
|
106
|
+
emitLog(logger, "[kimi-detect] using Kimi Code prompt mode");
|
|
107
|
+
return "prompt";
|
|
108
|
+
}
|
|
109
|
+
emitLog(logger, "[kimi-detect] CLI mode was not recognized; keeping legacy wire mode");
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
emitLog(logger, `[kimi-detect] capability probe failed; keeping legacy wire mode: ${sanitizeForLog(error?.message || error, 180)}`);
|
|
113
|
+
}
|
|
114
|
+
return "wire";
|
|
115
|
+
}
|
|
@@ -4,11 +4,13 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
4
4
|
options: {};
|
|
5
5
|
logger: any;
|
|
6
6
|
cwd: any;
|
|
7
|
+
cliMode: string;
|
|
7
8
|
resumeSessionId: any;
|
|
8
9
|
sessionId: any;
|
|
9
10
|
sessionInfo: {
|
|
10
11
|
backend: string;
|
|
11
12
|
sessionId: any;
|
|
13
|
+
sessionIdDeferred: boolean;
|
|
12
14
|
model: any;
|
|
13
15
|
};
|
|
14
16
|
history: any[];
|
|
@@ -21,6 +23,7 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
21
23
|
items: never[];
|
|
22
24
|
stderrTail: never[];
|
|
23
25
|
settled: boolean;
|
|
26
|
+
interruptRequested: boolean;
|
|
24
27
|
} | null;
|
|
25
28
|
currentTurnStatus: any;
|
|
26
29
|
sessionAnnounced: boolean;
|
|
@@ -47,6 +50,7 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
47
50
|
sessionInfo: {
|
|
48
51
|
backend: string;
|
|
49
52
|
sessionId: any;
|
|
53
|
+
sessionIdDeferred: boolean;
|
|
50
54
|
model: any;
|
|
51
55
|
} | null;
|
|
52
56
|
useSessionFileReplyStream: boolean;
|
|
@@ -61,12 +65,14 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
61
65
|
getSessionInfo(): {
|
|
62
66
|
backend: string;
|
|
63
67
|
sessionId: any;
|
|
68
|
+
sessionIdDeferred: boolean;
|
|
64
69
|
model: any;
|
|
65
70
|
} | null;
|
|
66
71
|
getCurrentTurnStatus(): any;
|
|
67
72
|
ensureSessionInfo(): Promise<{
|
|
68
73
|
backend: string;
|
|
69
74
|
sessionId: any;
|
|
75
|
+
sessionIdDeferred: boolean;
|
|
70
76
|
model: any;
|
|
71
77
|
} | null>;
|
|
72
78
|
getSessionUsageSummary(): Promise<{
|
|
@@ -87,12 +93,13 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
87
93
|
setSessionReplyTarget(replyTo: any): void;
|
|
88
94
|
getCurrentReplyTarget(): string | undefined;
|
|
89
95
|
announceSession(): void;
|
|
96
|
+
adoptSessionId(sessionId: any): void;
|
|
90
97
|
createSessionClosedError(): Error;
|
|
91
98
|
updateCurrentTurnStatus(payload: any): void;
|
|
92
99
|
emitWorkingStatus(payload: any, onProgress?: null): Promise<any>;
|
|
93
100
|
emitAssistantMessage(text: any): Promise<void>;
|
|
94
101
|
buildPrompt(promptText: any): string;
|
|
95
|
-
buildPrintArgs(): string[];
|
|
102
|
+
buildPrintArgs(promptText?: string): string[];
|
|
96
103
|
buildUserMessage(promptText: any, { useInitialImages }?: {
|
|
97
104
|
useInitialImages?: boolean | undefined;
|
|
98
105
|
}): {
|
|
@@ -100,6 +107,7 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
100
107
|
content: any;
|
|
101
108
|
};
|
|
102
109
|
maybeEmitAuthRequired(stderrTail: any): void;
|
|
110
|
+
interruptCurrentTurn(): Promise<boolean>;
|
|
103
111
|
runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
|
|
104
112
|
useInitialImages?: boolean | undefined;
|
|
105
113
|
onProgress?: null | undefined;
|
|
@@ -85,7 +85,7 @@ function statusLineForPhase(phase) {
|
|
|
85
85
|
return "Kimi is working";
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
function filterKimiPrintBaseArgs(args) {
|
|
88
|
+
function filterKimiPrintBaseArgs(args, cliMode = "legacy-print") {
|
|
89
89
|
const filtered = [];
|
|
90
90
|
let skipNext = false;
|
|
91
91
|
for (const rawArg of Array.isArray(args) ? args : []) {
|
|
@@ -97,7 +97,18 @@ function filterKimiPrintBaseArgs(args) {
|
|
|
97
97
|
skipNext = false;
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
|
-
if (arg === "--wire" ||
|
|
100
|
+
if (arg === "--wire" ||
|
|
101
|
+
arg === "--print" ||
|
|
102
|
+
arg === "--final-message-only" ||
|
|
103
|
+
arg === "--yolo" ||
|
|
104
|
+
arg === "--yes" ||
|
|
105
|
+
arg === "--auto-approve" ||
|
|
106
|
+
arg === "--auto" ||
|
|
107
|
+
arg === "--plan" ||
|
|
108
|
+
arg === "--continue" ||
|
|
109
|
+
arg === "-C" ||
|
|
110
|
+
arg === "-y" ||
|
|
111
|
+
(cliMode === "prompt" && arg === "-c")) {
|
|
101
112
|
continue;
|
|
102
113
|
}
|
|
103
114
|
if (arg === "--input-format" ||
|
|
@@ -105,8 +116,15 @@ function filterKimiPrintBaseArgs(args) {
|
|
|
105
116
|
arg === "--prompt" ||
|
|
106
117
|
arg === "--command" ||
|
|
107
118
|
arg === "--session" ||
|
|
119
|
+
arg === "--resume" ||
|
|
108
120
|
arg === "--work-dir" ||
|
|
109
|
-
arg === "--model"
|
|
121
|
+
arg === "--model" ||
|
|
122
|
+
arg === "-S" ||
|
|
123
|
+
arg === "-r" ||
|
|
124
|
+
arg === "-m" ||
|
|
125
|
+
arg === "-p" ||
|
|
126
|
+
arg === "-w" ||
|
|
127
|
+
(cliMode === "legacy-print" && arg === "-c")) {
|
|
110
128
|
skipNext = true;
|
|
111
129
|
continue;
|
|
112
130
|
}
|
|
@@ -115,14 +133,26 @@ function filterKimiPrintBaseArgs(args) {
|
|
|
115
133
|
arg.startsWith("--prompt=") ||
|
|
116
134
|
arg.startsWith("--command=") ||
|
|
117
135
|
arg.startsWith("--session=") ||
|
|
136
|
+
arg.startsWith("--resume=") ||
|
|
118
137
|
arg.startsWith("--work-dir=") ||
|
|
119
|
-
arg.startsWith("--model=")
|
|
138
|
+
arg.startsWith("--model=") ||
|
|
139
|
+
["-S", "-r", "-m", "-p", "-w", "-c"].some((prefix) => arg.startsWith(prefix) && arg.length > prefix.length)) {
|
|
120
140
|
continue;
|
|
121
141
|
}
|
|
122
142
|
filtered.push(arg);
|
|
123
143
|
}
|
|
124
144
|
return filtered;
|
|
125
145
|
}
|
|
146
|
+
function quoteShellArg(value) {
|
|
147
|
+
const normalized = String(value ?? "");
|
|
148
|
+
if (!normalized) {
|
|
149
|
+
return "''";
|
|
150
|
+
}
|
|
151
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(normalized)) {
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
return `'${normalized.replace(/'/g, `'\\''`)}'`;
|
|
155
|
+
}
|
|
126
156
|
function normalizeTextContent(content) {
|
|
127
157
|
if (typeof content === "string") {
|
|
128
158
|
return content;
|
|
@@ -170,11 +200,13 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
170
200
|
typeof options.cwd === "string" && options.cwd.trim()
|
|
171
201
|
? options.cwd.trim()
|
|
172
202
|
: process.cwd();
|
|
203
|
+
this.cliMode = options.kimiCliMode === "prompt" ? "prompt" : "legacy-print";
|
|
173
204
|
this.resumeSessionId = typeof options.resumeSessionId === "string" ? options.resumeSessionId.trim() : "";
|
|
174
|
-
this.sessionId = this.resumeSessionId || randomUUID();
|
|
205
|
+
this.sessionId = this.resumeSessionId || (this.cliMode === "prompt" ? "" : randomUUID());
|
|
175
206
|
this.sessionInfo = {
|
|
176
207
|
backend: this.backend,
|
|
177
|
-
sessionId: this.sessionId,
|
|
208
|
+
sessionId: this.sessionId || undefined,
|
|
209
|
+
sessionIdDeferred: this.cliMode === "prompt" && !this.sessionId,
|
|
178
210
|
model: typeof options.model === "string" && options.model.trim()
|
|
179
211
|
? options.model.trim()
|
|
180
212
|
: this.backend,
|
|
@@ -207,7 +239,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
207
239
|
throw new Error("Invalid kimi print command");
|
|
208
240
|
}
|
|
209
241
|
this.command = command;
|
|
210
|
-
this.baseArgs = filterKimiPrintBaseArgs(args);
|
|
242
|
+
this.baseArgs = filterKimiPrintBaseArgs(args, this.cliMode);
|
|
211
243
|
}
|
|
212
244
|
writeLog(message) {
|
|
213
245
|
emitLog(this.logger, message);
|
|
@@ -227,7 +259,21 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
227
259
|
};
|
|
228
260
|
}
|
|
229
261
|
buildManualResumeCommand() {
|
|
230
|
-
|
|
262
|
+
if (!this.sessionId) {
|
|
263
|
+
return "";
|
|
264
|
+
}
|
|
265
|
+
const parts = [...this.baseArgs];
|
|
266
|
+
if (this.cliMode === "legacy-print") {
|
|
267
|
+
parts.push("--work-dir", this.cwd);
|
|
268
|
+
}
|
|
269
|
+
parts.push("--session", this.sessionId);
|
|
270
|
+
if (typeof this.options.model === "string" && this.options.model.trim()) {
|
|
271
|
+
parts.push("--model", this.options.model.trim());
|
|
272
|
+
}
|
|
273
|
+
const command = [this.command, ...parts].map(quoteShellArg).join(" ");
|
|
274
|
+
return this.cliMode === "prompt"
|
|
275
|
+
? `cd ${quoteShellArg(this.cwd)} && ${command}`
|
|
276
|
+
: command;
|
|
231
277
|
}
|
|
232
278
|
getSnapshot() {
|
|
233
279
|
return {
|
|
@@ -255,7 +301,9 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
255
301
|
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
256
302
|
}
|
|
257
303
|
async ensureSessionInfo() {
|
|
258
|
-
this.
|
|
304
|
+
if (!this.sessionInfo?.sessionIdDeferred) {
|
|
305
|
+
this.announceSession();
|
|
306
|
+
}
|
|
259
307
|
return this.getSessionInfo();
|
|
260
308
|
}
|
|
261
309
|
async getSessionUsageSummary() {
|
|
@@ -294,12 +342,26 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
294
342
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
295
343
|
}
|
|
296
344
|
announceSession() {
|
|
297
|
-
if (this.sessionAnnounced) {
|
|
345
|
+
if (this.sessionAnnounced || !this.sessionId) {
|
|
298
346
|
return;
|
|
299
347
|
}
|
|
300
348
|
this.sessionAnnounced = true;
|
|
301
349
|
this.emit("session", this.getSessionInfo());
|
|
302
350
|
}
|
|
351
|
+
adoptSessionId(sessionId) {
|
|
352
|
+
const normalizedSessionId = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
353
|
+
if (!normalizedSessionId || normalizedSessionId === this.sessionId) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
this.sessionId = normalizedSessionId;
|
|
357
|
+
this.sessionInfo = {
|
|
358
|
+
...this.sessionInfo,
|
|
359
|
+
sessionId: normalizedSessionId,
|
|
360
|
+
sessionIdDeferred: false,
|
|
361
|
+
};
|
|
362
|
+
this.trace(`session ready id=${sanitizeForLog(normalizedSessionId, 120)}`);
|
|
363
|
+
this.announceSession();
|
|
364
|
+
}
|
|
303
365
|
createSessionClosedError() {
|
|
304
366
|
return createTurnError("Kimi print session closed", {
|
|
305
367
|
reason: "session_closed",
|
|
@@ -357,8 +419,19 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
357
419
|
this.pendingHistorySeed = false;
|
|
358
420
|
return buildHistoryPrompt(this.history, normalizedPrompt);
|
|
359
421
|
}
|
|
360
|
-
buildPrintArgs() {
|
|
422
|
+
buildPrintArgs(promptText = "") {
|
|
361
423
|
const args = [...this.baseArgs];
|
|
424
|
+
if (this.cliMode === "prompt") {
|
|
425
|
+
args.push("--output-format=stream-json");
|
|
426
|
+
if (this.sessionId) {
|
|
427
|
+
args.push(`--session=${this.sessionId}`);
|
|
428
|
+
}
|
|
429
|
+
if (typeof this.options.model === "string" && this.options.model.trim()) {
|
|
430
|
+
args.push(`--model=${this.options.model.trim()}`);
|
|
431
|
+
}
|
|
432
|
+
args.push("--prompt", promptText);
|
|
433
|
+
return args;
|
|
434
|
+
}
|
|
362
435
|
args.push("--print");
|
|
363
436
|
args.push("--input-format=stream-json");
|
|
364
437
|
args.push("--output-format=stream-json");
|
|
@@ -413,6 +486,20 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
413
486
|
message: lastMessage || "Kimi authentication required",
|
|
414
487
|
});
|
|
415
488
|
}
|
|
489
|
+
async interruptCurrentTurn() {
|
|
490
|
+
const currentTurn = this.currentTurn;
|
|
491
|
+
if (!currentTurn?.child) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
currentTurn.interruptRequested = true;
|
|
495
|
+
try {
|
|
496
|
+
currentTurn.child.kill("SIGINT");
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
416
503
|
async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
|
|
417
504
|
if (this.closeRequested || this.closed) {
|
|
418
505
|
throw this.createSessionClosedError();
|
|
@@ -432,6 +519,10 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
432
519
|
effectivePrompt = injectJsonSchemaPrompt(promptWithSchema, jsonSchema);
|
|
433
520
|
}
|
|
434
521
|
}
|
|
522
|
+
if (this.cliMode === "prompt" && imagePaths.length > 0) {
|
|
523
|
+
const imageContext = imagePaths.map((item, index) => `${index + 1}. ${item}`).join("\n");
|
|
524
|
+
effectivePrompt = `${effectivePrompt || "Analyze the attached images."}\n\nAttached image files:\n${imageContext}`;
|
|
525
|
+
}
|
|
435
526
|
if (!effectivePrompt && imagePaths.length === 0) {
|
|
436
527
|
return buildEmptyTurnResult();
|
|
437
528
|
}
|
|
@@ -443,6 +534,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
443
534
|
items: [],
|
|
444
535
|
stderrTail: [],
|
|
445
536
|
settled: false,
|
|
537
|
+
interruptRequested: false,
|
|
446
538
|
};
|
|
447
539
|
this.currentTurn = currentTurn;
|
|
448
540
|
try {
|
|
@@ -451,8 +543,11 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
451
543
|
reply_in_progress: true,
|
|
452
544
|
status_line: statusLineForPhase("turn_started"),
|
|
453
545
|
}, onProgress);
|
|
454
|
-
const args = this.buildPrintArgs();
|
|
455
|
-
|
|
546
|
+
const args = this.buildPrintArgs(effectivePrompt);
|
|
547
|
+
const argsForLog = this.cliMode === "prompt"
|
|
548
|
+
? [...args.slice(0, -2), "--prompt", `<redacted:${effectivePrompt.length} chars>`]
|
|
549
|
+
: args;
|
|
550
|
+
this.trace(`spawn ${[this.command, ...argsForLog].join(" ")}`);
|
|
456
551
|
const result = await new Promise((resolve, reject) => {
|
|
457
552
|
const child = spawn(this.command, args, {
|
|
458
553
|
cwd: this.cwd,
|
|
@@ -508,6 +603,13 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
508
603
|
}
|
|
509
604
|
currentTurn.items.push(payload);
|
|
510
605
|
const role = String(payload?.role || "").trim().toLowerCase();
|
|
606
|
+
if (role === "meta") {
|
|
607
|
+
const type = String(payload?.type || "").trim().toLowerCase();
|
|
608
|
+
if (type === "session.resume_hint") {
|
|
609
|
+
this.adoptSessionId(payload?.session_id);
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
511
613
|
if (role === "assistant") {
|
|
512
614
|
const assistantText = normalizeTextContent(payload.content);
|
|
513
615
|
if (Array.isArray(payload?.tool_calls) && payload.tool_calls.length > 0) {
|
|
@@ -552,11 +654,19 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
552
654
|
child.on("error", (error) => {
|
|
553
655
|
settle(error);
|
|
554
656
|
});
|
|
555
|
-
child.on("
|
|
657
|
+
child.on("close", (code, signal) => {
|
|
556
658
|
if (this.closeRequested) {
|
|
557
659
|
settle(this.createSessionClosedError());
|
|
558
660
|
return;
|
|
559
661
|
}
|
|
662
|
+
if (currentTurn.interruptRequested) {
|
|
663
|
+
settle(createTurnError("Kimi print turn interrupted", {
|
|
664
|
+
reason: "turn_interrupted",
|
|
665
|
+
code,
|
|
666
|
+
signal,
|
|
667
|
+
}));
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
560
670
|
if (code !== 0) {
|
|
561
671
|
this.maybeEmitAuthRequired(currentTurn.stderrTail);
|
|
562
672
|
const stderrSummary = sanitizeForLog(currentTurn.stderrTail.filter(Boolean).at(-1), 200);
|
|
@@ -574,8 +684,10 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
574
684
|
items: [...currentTurn.items],
|
|
575
685
|
});
|
|
576
686
|
});
|
|
577
|
-
|
|
578
|
-
|
|
687
|
+
if (this.cliMode === "legacy-print") {
|
|
688
|
+
const userMessage = this.buildUserMessage(effectivePrompt, { useInitialImages });
|
|
689
|
+
child.stdin.write(`${JSON.stringify(userMessage)}\n`);
|
|
690
|
+
}
|
|
579
691
|
child.stdin.end();
|
|
580
692
|
});
|
|
581
693
|
if (result?.text) {
|
package/dist/resume/kimi.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function buildCliArgs(sessionId: any): string[];
|
|
2
|
-
export function findSessionPath(sessionId: any, options?: {}): Promise<
|
|
2
|
+
export function findSessionPath(sessionId: any, options?: {}): Promise<any>;
|
|
3
3
|
export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
|
|
4
4
|
provider: any;
|
|
5
5
|
sessionId: any;
|
package/dist/resume/kimi.js
CHANGED
|
@@ -15,20 +15,20 @@ export async function findSessionPath(sessionId, options = {}) {
|
|
|
15
15
|
if (!normalizedSessionId) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
return findKimiSessionDirectory(sessionsDir, normalizedSessionId);
|
|
18
|
+
const target = await findKimiSessionTarget(normalizedSessionId, options);
|
|
19
|
+
return target?.sessionPath || null;
|
|
21
20
|
}
|
|
22
21
|
export async function resolveResumeContext(sessionId, options = {}) {
|
|
23
22
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
24
23
|
if (!normalizedSessionId) {
|
|
25
24
|
throw new Error("--resume requires a session id");
|
|
26
25
|
}
|
|
27
|
-
const
|
|
28
|
-
if (!sessionPath) {
|
|
26
|
+
const target = await findKimiSessionTarget(normalizedSessionId, options);
|
|
27
|
+
if (!target?.sessionPath) {
|
|
29
28
|
throw new Error(`Invalid --resume session id for kimi: ${normalizedSessionId}`);
|
|
30
29
|
}
|
|
31
|
-
const
|
|
30
|
+
const sessionPath = target.sessionPath;
|
|
31
|
+
const cwdFromSession = target.cwd || (await resolveLegacyKimiResumeCwd(sessionPath, normalizedSessionId, options));
|
|
32
32
|
if (!cwdFromSession) {
|
|
33
33
|
throw new Error(`Could not resolve workspace for Kimi session ${normalizedSessionId}. Re-run from the original workspace or resume a session previously started by conductor fire.`);
|
|
34
34
|
}
|
|
@@ -46,7 +46,7 @@ export async function resolveResumeContext(sessionId, options = {}) {
|
|
|
46
46
|
export function md5Hex(value) {
|
|
47
47
|
return crypto.createHash("md5").update(String(value ?? "")).digest("hex");
|
|
48
48
|
}
|
|
49
|
-
async function
|
|
49
|
+
async function resolveLegacyKimiResumeCwd(sessionPath, _sessionId, options = {}) {
|
|
50
50
|
const sessionDirectory = typeof sessionPath === "string" ? sessionPath.trim() : "";
|
|
51
51
|
if (!sessionDirectory) {
|
|
52
52
|
return null;
|
|
@@ -68,6 +68,111 @@ async function resolveKimiResumeCwd(sessionPath, _sessionId, options = {}) {
|
|
|
68
68
|
}
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
|
+
async function findKimiSessionTarget(sessionId, options = {}) {
|
|
72
|
+
const homeDir = resolveHomeDir(options);
|
|
73
|
+
const kimiCodeHome = resolveKimiCodeHome(homeDir, options);
|
|
74
|
+
const kimiCodeTarget = await findKimiCodeSession(kimiCodeHome, sessionId);
|
|
75
|
+
if (kimiCodeTarget) {
|
|
76
|
+
return kimiCodeTarget;
|
|
77
|
+
}
|
|
78
|
+
const legacySessionsDir = options.kimiSessionsDir || path.join(homeDir, ".kimi", "sessions");
|
|
79
|
+
const legacySessionPath = await findKimiSessionDirectory(legacySessionsDir, sessionId);
|
|
80
|
+
return legacySessionPath
|
|
81
|
+
? {
|
|
82
|
+
sessionPath: legacySessionPath,
|
|
83
|
+
cwd: null,
|
|
84
|
+
}
|
|
85
|
+
: null;
|
|
86
|
+
}
|
|
87
|
+
function resolveKimiCodeHome(homeDir, options = {}) {
|
|
88
|
+
const configured = options.kimiCodeHome ||
|
|
89
|
+
options.env?.KIMI_CODE_HOME ||
|
|
90
|
+
(!options.homeDir ? process.env.KIMI_CODE_HOME : "");
|
|
91
|
+
return typeof configured === "string" && configured.trim()
|
|
92
|
+
? configured.trim()
|
|
93
|
+
: path.join(homeDir, ".kimi-code");
|
|
94
|
+
}
|
|
95
|
+
async function findKimiCodeSession(kimiCodeHome, sessionId) {
|
|
96
|
+
const sessionsDir = path.join(kimiCodeHome, "sessions");
|
|
97
|
+
const indexed = await findIndexedKimiCodeSession(kimiCodeHome, sessionsDir, sessionId);
|
|
98
|
+
if (indexed) {
|
|
99
|
+
return indexed;
|
|
100
|
+
}
|
|
101
|
+
const sessionPath = await findKimiSessionDirectory(sessionsDir, sessionId);
|
|
102
|
+
if (!sessionPath) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
sessionPath,
|
|
107
|
+
cwd: await readKimiCodeSessionWorkDir(sessionPath),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async function findIndexedKimiCodeSession(kimiCodeHome, sessionsDir, sessionId) {
|
|
111
|
+
let raw = "";
|
|
112
|
+
try {
|
|
113
|
+
raw = await fsp.readFile(path.join(kimiCodeHome, "session_index.jsonl"), "utf8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
let matched = null;
|
|
119
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
if (!trimmed) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let record = null;
|
|
125
|
+
try {
|
|
126
|
+
record = JSON.parse(trimmed);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (record?.sessionId !== sessionId) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (record?.deleted === true) {
|
|
135
|
+
matched = null;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const sessionDir = typeof record?.sessionDir === "string" ? record.sessionDir.trim() : "";
|
|
139
|
+
if (!sessionDir || !path.isAbsolute(sessionDir)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const relativePath = path.relative(path.resolve(sessionsDir), path.resolve(sessionDir));
|
|
143
|
+
if (!relativePath ||
|
|
144
|
+
relativePath === ".." ||
|
|
145
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
146
|
+
path.isAbsolute(relativePath) ||
|
|
147
|
+
path.basename(sessionDir) !== sessionId) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
matched = {
|
|
151
|
+
sessionPath: sessionDir,
|
|
152
|
+
cwd: typeof record?.workDir === "string" ? record.workDir.trim() : "",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (!matched || !(await pathExists(matched.sessionPath, "directory"))) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
sessionPath: matched.sessionPath,
|
|
160
|
+
cwd: await readKimiCodeSessionWorkDir(matched.sessionPath, matched.cwd),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function readKimiCodeSessionWorkDir(sessionPath, fallback = "") {
|
|
164
|
+
try {
|
|
165
|
+
const raw = await fsp.readFile(path.join(sessionPath, "state.json"), "utf8");
|
|
166
|
+
const state = JSON.parse(raw);
|
|
167
|
+
if (typeof state?.workDir === "string" && state.workDir.trim()) {
|
|
168
|
+
return state.workDir.trim();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Fall back to the session index, which also records the working directory.
|
|
173
|
+
}
|
|
174
|
+
return typeof fallback === "string" && fallback.trim() ? fallback.trim() : null;
|
|
175
|
+
}
|
|
71
176
|
async function findKimiSessionDirectory(rootDir, sessionId) {
|
|
72
177
|
let hashDirs = [];
|
|
73
178
|
try {
|
package/dist/session-factory.js
CHANGED
|
@@ -5,6 +5,7 @@ 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
7
|
import { KimiCliSession } from "./providers/kimi-cli-session.js";
|
|
8
|
+
import { resolveKimiCliMode } from "./providers/kimi-cli-mode.js";
|
|
8
9
|
import { KimiPrintSession } from "./providers/kimi-print-session.js";
|
|
9
10
|
import { OpencodeSdkSession } from "./providers/opencode-sdk-session.js";
|
|
10
11
|
import { getExternalProviderDescriptor, resolveExternalBackend, } from "./external-provider-registry.js";
|
|
@@ -36,9 +37,13 @@ const SESSION_FACTORIES_BY_BACKEND = new Map([
|
|
|
36
37
|
["copilot", (backend, options) => new CopilotSdkSession(backend, options)],
|
|
37
38
|
[
|
|
38
39
|
"kimi",
|
|
39
|
-
(backend, options) =>
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
async (backend, options) => {
|
|
41
|
+
const kimiCliMode = await resolveKimiCliMode(options);
|
|
42
|
+
const resolvedOptions = { ...options, kimiCliMode };
|
|
43
|
+
return hasStructuredOutputPreference(options) || kimiCliMode === "prompt"
|
|
44
|
+
? new KimiPrintSession(backend, resolvedOptions)
|
|
45
|
+
: new KimiCliSession(backend, resolvedOptions);
|
|
46
|
+
},
|
|
42
47
|
],
|
|
43
48
|
["opencode", (backend, options) => new OpencodeSdkSession(backend, options)],
|
|
44
49
|
["chat-web", (backend, options) => new ChatWebSession(backend, options)],
|
|
@@ -109,7 +114,7 @@ export async function createLocalAiSession(backend, options = {}) {
|
|
|
109
114
|
const normalized = await assertSupportedBackend(backend, options);
|
|
110
115
|
const factory = SESSION_FACTORIES_BY_BACKEND.get(normalized);
|
|
111
116
|
if (factory) {
|
|
112
|
-
return factory(normalized, options);
|
|
117
|
+
return await factory(normalized, options);
|
|
113
118
|
}
|
|
114
119
|
const descriptor = await getExternalProviderDescriptor(normalized, options);
|
|
115
120
|
if (!descriptor) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"zod": "^4.1.5"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@love-moon/chat-web": "^0.7.
|
|
35
|
+
"@love-moon/chat-web": "^0.7.4"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.10.2",
|
|
39
39
|
"tsx": "^4.20.6",
|
|
40
40
|
"typescript": "^5.6.3"
|
|
41
41
|
},
|
|
42
|
-
"gitCommitId": "
|
|
42
|
+
"gitCommitId": "b9a27b4"
|
|
43
43
|
}
|