@love-moon/ai-sdk 0.12.0 → 0.13.1
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 +4 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +14 -1
- package/dist/providers/claude-agent-sdk-session.js +18 -4
- package/dist/providers/codex-app-server-session.js +5 -2
- package/dist/providers/codex-exec-session.js +9 -2
- package/dist/providers/copilot-sdk-session.js +4 -2
- package/dist/providers/dsh-sdk-session.js +6 -2
- package/dist/providers/kimi-cli-session.js +4 -2
- package/dist/providers/kimi-print-session.js +40 -12
- package/dist/providers/opencode-sdk-session.js +8 -2
- package/dist/shared.d.ts +12 -0
- package/dist/shared.js +70 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
package/dist/client.d.ts
CHANGED
|
@@ -56,6 +56,11 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
56
56
|
usesSessionFileReplyStream(): boolean;
|
|
57
57
|
getSessionInfo(): any;
|
|
58
58
|
getCurrentTurnStatus(): any;
|
|
59
|
+
/**
|
|
60
|
+
* Ask the worker for the live turn status (including `active_tool`). Uses
|
|
61
|
+
* the control lane so it answers while `runTurn` holds the request queue.
|
|
62
|
+
*/
|
|
63
|
+
fetchCurrentTurnStatus(): Promise<any>;
|
|
59
64
|
setSessionMessageHandler(handler: any): void;
|
|
60
65
|
setWorkingStatusHandler(handler: any): void;
|
|
61
66
|
setSessionReplyTarget(replyTo: any): void;
|
package/dist/client.js
CHANGED
|
@@ -155,6 +155,17 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
155
155
|
getCurrentTurnStatus() {
|
|
156
156
|
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Ask the worker for the live turn status (including `active_tool`). Uses
|
|
160
|
+
* the control lane so it answers while `runTurn` holds the request queue.
|
|
161
|
+
*/
|
|
162
|
+
async fetchCurrentTurnStatus() {
|
|
163
|
+
const status = await this.callWorker("getCurrentTurnStatus", [], { messageType: "control" });
|
|
164
|
+
if (status && typeof status === "object") {
|
|
165
|
+
this.currentTurnStatus = { ...status };
|
|
166
|
+
}
|
|
167
|
+
return this.getCurrentTurnStatus();
|
|
168
|
+
}
|
|
158
169
|
setSessionMessageHandler(handler) {
|
|
159
170
|
this.sessionMessageHandler = typeof handler === "function" ? handler : null;
|
|
160
171
|
}
|
|
@@ -172,7 +183,9 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
172
183
|
return this.callWorker("ensureSessionInfo", []);
|
|
173
184
|
}
|
|
174
185
|
async getSessionUsageSummary() {
|
|
175
|
-
|
|
186
|
+
// Control lane: fire awaits this before every runtime-status report, so
|
|
187
|
+
// queueing it behind a running `runTurn` would stall mid-turn statuses.
|
|
188
|
+
return this.callWorker("getSessionUsageSummary", [], { messageType: "control" });
|
|
176
189
|
}
|
|
177
190
|
async interruptCurrentTurn() {
|
|
178
191
|
return this.callWorker("interruptCurrentTurn", [], {
|
|
@@ -3,7 +3,7 @@ import { CLAUDE_AGENT_SDK_VARIANT as CLAUDE_PROVIDER_VARIANT } from "../built-in
|
|
|
3
3
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
4
4
|
import { PROVIDER_MEDIA_CAPABILITIES, buildClaudeContent } from "../media-adapters.js";
|
|
5
5
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
6
|
-
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, isGoalStatus, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
6
|
+
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, isGoalStatus, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
7
7
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
8
8
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
9
9
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -135,9 +135,11 @@ export function resolveClaudeCommandForRoot(commandLine, env = process.env) {
|
|
|
135
135
|
function normalizeText(value) {
|
|
136
136
|
return typeof value === "string" ? value : "";
|
|
137
137
|
}
|
|
138
|
+
function contentBlocks(message) {
|
|
139
|
+
return Array.isArray(message?.content) ? message.content : [];
|
|
140
|
+
}
|
|
138
141
|
function extractAssistantText(message) {
|
|
139
|
-
|
|
140
|
-
return content
|
|
142
|
+
return contentBlocks(message)
|
|
141
143
|
.map((block) => (block?.type === "text" && typeof block.text === "string" ? block.text : ""))
|
|
142
144
|
.filter(Boolean)
|
|
143
145
|
.join("");
|
|
@@ -339,7 +341,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
339
341
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
340
342
|
}
|
|
341
343
|
getCurrentTurnStatus() {
|
|
342
|
-
return this.currentTurnStatus
|
|
344
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
343
345
|
}
|
|
344
346
|
async ensureSessionInfo() {
|
|
345
347
|
return this.getSessionInfo();
|
|
@@ -773,6 +775,11 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
773
775
|
}
|
|
774
776
|
case "assistant": {
|
|
775
777
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
778
|
+
for (const block of contentBlocks(message.message)) {
|
|
779
|
+
if (block?.type === "tool_use") {
|
|
780
|
+
noteToolStarted(currentTurn, block.id, block.name, block.input);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
776
783
|
const text = extractAssistantText(message.message);
|
|
777
784
|
if (!text) {
|
|
778
785
|
return;
|
|
@@ -788,6 +795,13 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
788
795
|
await this.emitAssistantMessage(text);
|
|
789
796
|
return;
|
|
790
797
|
}
|
|
798
|
+
case "user":
|
|
799
|
+
for (const block of contentBlocks(message.message)) {
|
|
800
|
+
if (block?.type === "tool_result") {
|
|
801
|
+
noteToolFinished(currentTurn, block.tool_use_id);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return;
|
|
791
805
|
case "rate_limit_event":
|
|
792
806
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
793
807
|
this.rateLimitInfo =
|
|
@@ -4,7 +4,7 @@ import { appendContextFilesToPrompt } from "../context-files.js";
|
|
|
4
4
|
import { PROVIDER_MEDIA_CAPABILITIES, buildCodexAppServerInput } from "../media-adapters.js";
|
|
5
5
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
6
6
|
import { CodexAppServerTransport } from "../transports/codex-app-server-transport.js";
|
|
7
|
-
import { TERMINAL_GOAL_STATUSES, emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
7
|
+
import { TERMINAL_GOAL_STATUSES, describeCodexToolItem, emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
8
8
|
const TERMINAL_GOAL_STATUSES_LOCAL = new Set(TERMINAL_GOAL_STATUSES);
|
|
9
9
|
function isTerminalGoalStatusLocal(status) {
|
|
10
10
|
return typeof status === "string" && TERMINAL_GOAL_STATUSES_LOCAL.has(status);
|
|
@@ -301,7 +301,7 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
301
301
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
302
302
|
}
|
|
303
303
|
getCurrentTurnStatus() {
|
|
304
|
-
return this.currentTurnStatus
|
|
304
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
305
305
|
}
|
|
306
306
|
async ensureSessionInfo() {
|
|
307
307
|
await this.boot();
|
|
@@ -835,6 +835,8 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
835
835
|
if (!currentTurn) {
|
|
836
836
|
return;
|
|
837
837
|
}
|
|
838
|
+
const tool = describeCodexToolItem(params?.item);
|
|
839
|
+
noteToolStarted(currentTurn, extractItemId(params?.item), tool?.name, tool?.input);
|
|
838
840
|
const phase = resolveItemPhase(params?.item);
|
|
839
841
|
if (!phase) {
|
|
840
842
|
return;
|
|
@@ -864,6 +866,7 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
864
866
|
if (!currentTurn) {
|
|
865
867
|
return;
|
|
866
868
|
}
|
|
869
|
+
noteToolFinished(currentTurn, extractItemId(params?.item));
|
|
867
870
|
const phase = resolveItemPhase(params?.item);
|
|
868
871
|
if (phase !== "assistant_message") {
|
|
869
872
|
return;
|
|
@@ -9,7 +9,7 @@ import { CODEX_EXEC_VARIANT as CODEX_EXEC_PROVIDER_VARIANT } from "../built-in-b
|
|
|
9
9
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
10
10
|
import { PROVIDER_MEDIA_CAPABILITIES } from "../media-adapters.js";
|
|
11
11
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
12
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
12
|
+
import { describeCodexToolItem, emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, parseCommandParts, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
13
13
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
14
14
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
15
15
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -250,7 +250,7 @@ export class CodexExecSession extends EventEmitter {
|
|
|
250
250
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
251
251
|
}
|
|
252
252
|
getCurrentTurnStatus() {
|
|
253
|
-
return this.currentTurnStatus
|
|
253
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
254
254
|
}
|
|
255
255
|
async ensureSessionInfo() {
|
|
256
256
|
this.announceSession();
|
|
@@ -475,6 +475,13 @@ export class CodexExecSession extends EventEmitter {
|
|
|
475
475
|
payload = { type: "raw", line: normalizedLine };
|
|
476
476
|
}
|
|
477
477
|
currentTurn.stdoutEvents.push(payload);
|
|
478
|
+
if (payload?.type === "item.started") {
|
|
479
|
+
const tool = describeCodexToolItem(payload.item);
|
|
480
|
+
noteToolStarted(currentTurn, payload.item?.id, tool?.name, tool?.input);
|
|
481
|
+
}
|
|
482
|
+
else if (payload?.type === "item.completed") {
|
|
483
|
+
noteToolFinished(currentTurn, payload.item?.id);
|
|
484
|
+
}
|
|
478
485
|
const phase = resolveExecPhase(payload);
|
|
479
486
|
if (phase) {
|
|
480
487
|
void this.emitWorkingStatus({
|
|
@@ -6,7 +6,7 @@ import { COPILOT_SDK_VARIANT as COPILOT_PROVIDER_VARIANT } from "../built-in-bac
|
|
|
6
6
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
7
7
|
import { PROVIDER_MEDIA_CAPABILITIES, buildCopilotAttachments } from "../media-adapters.js";
|
|
8
8
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
9
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, withoutCopilotGithubTokenEnv, } from "../shared.js";
|
|
9
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, parseCommandParts, proxyToEnv, sanitizeForLog, withoutCopilotGithubTokenEnv, withActiveTool, } from "../shared.js";
|
|
10
10
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
11
11
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
12
12
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -601,7 +601,7 @@ export class CopilotSdkSession extends EventEmitter {
|
|
|
601
601
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
602
602
|
}
|
|
603
603
|
getCurrentTurnStatus() {
|
|
604
|
-
return this.currentTurnStatus
|
|
604
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
605
605
|
}
|
|
606
606
|
async ensureSessionInfo() {
|
|
607
607
|
await this.boot();
|
|
@@ -1199,6 +1199,7 @@ export class CopilotSdkSession extends EventEmitter {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
currentTurn.activeToolName = normalizeText(event.data?.toolName);
|
|
1201
1201
|
currentTurn.activeToolPhase = toolPhaseForName(currentTurn.activeToolName);
|
|
1202
|
+
noteToolStarted(currentTurn, event.data?.toolCallId, currentTurn.activeToolName, event.data?.arguments);
|
|
1202
1203
|
await this.emitWorkingStatus({
|
|
1203
1204
|
phase: currentTurn.activeToolPhase,
|
|
1204
1205
|
reply_in_progress: true,
|
|
@@ -1239,6 +1240,7 @@ export class CopilotSdkSession extends EventEmitter {
|
|
|
1239
1240
|
}
|
|
1240
1241
|
currentTurn.activeToolName = "";
|
|
1241
1242
|
currentTurn.activeToolPhase = "";
|
|
1243
|
+
noteToolFinished(currentTurn, event.data?.toolCallId);
|
|
1242
1244
|
const preview = extractToolResultPreview(event.data?.result);
|
|
1243
1245
|
await this.emitWorkingStatus({
|
|
1244
1246
|
phase: "message_aggregation",
|
|
@@ -6,7 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { DSH_SDK_VARIANT } from "../built-in-backends.js";
|
|
9
|
-
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
9
|
+
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
12
12
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
@@ -220,7 +220,7 @@ export class DshSdkSession extends EventEmitter {
|
|
|
220
220
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
221
221
|
}
|
|
222
222
|
getCurrentTurnStatus() {
|
|
223
|
-
return this.currentTurnStatus
|
|
223
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
224
224
|
}
|
|
225
225
|
async ensureSessionInfo() {
|
|
226
226
|
return this.getSessionInfo();
|
|
@@ -714,6 +714,7 @@ export class DshSdkSession extends EventEmitter {
|
|
|
714
714
|
}
|
|
715
715
|
case "tool/call": {
|
|
716
716
|
const toolName = String(event.data?.name || "");
|
|
717
|
+
noteToolStarted(currentTurn, event.data?.callId, toolName, event.data?.arguments);
|
|
717
718
|
const phase = toolPhaseForName(toolName);
|
|
718
719
|
const argsPreview = sanitizeForLog(event.data?.arguments, 120);
|
|
719
720
|
await this.emitWorkingStatus({
|
|
@@ -723,6 +724,9 @@ export class DshSdkSession extends EventEmitter {
|
|
|
723
724
|
}, onProgress);
|
|
724
725
|
return;
|
|
725
726
|
}
|
|
727
|
+
case "tool/result":
|
|
728
|
+
noteToolFinished(currentTurn, event.data?.message?.content?.[0]?.toolCallId);
|
|
729
|
+
return;
|
|
726
730
|
case "todo/write": {
|
|
727
731
|
const todos = Array.isArray(event.data?.todos) ? event.data.todos : [];
|
|
728
732
|
const done = todos.filter((item) => item?.status === "completed").length;
|
|
@@ -5,7 +5,7 @@ import { appendContextFilesToPrompt } from "../context-files.js";
|
|
|
5
5
|
import { PROVIDER_MEDIA_CAPABILITIES, buildKimiContent } from "../media-adapters.js";
|
|
6
6
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
7
7
|
import { KimiWireTransport } from "../transports/kimi-wire-transport.js";
|
|
8
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
8
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
9
9
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
10
10
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
11
11
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -241,7 +241,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
241
241
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
242
242
|
}
|
|
243
243
|
getCurrentTurnStatus() {
|
|
244
|
-
return this.currentTurnStatus
|
|
244
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
245
245
|
}
|
|
246
246
|
async ensureSessionInfo() {
|
|
247
247
|
await this.boot();
|
|
@@ -802,6 +802,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
802
802
|
if (toolId) {
|
|
803
803
|
currentTurn.toolCalls.set(toolId, toolName);
|
|
804
804
|
}
|
|
805
|
+
noteToolStarted(currentTurn, toolId, toolName, normalizedPayload?.function?.arguments);
|
|
805
806
|
const phase = toolPhaseForName(toolName);
|
|
806
807
|
await this.emitWorkingStatus({
|
|
807
808
|
phase,
|
|
@@ -813,6 +814,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
813
814
|
case "ToolResult": {
|
|
814
815
|
const toolId = normalizeText(normalizedPayload.tool_call_id);
|
|
815
816
|
const toolName = currentTurn.toolCalls.get(toolId) || "";
|
|
817
|
+
noteToolFinished(currentTurn, toolId);
|
|
816
818
|
const phase = toolPhaseForName(toolName);
|
|
817
819
|
const statusDoneLine = sanitizeSummary(normalizedPayload?.return_value?.message || normalizedPayload?.return_value?.output, 160) || undefined;
|
|
818
820
|
await this.emitWorkingStatus({
|
|
@@ -8,7 +8,7 @@ import { KIMI_CLI_PRINT_VARIANT as KIMI_PRINT_PROVIDER_VARIANT } from "../built-
|
|
|
8
8
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
9
9
|
import { PROVIDER_MEDIA_CAPABILITIES, UNSUPPORTED_MEDIA_CAPABILITIES, buildKimiContent, } from "../media-adapters.js";
|
|
10
10
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
11
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
11
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, parseCommandParts, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
12
12
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
13
13
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
14
14
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -281,7 +281,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
281
281
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
282
282
|
}
|
|
283
283
|
getCurrentTurnStatus() {
|
|
284
|
-
return this.currentTurnStatus
|
|
284
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
285
285
|
}
|
|
286
286
|
async ensureSessionInfo() {
|
|
287
287
|
if (!this.sessionInfo?.sessionIdDeferred) {
|
|
@@ -532,6 +532,10 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
532
532
|
const stdoutReader = readline.createInterface({ input: child.stdout });
|
|
533
533
|
const stderrReader = readline.createInterface({ input: child.stderr });
|
|
534
534
|
let timeoutId = null;
|
|
535
|
+
let lastActivityAt = Date.now();
|
|
536
|
+
const noteTurnActivity = () => {
|
|
537
|
+
lastActivityAt = Date.now();
|
|
538
|
+
};
|
|
535
539
|
const settle = (error, value = null) => {
|
|
536
540
|
if (currentTurn.settled) {
|
|
537
541
|
return;
|
|
@@ -548,17 +552,35 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
548
552
|
}
|
|
549
553
|
resolve(value);
|
|
550
554
|
};
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
555
|
+
// Activity-based idle deadline (aligned with wire mode): any stdout/stderr
|
|
556
|
+
// output resets the idle clock, so a busy turn is never killed mid-work;
|
|
557
|
+
// only a turn with no output at all for turnDeadlineMs is considered stuck.
|
|
558
|
+
const scheduleTurnDeadline = () => {
|
|
559
|
+
if (currentTurn.settled) {
|
|
560
|
+
return;
|
|
557
561
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
+
const idleMs = Math.max(0, Date.now() - lastActivityAt);
|
|
563
|
+
const waitMs = Math.max(1, this.turnDeadlineMs - idleMs);
|
|
564
|
+
timeoutId = setTimeout(() => {
|
|
565
|
+
if (currentTurn.settled) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (Date.now() - lastActivityAt < this.turnDeadlineMs) {
|
|
569
|
+
scheduleTurnDeadline();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
child.kill("SIGTERM");
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// ignore
|
|
577
|
+
}
|
|
578
|
+
settle(createTurnError("Kimi print turn timed out", {
|
|
579
|
+
reason: "turn_timeout",
|
|
580
|
+
}));
|
|
581
|
+
}, waitMs);
|
|
582
|
+
};
|
|
583
|
+
scheduleTurnDeadline();
|
|
562
584
|
stdoutReader.on("line", (line) => {
|
|
563
585
|
const normalizedLine = String(line || "").trim();
|
|
564
586
|
if (!normalizedLine) {
|
|
@@ -571,6 +593,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
571
593
|
catch {
|
|
572
594
|
payload = { role: "raw", content: normalizedLine };
|
|
573
595
|
}
|
|
596
|
+
noteTurnActivity();
|
|
574
597
|
currentTurn.items.push(payload);
|
|
575
598
|
const role = String(payload?.role || "").trim().toLowerCase();
|
|
576
599
|
if (role === "meta") {
|
|
@@ -583,6 +606,9 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
583
606
|
if (role === "assistant") {
|
|
584
607
|
const assistantText = normalizeTextContent(payload.content);
|
|
585
608
|
if (Array.isArray(payload?.tool_calls) && payload.tool_calls.length > 0) {
|
|
609
|
+
for (const call of payload.tool_calls) {
|
|
610
|
+
noteToolStarted(currentTurn, call?.id, call?.function?.name, call?.function?.arguments);
|
|
611
|
+
}
|
|
586
612
|
void this.emitWorkingStatus({
|
|
587
613
|
phase: "command_execution",
|
|
588
614
|
reply_in_progress: true,
|
|
@@ -603,6 +629,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
603
629
|
return;
|
|
604
630
|
}
|
|
605
631
|
if (role === "tool") {
|
|
632
|
+
noteToolFinished(currentTurn, payload.tool_call_id);
|
|
606
633
|
void this.emitWorkingStatus({
|
|
607
634
|
phase: "command_execution",
|
|
608
635
|
reply_in_progress: true,
|
|
@@ -615,6 +642,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
615
642
|
if (!normalizedLine.trim()) {
|
|
616
643
|
return;
|
|
617
644
|
}
|
|
645
|
+
noteTurnActivity();
|
|
618
646
|
currentTurn.stderrTail.push(normalizedLine);
|
|
619
647
|
if (currentTurn.stderrTail.length > 20) {
|
|
620
648
|
currentTurn.stderrTail.shift();
|
|
@@ -4,7 +4,7 @@ import { appendContextFilesToPrompt } from "../context-files.js";
|
|
|
4
4
|
import { PROVIDER_MEDIA_CAPABILITIES, buildOpencodeParts } from "../media-adapters.js";
|
|
5
5
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
6
6
|
import { OpencodeServerTransport } from "../transports/opencode-server-transport.js";
|
|
7
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
7
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
8
8
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
9
9
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
10
10
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -234,7 +234,7 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
234
234
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
235
235
|
}
|
|
236
236
|
getCurrentTurnStatus() {
|
|
237
|
-
return this.currentTurnStatus
|
|
237
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
238
238
|
}
|
|
239
239
|
async ensureSessionInfo() {
|
|
240
240
|
await this.boot();
|
|
@@ -742,6 +742,12 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
742
742
|
return;
|
|
743
743
|
}
|
|
744
744
|
if (part.type === "tool") {
|
|
745
|
+
if (part.state?.status === "completed" || part.state?.status === "error") {
|
|
746
|
+
noteToolFinished(currentTurn, part.callID || part.id);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
noteToolStarted(currentTurn, part.callID || part.id, part.tool, part.state?.input);
|
|
750
|
+
}
|
|
745
751
|
const phase = toolPhaseForName(part.tool);
|
|
746
752
|
const toolStatus = part.state?.status === "completed"
|
|
747
753
|
? part.state?.title || part.state?.output || ""
|
package/dist/shared.d.ts
CHANGED
|
@@ -20,6 +20,18 @@ export function normalizeLogger(logger: any): any;
|
|
|
20
20
|
export function emitLog(logger: any, message: any): void;
|
|
21
21
|
export function truncateText(value: any, maxLen?: number): string;
|
|
22
22
|
export function sanitizeForLog(value: any, maxLen?: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Track tool calls that started but have not finished on a turn object, so
|
|
25
|
+
* `getCurrentTurnStatus()` can report what a long silent turn is running.
|
|
26
|
+
*/
|
|
27
|
+
export function noteToolStarted(turn: any, id: any, name: any, input: any): void;
|
|
28
|
+
export function noteToolFinished(turn: any, id: any): void;
|
|
29
|
+
/** Map a codex app-server (camelCase) or exec (snake_case) tool item to `{ name, input }`. */
|
|
30
|
+
export function describeCodexToolItem(item: any): {
|
|
31
|
+
name: any;
|
|
32
|
+
input: any;
|
|
33
|
+
} | null;
|
|
34
|
+
export function withActiveTool(status: any, turn: any): any;
|
|
23
35
|
export function isTruthyEnv(value: any): boolean;
|
|
24
36
|
export function getBoundedEnvInt(envName: any, fallback: any, min: any, max: any): any;
|
|
25
37
|
export function parseCommandParts(commandLine: any): {
|
package/dist/shared.js
CHANGED
|
@@ -146,6 +146,76 @@ export function sanitizeForLog(value, maxLen = 180) {
|
|
|
146
146
|
return "";
|
|
147
147
|
return truncateText(String(value).replace(/\s+/g, " ").trim(), maxLen);
|
|
148
148
|
}
|
|
149
|
+
const TOOL_INPUT_SUMMARY_KEYS = ["command", "cmd", "file_path", "filePath", "path", "pattern", "query", "url", "description", "prompt"];
|
|
150
|
+
function summarizeToolInput(input) {
|
|
151
|
+
let value = input;
|
|
152
|
+
if (typeof value === "string" && /^\s*[[{]/.test(value)) {
|
|
153
|
+
try {
|
|
154
|
+
value = JSON.parse(value);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// keep the raw string
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
161
|
+
const key = TOOL_INPUT_SUMMARY_KEYS.find((candidate) => value[candidate]);
|
|
162
|
+
value = key ? value[key] : JSON.stringify(value);
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
value = value.join(" ");
|
|
166
|
+
}
|
|
167
|
+
return sanitizeForLog(value, 200) || undefined;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Track tool calls that started but have not finished on a turn object, so
|
|
171
|
+
* `getCurrentTurnStatus()` can report what a long silent turn is running.
|
|
172
|
+
*/
|
|
173
|
+
export function noteToolStarted(turn, id, name, input) {
|
|
174
|
+
if (!turn || !id || !name) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
turn.activeTools ||= new Map();
|
|
178
|
+
const key = String(id);
|
|
179
|
+
if (turn.activeTools.has(key)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
turn.activeTools.set(key, {
|
|
183
|
+
name: String(name),
|
|
184
|
+
summary: summarizeToolInput(input),
|
|
185
|
+
started_at: new Date().toISOString(),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export function noteToolFinished(turn, id) {
|
|
189
|
+
if (id) {
|
|
190
|
+
turn?.activeTools?.delete(String(id));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Map a codex app-server (camelCase) or exec (snake_case) tool item to `{ name, input }`. */
|
|
194
|
+
export function describeCodexToolItem(item) {
|
|
195
|
+
switch (String(item?.type || "").replace(/_/g, "").toLowerCase()) {
|
|
196
|
+
case "commandexecution":
|
|
197
|
+
return { name: "command", input: item.command };
|
|
198
|
+
case "filechange":
|
|
199
|
+
return { name: "apply_patch", input: (item.changes || []).map((change) => change?.path).join(" ") };
|
|
200
|
+
case "mcptoolcall":
|
|
201
|
+
return { name: [item.server, item.tool].filter(Boolean).join("."), input: item.arguments };
|
|
202
|
+
case "dynamictoolcall":
|
|
203
|
+
case "collabagenttoolcall":
|
|
204
|
+
return { name: item.tool, input: item.arguments ?? item.prompt };
|
|
205
|
+
case "websearch":
|
|
206
|
+
return { name: "web_search", input: item.query };
|
|
207
|
+
default:
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
export function withActiveTool(status, turn) {
|
|
212
|
+
if (!status) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const tools = turn?.activeTools ? [...turn.activeTools.values()] : [];
|
|
216
|
+
const activeTool = tools[tools.length - 1];
|
|
217
|
+
return activeTool ? { ...status, active_tool: { ...activeTool } } : { ...status };
|
|
218
|
+
}
|
|
149
219
|
export function isTruthyEnv(value) {
|
|
150
220
|
if (value === undefined || value === null)
|
|
151
221
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"zod": "^4.1.5"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@love-moon/chat-web": "^0.
|
|
56
|
+
"@love-moon/chat-web": "^0.13.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.2",
|
|
60
60
|
"tsx": "^4.20.6",
|
|
61
61
|
"typescript": "^5.6.3"
|
|
62
62
|
},
|
|
63
|
-
"gitCommitId": "
|
|
63
|
+
"gitCommitId": "b82ba1c"
|
|
64
64
|
}
|