@botcord/daemon 0.2.50 → 0.2.51
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/dist/gateway/channels/botcord.js +11 -0
- package/dist/gateway/runtimes/kimi.d.ts +1 -1
- package/dist/gateway/runtimes/kimi.js +104 -3
- package/package.json +1 -1
- package/src/gateway/__tests__/botcord-channel.test.ts +86 -0
- package/src/gateway/__tests__/kimi-adapter.test.ts +85 -1
- package/src/gateway/channels/botcord.ts +13 -0
- package/src/gateway/runtimes/kimi.ts +109 -2
|
@@ -768,6 +768,8 @@ function normalizeBlockForHub(block, seq) {
|
|
|
768
768
|
if (kind === "assistant_text") {
|
|
769
769
|
// Claude Code: {type:"assistant", message:{content:[{type:"text",text}]}}
|
|
770
770
|
// Codex: {type:"item.completed", item:{type:"agent_message", text}}
|
|
771
|
+
// DeepSeek: {event:"message.delta", payload:{content}} or
|
|
772
|
+
// {event:"item.delta", payload:{payload:{kind:"agent_message", delta}}}
|
|
771
773
|
let text = "";
|
|
772
774
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
773
775
|
for (const c of contents) {
|
|
@@ -776,6 +778,15 @@ function normalizeBlockForHub(block, seq) {
|
|
|
776
778
|
}
|
|
777
779
|
if (!text && typeof raw?.item?.text === "string")
|
|
778
780
|
text = raw.item.text;
|
|
781
|
+
if (!text && raw?.event === "message.delta" && typeof raw?.payload?.content === "string") {
|
|
782
|
+
text = raw.payload.content;
|
|
783
|
+
}
|
|
784
|
+
if (!text &&
|
|
785
|
+
raw?.event === "item.delta" &&
|
|
786
|
+
raw?.payload?.payload?.kind === "agent_message" &&
|
|
787
|
+
typeof raw?.payload?.payload?.delta === "string") {
|
|
788
|
+
text = raw.payload.payload.delta;
|
|
789
|
+
}
|
|
779
790
|
return { kind: "assistant", seq, payload: { text } };
|
|
780
791
|
}
|
|
781
792
|
if (kind === "tool_use") {
|
|
@@ -8,7 +8,7 @@ export declare function probeKimi(deps?: ProbeDeps): RuntimeProbeResult;
|
|
|
8
8
|
/**
|
|
9
9
|
* Kimi CLI adapter — spawns:
|
|
10
10
|
*
|
|
11
|
-
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --prompt <text>
|
|
11
|
+
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --afk --prompt <text>
|
|
12
12
|
*
|
|
13
13
|
* `--session <sid>` resumes an existing session or creates a new session with
|
|
14
14
|
* that id, so the adapter generates a UUID on first turn and persists it for
|
|
@@ -17,6 +17,108 @@ function isValidKimiSessionId(sessionId) {
|
|
|
17
17
|
function invalidKimiSessionIdError() {
|
|
18
18
|
return "kimi-cli: invalid sessionId (expected non-control text not starting with '-')";
|
|
19
19
|
}
|
|
20
|
+
const KIMI_EXTRA_FLAGS_WITH_VALUE = new Set([
|
|
21
|
+
"--add-dir",
|
|
22
|
+
"--agent",
|
|
23
|
+
"--agent-file",
|
|
24
|
+
"--config",
|
|
25
|
+
"--config-file",
|
|
26
|
+
"--max-ralph-iterations",
|
|
27
|
+
"--max-retries-per-step",
|
|
28
|
+
"--max-steps-per-turn",
|
|
29
|
+
"--mcp-config",
|
|
30
|
+
"--mcp-config-file",
|
|
31
|
+
"--model",
|
|
32
|
+
"--skills-dir",
|
|
33
|
+
"-m",
|
|
34
|
+
]);
|
|
35
|
+
const KIMI_EXTRA_BOOLEAN_FLAGS = new Set([
|
|
36
|
+
"--afk",
|
|
37
|
+
"--auto-approve",
|
|
38
|
+
"--debug",
|
|
39
|
+
"--no-thinking",
|
|
40
|
+
"--plan",
|
|
41
|
+
"--thinking",
|
|
42
|
+
"--verbose",
|
|
43
|
+
"--yes",
|
|
44
|
+
"--yolo",
|
|
45
|
+
"-y",
|
|
46
|
+
]);
|
|
47
|
+
// Flags owned by the adapter because BotCord depends on Kimi's non-interactive
|
|
48
|
+
// stream-json contract, cwd isolation, prompt placement, and session routing.
|
|
49
|
+
const KIMI_ADAPTER_OWNED_FLAGS = new Set([
|
|
50
|
+
"--acp",
|
|
51
|
+
"--command",
|
|
52
|
+
"--continue",
|
|
53
|
+
"--final-message-only",
|
|
54
|
+
"--help",
|
|
55
|
+
"--input-format",
|
|
56
|
+
"--output-format",
|
|
57
|
+
"--print",
|
|
58
|
+
"--prompt",
|
|
59
|
+
"--quiet",
|
|
60
|
+
"--resume",
|
|
61
|
+
"--session",
|
|
62
|
+
"--version",
|
|
63
|
+
"--wire",
|
|
64
|
+
"--work-dir",
|
|
65
|
+
"-C",
|
|
66
|
+
"-S",
|
|
67
|
+
"-V",
|
|
68
|
+
"-c",
|
|
69
|
+
"-h",
|
|
70
|
+
"-p",
|
|
71
|
+
"-r",
|
|
72
|
+
"-w",
|
|
73
|
+
]);
|
|
74
|
+
function flagName(arg) {
|
|
75
|
+
if (!arg.startsWith("-"))
|
|
76
|
+
return arg;
|
|
77
|
+
const eq = arg.indexOf("=");
|
|
78
|
+
return eq === -1 ? arg : arg.slice(0, eq);
|
|
79
|
+
}
|
|
80
|
+
function nextValue(args, index) {
|
|
81
|
+
const next = args[index + 1];
|
|
82
|
+
if (typeof next !== "string")
|
|
83
|
+
return undefined;
|
|
84
|
+
if (!next.startsWith("-"))
|
|
85
|
+
return next;
|
|
86
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
87
|
+
}
|
|
88
|
+
function sanitizeKimiExtraArgs(extraArgs) {
|
|
89
|
+
if (!extraArgs?.length)
|
|
90
|
+
return [];
|
|
91
|
+
const out = [];
|
|
92
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
93
|
+
const arg = extraArgs[i];
|
|
94
|
+
const name = flagName(arg);
|
|
95
|
+
if (KIMI_ADAPTER_OWNED_FLAGS.has(name)) {
|
|
96
|
+
if (!arg.includes("=") && nextValue(extraArgs, i) !== undefined)
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (KIMI_EXTRA_FLAGS_WITH_VALUE.has(name)) {
|
|
101
|
+
if (arg.includes("=")) {
|
|
102
|
+
out.push(arg);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const value = nextValue(extraArgs, i);
|
|
106
|
+
if (value !== undefined) {
|
|
107
|
+
out.push(arg, value);
|
|
108
|
+
i += 1;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (KIMI_EXTRA_BOOLEAN_FLAGS.has(name)) {
|
|
113
|
+
out.push(arg);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (arg.startsWith("-") && !arg.includes("=") && nextValue(extraArgs, i) !== undefined) {
|
|
117
|
+
i += 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
20
122
|
/** Resolve the Kimi CLI executable on PATH. */
|
|
21
123
|
export function resolveKimiCommand(deps = {}) {
|
|
22
124
|
return resolveCommandOnPath("kimi", deps);
|
|
@@ -35,7 +137,7 @@ export function probeKimi(deps = {}) {
|
|
|
35
137
|
/**
|
|
36
138
|
* Kimi CLI adapter — spawns:
|
|
37
139
|
*
|
|
38
|
-
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --prompt <text>
|
|
140
|
+
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --afk --prompt <text>
|
|
39
141
|
*
|
|
40
142
|
* `--session <sid>` resumes an existing session or creates a new session with
|
|
41
143
|
* that id, so the adapter generates a UUID on first turn and persists it for
|
|
@@ -83,8 +185,7 @@ export class KimiAdapter extends NdjsonStreamAdapter {
|
|
|
83
185
|
sessionId,
|
|
84
186
|
"--afk",
|
|
85
187
|
];
|
|
86
|
-
|
|
87
|
-
args.push(...opts.extraArgs);
|
|
188
|
+
args.push(...sanitizeKimiExtraArgs(opts.extraArgs));
|
|
88
189
|
args.push("--prompt", promptWithSystemContext(opts.text, opts.systemContext));
|
|
89
190
|
return args;
|
|
90
191
|
}
|
package/package.json
CHANGED
|
@@ -618,6 +618,92 @@ describe("createBotCordChannel — streamBlock()", () => {
|
|
|
618
618
|
}
|
|
619
619
|
});
|
|
620
620
|
|
|
621
|
+
it("normalizes DeepSeek message.delta assistant text", async () => {
|
|
622
|
+
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
623
|
+
const realFetch = globalThis.fetch;
|
|
624
|
+
globalThis.fetch = fetchSpy as unknown as typeof fetch;
|
|
625
|
+
try {
|
|
626
|
+
const client = makeClient({
|
|
627
|
+
getHubUrl: vi.fn().mockReturnValue("https://hub.example.com"),
|
|
628
|
+
});
|
|
629
|
+
const channel = createBotCordChannel({
|
|
630
|
+
id: "botcord-main",
|
|
631
|
+
accountId: "ag_self",
|
|
632
|
+
agentId: "ag_self",
|
|
633
|
+
client,
|
|
634
|
+
hubBaseUrl: "https://hub.example.com",
|
|
635
|
+
});
|
|
636
|
+
await channel.streamBlock!({
|
|
637
|
+
traceId: "m_trace",
|
|
638
|
+
accountId: "ag_self",
|
|
639
|
+
conversationId: "rm_oc_1",
|
|
640
|
+
block: {
|
|
641
|
+
kind: "assistant_text",
|
|
642
|
+
seq: 4,
|
|
643
|
+
raw: {
|
|
644
|
+
event: "message.delta",
|
|
645
|
+
payload: { thread_id: "thr_1", turn_id: "turn_1", content: "hello " },
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
log: silentLog,
|
|
649
|
+
});
|
|
650
|
+
const [, init] = fetchSpy.mock.calls[0];
|
|
651
|
+
const body = JSON.parse(init.body as string);
|
|
652
|
+
expect(body.block).toEqual({
|
|
653
|
+
kind: "assistant",
|
|
654
|
+
seq: 4,
|
|
655
|
+
payload: { text: "hello " },
|
|
656
|
+
});
|
|
657
|
+
} finally {
|
|
658
|
+
globalThis.fetch = realFetch;
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("normalizes DeepSeek item.delta assistant text", async () => {
|
|
663
|
+
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
664
|
+
const realFetch = globalThis.fetch;
|
|
665
|
+
globalThis.fetch = fetchSpy as unknown as typeof fetch;
|
|
666
|
+
try {
|
|
667
|
+
const client = makeClient({
|
|
668
|
+
getHubUrl: vi.fn().mockReturnValue("https://hub.example.com"),
|
|
669
|
+
});
|
|
670
|
+
const channel = createBotCordChannel({
|
|
671
|
+
id: "botcord-main",
|
|
672
|
+
accountId: "ag_self",
|
|
673
|
+
agentId: "ag_self",
|
|
674
|
+
client,
|
|
675
|
+
hubBaseUrl: "https://hub.example.com",
|
|
676
|
+
});
|
|
677
|
+
await channel.streamBlock!({
|
|
678
|
+
traceId: "m_trace",
|
|
679
|
+
accountId: "ag_self",
|
|
680
|
+
conversationId: "rm_oc_1",
|
|
681
|
+
block: {
|
|
682
|
+
kind: "assistant_text",
|
|
683
|
+
seq: 5,
|
|
684
|
+
raw: {
|
|
685
|
+
event: "item.delta",
|
|
686
|
+
payload: {
|
|
687
|
+
thread_id: "thr_1",
|
|
688
|
+
turn_id: "turn_1",
|
|
689
|
+
payload: { kind: "agent_message", delta: "deepseek" },
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
log: silentLog,
|
|
694
|
+
});
|
|
695
|
+
const [, init] = fetchSpy.mock.calls[0];
|
|
696
|
+
const body = JSON.parse(init.body as string);
|
|
697
|
+
expect(body.block).toEqual({
|
|
698
|
+
kind: "assistant",
|
|
699
|
+
seq: 5,
|
|
700
|
+
payload: { text: "deepseek" },
|
|
701
|
+
});
|
|
702
|
+
} finally {
|
|
703
|
+
globalThis.fetch = realFetch;
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
621
707
|
it("normalizes a thinking block with phase/label/source payload", async () => {
|
|
622
708
|
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
623
709
|
const realFetch = globalThis.fetch;
|
|
@@ -18,7 +18,7 @@ afterAll(() => {
|
|
|
18
18
|
rmSync(tmpRoot, { recursive: true, force: true });
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
function runAdapter(script: string, sessionId: string | null = null) {
|
|
21
|
+
function runAdapter(script: string, sessionId: string | null = null, extraArgs?: string[]) {
|
|
22
22
|
const adapter = new KimiAdapter({ binary: script });
|
|
23
23
|
const ctrl = new AbortController();
|
|
24
24
|
return adapter.run({
|
|
@@ -28,6 +28,7 @@ function runAdapter(script: string, sessionId: string | null = null) {
|
|
|
28
28
|
cwd: tmpRoot,
|
|
29
29
|
signal: ctrl.signal,
|
|
30
30
|
trustLevel: "owner",
|
|
31
|
+
extraArgs,
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -69,6 +70,89 @@ process.stdout.write(JSON.stringify({role:"assistant", content:JSON.stringify(ar
|
|
|
69
70
|
expect(argv).toContain("--afk");
|
|
70
71
|
});
|
|
71
72
|
|
|
73
|
+
it("drops non-Kimi inherited extraArgs and their values", async () => {
|
|
74
|
+
const script = makeScript(
|
|
75
|
+
"filter-foreign-argv.js",
|
|
76
|
+
`
|
|
77
|
+
const argv = process.argv.slice(2);
|
|
78
|
+
process.stdout.write(JSON.stringify({role:"assistant", content:JSON.stringify(argv)}) + "\\n");
|
|
79
|
+
`,
|
|
80
|
+
);
|
|
81
|
+
const res = await runAdapter(script, "sid-123", [
|
|
82
|
+
"--permission-mode",
|
|
83
|
+
"bypassPermissions",
|
|
84
|
+
"--model",
|
|
85
|
+
"kimi-k2",
|
|
86
|
+
]);
|
|
87
|
+
const argv = JSON.parse(res.text) as string[];
|
|
88
|
+
expect(argv).not.toContain("--permission-mode");
|
|
89
|
+
expect(argv).not.toContain("bypassPermissions");
|
|
90
|
+
expect(argv).toContain("--model");
|
|
91
|
+
expect(argv[argv.indexOf("--model") + 1]).toBe("kimi-k2");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("preserves Kimi value flags with negative numeric values", async () => {
|
|
95
|
+
const script = makeScript(
|
|
96
|
+
"negative-value-argv.js",
|
|
97
|
+
`
|
|
98
|
+
const argv = process.argv.slice(2);
|
|
99
|
+
process.stdout.write(JSON.stringify({role:"assistant", content:JSON.stringify(argv)}) + "\\n");
|
|
100
|
+
`,
|
|
101
|
+
);
|
|
102
|
+
const res = await runAdapter(script, "sid-123", [
|
|
103
|
+
"--max-ralph-iterations",
|
|
104
|
+
"-1",
|
|
105
|
+
"--max-steps-per-turn=3",
|
|
106
|
+
]);
|
|
107
|
+
const argv = JSON.parse(res.text) as string[];
|
|
108
|
+
expect(argv).toContain("--max-ralph-iterations");
|
|
109
|
+
expect(argv[argv.indexOf("--max-ralph-iterations") + 1]).toBe("-1");
|
|
110
|
+
expect(argv).toContain("--max-steps-per-turn=3");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("drops incomplete Kimi value flags instead of passing invalid argv", async () => {
|
|
114
|
+
const script = makeScript(
|
|
115
|
+
"incomplete-value-argv.js",
|
|
116
|
+
`
|
|
117
|
+
const argv = process.argv.slice(2);
|
|
118
|
+
process.stdout.write(JSON.stringify({role:"assistant", content:JSON.stringify(argv)}) + "\\n");
|
|
119
|
+
`,
|
|
120
|
+
);
|
|
121
|
+
const res = await runAdapter(script, "sid-123", ["--model", "--plan"]);
|
|
122
|
+
const argv = JSON.parse(res.text) as string[];
|
|
123
|
+
expect(argv).not.toContain("--model");
|
|
124
|
+
expect(argv).toContain("--plan");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("does not let extraArgs override adapter-owned stream/session/prompt flags", async () => {
|
|
128
|
+
const script = makeScript(
|
|
129
|
+
"filter-owned-argv.js",
|
|
130
|
+
`
|
|
131
|
+
const argv = process.argv.slice(2);
|
|
132
|
+
process.stdout.write(JSON.stringify({role:"assistant", content:JSON.stringify(argv)}) + "\\n");
|
|
133
|
+
`,
|
|
134
|
+
);
|
|
135
|
+
const res = await runAdapter(script, "real-session", [
|
|
136
|
+
"--output-format",
|
|
137
|
+
"text",
|
|
138
|
+
"--session",
|
|
139
|
+
"evil-session",
|
|
140
|
+
"--prompt",
|
|
141
|
+
"evil prompt",
|
|
142
|
+
"--plan",
|
|
143
|
+
]);
|
|
144
|
+
const argv = JSON.parse(res.text) as string[];
|
|
145
|
+
expect(argv.filter((a) => a === "--output-format")).toHaveLength(1);
|
|
146
|
+
expect(argv[argv.indexOf("--output-format") + 1]).toBe("stream-json");
|
|
147
|
+
expect(argv.filter((a) => a === "--session")).toHaveLength(1);
|
|
148
|
+
expect(argv[argv.indexOf("--session") + 1]).toBe("real-session");
|
|
149
|
+
expect(argv.filter((a) => a === "--prompt")).toHaveLength(1);
|
|
150
|
+
expect(argv[argv.indexOf("--prompt") + 1]).toBe("hi");
|
|
151
|
+
expect(argv).toContain("--plan");
|
|
152
|
+
expect(argv).not.toContain("evil-session");
|
|
153
|
+
expect(argv).not.toContain("evil prompt");
|
|
154
|
+
});
|
|
155
|
+
|
|
72
156
|
it("rejects session ids that could be parsed as flags", async () => {
|
|
73
157
|
const script = makeScript(
|
|
74
158
|
"should-not-spawn.js",
|
|
@@ -907,12 +907,25 @@ function normalizeBlockForHub(
|
|
|
907
907
|
if (kind === "assistant_text") {
|
|
908
908
|
// Claude Code: {type:"assistant", message:{content:[{type:"text",text}]}}
|
|
909
909
|
// Codex: {type:"item.completed", item:{type:"agent_message", text}}
|
|
910
|
+
// DeepSeek: {event:"message.delta", payload:{content}} or
|
|
911
|
+
// {event:"item.delta", payload:{payload:{kind:"agent_message", delta}}}
|
|
910
912
|
let text = "";
|
|
911
913
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
912
914
|
for (const c of contents) {
|
|
913
915
|
if (c?.type === "text" && typeof c.text === "string") text += c.text;
|
|
914
916
|
}
|
|
915
917
|
if (!text && typeof raw?.item?.text === "string") text = raw.item.text;
|
|
918
|
+
if (!text && raw?.event === "message.delta" && typeof raw?.payload?.content === "string") {
|
|
919
|
+
text = raw.payload.content;
|
|
920
|
+
}
|
|
921
|
+
if (
|
|
922
|
+
!text &&
|
|
923
|
+
raw?.event === "item.delta" &&
|
|
924
|
+
raw?.payload?.payload?.kind === "agent_message" &&
|
|
925
|
+
typeof raw?.payload?.payload?.delta === "string"
|
|
926
|
+
) {
|
|
927
|
+
text = raw.payload.payload.delta;
|
|
928
|
+
}
|
|
916
929
|
return { kind: "assistant", seq, payload: { text } };
|
|
917
930
|
}
|
|
918
931
|
|
|
@@ -22,6 +22,113 @@ function invalidKimiSessionIdError(): string {
|
|
|
22
22
|
return "kimi-cli: invalid sessionId (expected non-control text not starting with '-')";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const KIMI_EXTRA_FLAGS_WITH_VALUE = new Set([
|
|
26
|
+
"--add-dir",
|
|
27
|
+
"--agent",
|
|
28
|
+
"--agent-file",
|
|
29
|
+
"--config",
|
|
30
|
+
"--config-file",
|
|
31
|
+
"--max-ralph-iterations",
|
|
32
|
+
"--max-retries-per-step",
|
|
33
|
+
"--max-steps-per-turn",
|
|
34
|
+
"--mcp-config",
|
|
35
|
+
"--mcp-config-file",
|
|
36
|
+
"--model",
|
|
37
|
+
"--skills-dir",
|
|
38
|
+
"-m",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const KIMI_EXTRA_BOOLEAN_FLAGS = new Set([
|
|
42
|
+
"--afk",
|
|
43
|
+
"--auto-approve",
|
|
44
|
+
"--debug",
|
|
45
|
+
"--no-thinking",
|
|
46
|
+
"--plan",
|
|
47
|
+
"--thinking",
|
|
48
|
+
"--verbose",
|
|
49
|
+
"--yes",
|
|
50
|
+
"--yolo",
|
|
51
|
+
"-y",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
// Flags owned by the adapter because BotCord depends on Kimi's non-interactive
|
|
55
|
+
// stream-json contract, cwd isolation, prompt placement, and session routing.
|
|
56
|
+
const KIMI_ADAPTER_OWNED_FLAGS = new Set([
|
|
57
|
+
"--acp",
|
|
58
|
+
"--command",
|
|
59
|
+
"--continue",
|
|
60
|
+
"--final-message-only",
|
|
61
|
+
"--help",
|
|
62
|
+
"--input-format",
|
|
63
|
+
"--output-format",
|
|
64
|
+
"--print",
|
|
65
|
+
"--prompt",
|
|
66
|
+
"--quiet",
|
|
67
|
+
"--resume",
|
|
68
|
+
"--session",
|
|
69
|
+
"--version",
|
|
70
|
+
"--wire",
|
|
71
|
+
"--work-dir",
|
|
72
|
+
"-C",
|
|
73
|
+
"-S",
|
|
74
|
+
"-V",
|
|
75
|
+
"-c",
|
|
76
|
+
"-h",
|
|
77
|
+
"-p",
|
|
78
|
+
"-r",
|
|
79
|
+
"-w",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function flagName(arg: string): string {
|
|
83
|
+
if (!arg.startsWith("-")) return arg;
|
|
84
|
+
const eq = arg.indexOf("=");
|
|
85
|
+
return eq === -1 ? arg : arg.slice(0, eq);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nextValue(args: string[], index: number): string | undefined {
|
|
89
|
+
const next = args[index + 1];
|
|
90
|
+
if (typeof next !== "string") return undefined;
|
|
91
|
+
if (!next.startsWith("-")) return next;
|
|
92
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sanitizeKimiExtraArgs(extraArgs: string[] | undefined): string[] {
|
|
96
|
+
if (!extraArgs?.length) return [];
|
|
97
|
+
const out: string[] = [];
|
|
98
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
99
|
+
const arg = extraArgs[i];
|
|
100
|
+
const name = flagName(arg);
|
|
101
|
+
|
|
102
|
+
if (KIMI_ADAPTER_OWNED_FLAGS.has(name)) {
|
|
103
|
+
if (!arg.includes("=") && nextValue(extraArgs, i) !== undefined) i += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (KIMI_EXTRA_FLAGS_WITH_VALUE.has(name)) {
|
|
108
|
+
if (arg.includes("=")) {
|
|
109
|
+
out.push(arg);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const value = nextValue(extraArgs, i);
|
|
113
|
+
if (value !== undefined) {
|
|
114
|
+
out.push(arg, value);
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (KIMI_EXTRA_BOOLEAN_FLAGS.has(name)) {
|
|
121
|
+
out.push(arg);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (arg.startsWith("-") && !arg.includes("=") && nextValue(extraArgs, i) !== undefined) {
|
|
126
|
+
i += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
25
132
|
/** Resolve the Kimi CLI executable on PATH. */
|
|
26
133
|
export function resolveKimiCommand(deps: ProbeDeps = {}): string | null {
|
|
27
134
|
return resolveCommandOnPath("kimi", deps);
|
|
@@ -41,7 +148,7 @@ export function probeKimi(deps: ProbeDeps = {}): RuntimeProbeResult {
|
|
|
41
148
|
/**
|
|
42
149
|
* Kimi CLI adapter — spawns:
|
|
43
150
|
*
|
|
44
|
-
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --prompt <text>
|
|
151
|
+
* kimi --work-dir <cwd> --print --output-format stream-json --session <sid> --afk --prompt <text>
|
|
45
152
|
*
|
|
46
153
|
* `--session <sid>` resumes an existing session or creates a new session with
|
|
47
154
|
* that id, so the adapter generates a UUID on first turn and persists it for
|
|
@@ -93,7 +200,7 @@ export class KimiAdapter extends NdjsonStreamAdapter {
|
|
|
93
200
|
sessionId,
|
|
94
201
|
"--afk",
|
|
95
202
|
];
|
|
96
|
-
|
|
203
|
+
args.push(...sanitizeKimiExtraArgs(opts.extraArgs));
|
|
97
204
|
args.push("--prompt", promptWithSystemContext(opts.text, opts.systemContext));
|
|
98
205
|
return args;
|
|
99
206
|
}
|