@cleocode/adapters 2026.4.67 → 2026.4.69
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/index.js +50 -32
- package/dist/index.js.map +4 -4
- package/dist/providers/codex/index.d.ts +1 -0
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/spawn.d.ts +82 -0
- package/dist/providers/codex/spawn.d.ts.map +1 -0
- package/dist/providers/gemini-cli/index.d.ts +1 -0
- package/dist/providers/gemini-cli/index.d.ts.map +1 -1
- package/dist/providers/gemini-cli/spawn.d.ts +83 -0
- package/dist/providers/gemini-cli/spawn.d.ts.map +1 -0
- package/dist/providers/kimi/index.d.ts +1 -0
- package/dist/providers/kimi/index.d.ts.map +1 -1
- package/dist/providers/kimi/spawn.d.ts +91 -0
- package/dist/providers/kimi/spawn.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/providers/codex/index.ts +1 -0
- package/src/providers/codex/spawn.ts +224 -0
- package/src/providers/gemini-cli/index.ts +1 -0
- package/src/providers/gemini-cli/spawn.ts +220 -0
- package/src/providers/kimi/index.ts +1 -0
- package/src/providers/kimi/spawn.ts +274 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Spawn Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements `AdapterSpawnProvider` for the Google Gemini CLI (`gemini` binary).
|
|
5
|
+
*
|
|
6
|
+
* The `gemini` binary is the Google Gemini CLI agent, available at:
|
|
7
|
+
* https://github.com/google-gemini/gemini-cli
|
|
8
|
+
*
|
|
9
|
+
* Invocation: `gemini --yolo < <prompt-file>`
|
|
10
|
+
*
|
|
11
|
+
* The provider pipes the prompt via stdin using the `--yolo` flag, which
|
|
12
|
+
* enables non-interactive mode (auto-approve all actions). Processes run
|
|
13
|
+
* detached and are tracked by PID for listing and termination.
|
|
14
|
+
*
|
|
15
|
+
* If the `gemini` binary is not found, `canSpawn()` returns `false` with a
|
|
16
|
+
* graceful error — no crash.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* The Gemini CLI supports a `--model` flag to select the model family and a
|
|
20
|
+
* `--yolo` flag for non-interactive headless execution (equivalent to Claude
|
|
21
|
+
* Code's `--dangerously-skip-permissions`). Prompts are supplied via stdin
|
|
22
|
+
* when run in `--yolo` mode. Install: `npm install -g @google/gemini-cli`
|
|
23
|
+
* or see the GitHub repo above.
|
|
24
|
+
*
|
|
25
|
+
* @task T648
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { exec, spawn as nodeSpawn } from 'node:child_process';
|
|
29
|
+
import { promisify } from 'node:util';
|
|
30
|
+
import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
|
|
31
|
+
import { getErrorMessage } from '@cleocode/contracts';
|
|
32
|
+
|
|
33
|
+
const execAsync = promisify(exec);
|
|
34
|
+
|
|
35
|
+
/** Default Gemini model for subagent spawns. */
|
|
36
|
+
const DEFAULT_MODEL = 'gemini-2.5-pro';
|
|
37
|
+
|
|
38
|
+
/** Internal tracking entry for a spawned process. */
|
|
39
|
+
interface TrackedProcess {
|
|
40
|
+
pid: number;
|
|
41
|
+
taskId: string;
|
|
42
|
+
startTime: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Spawn provider for the Google Gemini CLI.
|
|
47
|
+
*
|
|
48
|
+
* Spawns detached Gemini CLI processes for subagent execution. Each spawn
|
|
49
|
+
* pipes its prompt via stdin, then runs
|
|
50
|
+
* `gemini --yolo --model <model>` as a detached, unref'd child process.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* `canSpawn()` returns `false` (with no crash) when the `gemini` binary is
|
|
54
|
+
* not found in PATH. Install instructions are emitted via `console.warn`
|
|
55
|
+
* once to help operators discover the binary is missing.
|
|
56
|
+
*
|
|
57
|
+
* Processes are tracked by instance ID in an in-memory map and verified
|
|
58
|
+
* via `kill(pid, 0)` liveness checks.
|
|
59
|
+
*
|
|
60
|
+
* @task T648
|
|
61
|
+
*/
|
|
62
|
+
export class GeminiCliSpawnProvider implements AdapterSpawnProvider {
|
|
63
|
+
/** Map of instance IDs to tracked process info. */
|
|
64
|
+
private processMap = new Map<string, TrackedProcess>();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if the Gemini CLI is available in PATH.
|
|
68
|
+
*
|
|
69
|
+
* @returns `true` if `gemini` is found via `which`
|
|
70
|
+
*/
|
|
71
|
+
async canSpawn(): Promise<boolean> {
|
|
72
|
+
try {
|
|
73
|
+
await execAsync('which gemini');
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
console.warn(
|
|
77
|
+
'[GeminiCliSpawnProvider] gemini CLI not found. ' +
|
|
78
|
+
'Install: npm install -g @google/gemini-cli ' +
|
|
79
|
+
'Docs: https://github.com/google-gemini/gemini-cli',
|
|
80
|
+
);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Spawn a subagent via the Gemini CLI.
|
|
87
|
+
*
|
|
88
|
+
* Pipes the enriched prompt to stdin and spawns a detached Gemini
|
|
89
|
+
* process. The process runs independently of the parent.
|
|
90
|
+
*
|
|
91
|
+
* @param context - Spawn context with taskId, prompt, and options
|
|
92
|
+
* @returns Spawn result with instance ID and status
|
|
93
|
+
*/
|
|
94
|
+
async spawn(context: SpawnContext): Promise<SpawnResult> {
|
|
95
|
+
const instanceId = `gemini-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
96
|
+
const startTime = new Date().toISOString();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Enrich prompt with CANT bundle, memory bridge, and mental model.
|
|
100
|
+
// Best-effort: if CANT context is unavailable, the raw prompt is used.
|
|
101
|
+
let enrichedPrompt = context.prompt;
|
|
102
|
+
try {
|
|
103
|
+
const { buildCantEnrichedPrompt } = await import('../../cant-context.js');
|
|
104
|
+
enrichedPrompt = await buildCantEnrichedPrompt({
|
|
105
|
+
projectDir: context.workingDirectory ?? process.cwd(),
|
|
106
|
+
basePrompt: context.prompt,
|
|
107
|
+
agentName: (context.options?.agentName as string) ?? undefined,
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
// CANT enrichment unavailable — use raw prompt
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const model = (context.options?.model as string) ?? DEFAULT_MODEL;
|
|
114
|
+
|
|
115
|
+
// --yolo: non-interactive batch mode (auto-approve all actions)
|
|
116
|
+
// --model: select the Gemini model variant
|
|
117
|
+
// Prompt is supplied via stdin (pipe)
|
|
118
|
+
const args = ['--yolo', '--model', model];
|
|
119
|
+
const spawnOpts: Parameters<typeof nodeSpawn>[2] = {
|
|
120
|
+
detached: true,
|
|
121
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (context.workingDirectory) {
|
|
125
|
+
spawnOpts.cwd = context.workingDirectory;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const child = nodeSpawn('gemini', args, spawnOpts);
|
|
129
|
+
|
|
130
|
+
// Write the prompt to stdin then close so the CLI receives it.
|
|
131
|
+
if (child.stdin) {
|
|
132
|
+
child.stdin.write(enrichedPrompt, 'utf-8');
|
|
133
|
+
child.stdin.end();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
child.unref();
|
|
137
|
+
|
|
138
|
+
if (child.pid) {
|
|
139
|
+
this.processMap.set(instanceId, {
|
|
140
|
+
pid: child.pid,
|
|
141
|
+
taskId: context.taskId,
|
|
142
|
+
startTime,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
child.on('exit', () => {
|
|
147
|
+
this.processMap.delete(instanceId);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
instanceId,
|
|
152
|
+
taskId: context.taskId,
|
|
153
|
+
providerId: 'gemini-cli',
|
|
154
|
+
status: 'running',
|
|
155
|
+
startTime,
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`[GeminiCliSpawnProvider] Failed to spawn: ${getErrorMessage(error)}`);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
instanceId,
|
|
162
|
+
taskId: context.taskId,
|
|
163
|
+
providerId: 'gemini-cli',
|
|
164
|
+
status: 'failed',
|
|
165
|
+
startTime,
|
|
166
|
+
endTime: new Date().toISOString(),
|
|
167
|
+
error: getErrorMessage(error),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* List currently running Gemini CLI subagent processes.
|
|
174
|
+
*
|
|
175
|
+
* Checks each tracked process via kill(pid, 0) to verify it is still alive.
|
|
176
|
+
* Dead processes are automatically cleaned from the tracking map.
|
|
177
|
+
*
|
|
178
|
+
* @returns Array of spawn results for running processes
|
|
179
|
+
*/
|
|
180
|
+
async listRunning(): Promise<SpawnResult[]> {
|
|
181
|
+
const running: SpawnResult[] = [];
|
|
182
|
+
|
|
183
|
+
for (const [instanceId, tracked] of this.processMap.entries()) {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(tracked.pid, 0);
|
|
186
|
+
running.push({
|
|
187
|
+
instanceId,
|
|
188
|
+
taskId: tracked.taskId,
|
|
189
|
+
providerId: 'gemini-cli',
|
|
190
|
+
status: 'running',
|
|
191
|
+
startTime: tracked.startTime,
|
|
192
|
+
});
|
|
193
|
+
} catch {
|
|
194
|
+
this.processMap.delete(instanceId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return running;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Terminate a running spawn by instance ID.
|
|
203
|
+
*
|
|
204
|
+
* Sends SIGTERM to the tracked process. If the process is not found
|
|
205
|
+
* or has already exited, this is a no-op.
|
|
206
|
+
*
|
|
207
|
+
* @param instanceId - ID of the spawn instance to terminate
|
|
208
|
+
*/
|
|
209
|
+
async terminate(instanceId: string): Promise<void> {
|
|
210
|
+
const tracked = this.processMap.get(instanceId);
|
|
211
|
+
if (!tracked) return;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
process.kill(tracked.pid, 'SIGTERM');
|
|
215
|
+
} catch {
|
|
216
|
+
// Process may have already exited
|
|
217
|
+
}
|
|
218
|
+
this.processMap.delete(instanceId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -13,6 +13,7 @@ import { KimiAdapter } from './adapter.js';
|
|
|
13
13
|
export { KimiAdapter } from './adapter.js';
|
|
14
14
|
export { KimiHookProvider } from './hooks.js';
|
|
15
15
|
export { KimiInstallProvider } from './install.js';
|
|
16
|
+
export { KimiSpawnProvider } from './spawn.js';
|
|
16
17
|
|
|
17
18
|
export default KimiAdapter;
|
|
18
19
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi (Moonshot AI) Spawn Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements `AdapterSpawnProvider` for Moonshot AI's Kimi models.
|
|
5
|
+
*
|
|
6
|
+
* There is no widely-distributed standalone Kimi CLI binary. This provider
|
|
7
|
+
* uses the Moonshot AI Chat Completions API directly (REST, no extra SDK
|
|
8
|
+
* dependency) when `MOONSHOT_API_KEY` is present in the environment.
|
|
9
|
+
*
|
|
10
|
+
* API documentation: https://platform.moonshot.cn/docs/api/chat
|
|
11
|
+
* Endpoint: https://api.moonshot.cn/v1/chat/completions
|
|
12
|
+
*
|
|
13
|
+
* `canSpawn()` returns `true` only when:
|
|
14
|
+
* 1. `MOONSHOT_API_KEY` is set in the environment, OR
|
|
15
|
+
* 2. A `kimi` binary is found in PATH (future CLI support)
|
|
16
|
+
*
|
|
17
|
+
* If neither condition holds, `canSpawn()` returns `false` with a clear
|
|
18
|
+
* message — no crash.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Unlike the CLI-based providers (codex, gemini-cli), Kimi spawn runs the
|
|
22
|
+
* API call to completion before returning (`status: 'completed'` or
|
|
23
|
+
* `status: 'failed'`). This mirrors the claude-sdk and openai-sdk providers.
|
|
24
|
+
* The API call uses Node's built-in `fetch` (Node 18+) with no extra
|
|
25
|
+
* dependencies.
|
|
26
|
+
*
|
|
27
|
+
* @task T648
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { exec } from 'node:child_process';
|
|
31
|
+
import { promisify } from 'node:util';
|
|
32
|
+
import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
|
|
33
|
+
import { getErrorMessage } from '@cleocode/contracts';
|
|
34
|
+
|
|
35
|
+
const execAsync = promisify(exec);
|
|
36
|
+
|
|
37
|
+
/** Moonshot AI API base URL. */
|
|
38
|
+
const MOONSHOT_API_BASE = 'https://api.moonshot.cn/v1';
|
|
39
|
+
|
|
40
|
+
/** Default model when none is specified in spawn options. */
|
|
41
|
+
const DEFAULT_MODEL = 'moonshot-v1-8k';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Shape of a Moonshot chat completion response (subset we care about).
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
interface MoonshotChatResponse {
|
|
49
|
+
choices: Array<{
|
|
50
|
+
message: {
|
|
51
|
+
content: string;
|
|
52
|
+
};
|
|
53
|
+
finish_reason: string;
|
|
54
|
+
}>;
|
|
55
|
+
error?: {
|
|
56
|
+
message: string;
|
|
57
|
+
type: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Internal tracking entry for an in-flight API call. */
|
|
62
|
+
interface TrackedRun {
|
|
63
|
+
instanceId: string;
|
|
64
|
+
taskId: string;
|
|
65
|
+
startTime: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the Moonshot API key from the environment.
|
|
70
|
+
*
|
|
71
|
+
* @returns The key string if set, or `null` if absent/empty.
|
|
72
|
+
*/
|
|
73
|
+
function resolveMoonshotApiKey(): string | null {
|
|
74
|
+
const key = process.env.MOONSHOT_API_KEY;
|
|
75
|
+
return key?.trim() ? key : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check whether a `kimi` CLI binary is available in PATH.
|
|
80
|
+
*
|
|
81
|
+
* This is a forward-compatibility hook for any future official Kimi CLI.
|
|
82
|
+
*
|
|
83
|
+
* @returns `true` if `kimi` is found via `which`
|
|
84
|
+
*/
|
|
85
|
+
async function kimiCliBinaryAvailable(): Promise<boolean> {
|
|
86
|
+
try {
|
|
87
|
+
await execAsync('which kimi');
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Spawn provider for Moonshot AI Kimi.
|
|
96
|
+
*
|
|
97
|
+
* Uses the Moonshot AI Chat Completions REST API to run subagent prompts.
|
|
98
|
+
* Each `spawn()` call completes synchronously (awaits the API response) and
|
|
99
|
+
* returns `status: 'completed'` or `status: 'failed'`.
|
|
100
|
+
*
|
|
101
|
+
* In-flight runs are tracked by instance ID so `listRunning()` reflects
|
|
102
|
+
* concurrent spawns correctly.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* `canSpawn()` checks for `MOONSHOT_API_KEY` first (API mode), then falls
|
|
106
|
+
* back to checking for a `kimi` CLI binary (CLI mode, future). If neither is
|
|
107
|
+
* available, `canSpawn()` returns `false` and `spawn()` throws a descriptive
|
|
108
|
+
* error rather than crashing silently.
|
|
109
|
+
*
|
|
110
|
+
* @task T648
|
|
111
|
+
*/
|
|
112
|
+
export class KimiSpawnProvider implements AdapterSpawnProvider {
|
|
113
|
+
/** In-flight run tracking set. */
|
|
114
|
+
private readonly runningInstances = new Map<string, TrackedRun>();
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check whether Kimi spawning is available in the current environment.
|
|
118
|
+
*
|
|
119
|
+
* Returns `true` when either:
|
|
120
|
+
* - `MOONSHOT_API_KEY` is set (API mode), or
|
|
121
|
+
* - A `kimi` binary is found in PATH (CLI mode — future)
|
|
122
|
+
*
|
|
123
|
+
* @returns `true` when any Kimi access method is available
|
|
124
|
+
*/
|
|
125
|
+
async canSpawn(): Promise<boolean> {
|
|
126
|
+
if (resolveMoonshotApiKey()) return true;
|
|
127
|
+
if (await kimiCliBinaryAvailable()) return true;
|
|
128
|
+
|
|
129
|
+
console.warn(
|
|
130
|
+
'[KimiSpawnProvider] No Kimi access method found. ' +
|
|
131
|
+
'Set MOONSHOT_API_KEY to enable API-based spawning. ' +
|
|
132
|
+
'Get a key at: https://platform.moonshot.cn/',
|
|
133
|
+
);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Spawn a subagent via the Moonshot AI Kimi API.
|
|
139
|
+
*
|
|
140
|
+
* Enriches the prompt with CANT context (best-effort), then calls
|
|
141
|
+
* the Moonshot Chat Completions API. The call is awaited to completion.
|
|
142
|
+
*
|
|
143
|
+
* @param context - Spawn context with taskId, prompt, and options
|
|
144
|
+
* @returns Resolved spawn result with `status: 'completed'` or `'failed'`
|
|
145
|
+
*/
|
|
146
|
+
async spawn(context: SpawnContext): Promise<SpawnResult> {
|
|
147
|
+
const instanceId = `kimi-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
148
|
+
const startTime = new Date().toISOString();
|
|
149
|
+
|
|
150
|
+
this.runningInstances.set(instanceId, {
|
|
151
|
+
instanceId,
|
|
152
|
+
taskId: context.taskId,
|
|
153
|
+
startTime,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const apiKey = resolveMoonshotApiKey();
|
|
158
|
+
if (!apiKey) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
'MOONSHOT_API_KEY is not set. ' +
|
|
161
|
+
'Set the environment variable to enable Kimi spawning. ' +
|
|
162
|
+
'Get a key at: https://platform.moonshot.cn/',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Enrich prompt with CANT bundle, memory bridge, and mental model.
|
|
167
|
+
// Best-effort: if CANT context is unavailable, the raw prompt is used.
|
|
168
|
+
let enrichedPrompt = context.prompt;
|
|
169
|
+
try {
|
|
170
|
+
const { buildCantEnrichedPrompt } = await import('../../cant-context.js');
|
|
171
|
+
enrichedPrompt = await buildCantEnrichedPrompt({
|
|
172
|
+
projectDir: context.workingDirectory ?? process.cwd(),
|
|
173
|
+
basePrompt: context.prompt,
|
|
174
|
+
agentName: (context.options?.agentName as string) ?? undefined,
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
// CANT enrichment unavailable — use raw prompt
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const model = (context.options?.model as string) ?? DEFAULT_MODEL;
|
|
181
|
+
|
|
182
|
+
const response = await fetch(`${MOONSHOT_API_BASE}/chat/completions`, {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: {
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
Authorization: `Bearer ${apiKey}`,
|
|
187
|
+
},
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
model,
|
|
190
|
+
messages: [
|
|
191
|
+
{
|
|
192
|
+
role: 'user',
|
|
193
|
+
content: enrichedPrompt,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
stream: false,
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
const bodyText = await response.text();
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Moonshot API error ${response.status} ${response.statusText}: ${bodyText}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const data = (await response.json()) as MoonshotChatResponse;
|
|
208
|
+
|
|
209
|
+
if (data.error) {
|
|
210
|
+
throw new Error(`Moonshot API returned error: ${data.error.message} (${data.error.type})`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const output = data.choices[0]?.message?.content ?? '';
|
|
214
|
+
const endTime = new Date().toISOString();
|
|
215
|
+
this.runningInstances.delete(instanceId);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
instanceId,
|
|
219
|
+
taskId: context.taskId,
|
|
220
|
+
providerId: 'kimi',
|
|
221
|
+
status: 'completed',
|
|
222
|
+
output,
|
|
223
|
+
exitCode: 0,
|
|
224
|
+
startTime,
|
|
225
|
+
endTime,
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const endTime = new Date().toISOString();
|
|
229
|
+
this.runningInstances.delete(instanceId);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
instanceId,
|
|
233
|
+
taskId: context.taskId,
|
|
234
|
+
providerId: 'kimi',
|
|
235
|
+
status: 'failed',
|
|
236
|
+
exitCode: 1,
|
|
237
|
+
startTime,
|
|
238
|
+
endTime,
|
|
239
|
+
error: getErrorMessage(error),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* List currently in-flight Kimi API calls.
|
|
246
|
+
*
|
|
247
|
+
* Because each `spawn()` call awaits the API response, this list is
|
|
248
|
+
* typically empty unless concurrent spawns are in flight.
|
|
249
|
+
*
|
|
250
|
+
* @returns Array of in-progress spawn results
|
|
251
|
+
*/
|
|
252
|
+
async listRunning(): Promise<SpawnResult[]> {
|
|
253
|
+
return [...this.runningInstances.values()].map((entry) => ({
|
|
254
|
+
instanceId: entry.instanceId,
|
|
255
|
+
taskId: entry.taskId,
|
|
256
|
+
providerId: 'kimi',
|
|
257
|
+
status: 'running' as const,
|
|
258
|
+
startTime: entry.startTime,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Remove an instance from the running-instances tracking map.
|
|
264
|
+
*
|
|
265
|
+
* The underlying fetch call cannot be cancelled externally once started.
|
|
266
|
+
* This method removes the entry so it will no longer appear in
|
|
267
|
+
* `listRunning()`, but does not abort the in-progress HTTP request.
|
|
268
|
+
*
|
|
269
|
+
* @param instanceId - ID of the spawn instance to terminate
|
|
270
|
+
*/
|
|
271
|
+
async terminate(instanceId: string): Promise<void> {
|
|
272
|
+
this.runningInstances.delete(instanceId);
|
|
273
|
+
}
|
|
274
|
+
}
|