@love-moon/ai-sdk 0.13.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # @love-moon/ai-sdk
2
2
 
3
+ ## 0.13.1
4
+
3
5
  ## 0.13.0
4
6
 
5
7
  ## 0.12.0
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
- return this.callWorker("getSessionUsageSummary", []);
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
- const content = Array.isArray(message?.content) ? message.content : [];
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
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 ? { ...this.currentTurnStatus } : null;
284
+ return withActiveTool(this.currentTurnStatus, this.currentTurn);
285
285
  }
286
286
  async ensureSessionInfo() {
287
287
  if (!this.sessionInfo?.sessionIdDeferred) {
@@ -606,6 +606,9 @@ export class KimiPrintSession extends EventEmitter {
606
606
  if (role === "assistant") {
607
607
  const assistantText = normalizeTextContent(payload.content);
608
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
+ }
609
612
  void this.emitWorkingStatus({
610
613
  phase: "command_execution",
611
614
  reply_in_progress: true,
@@ -626,6 +629,7 @@ export class KimiPrintSession extends EventEmitter {
626
629
  return;
627
630
  }
628
631
  if (role === "tool") {
632
+ noteToolFinished(currentTurn, payload.tool_call_id);
629
633
  void this.emitWorkingStatus({
630
634
  phase: "command_execution",
631
635
  reply_in_progress: true,
@@ -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 ? { ...this.currentTurnStatus } : null;
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.13.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.13.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": "e1e507f"
63
+ "gitCommitId": "b82ba1c"
64
64
  }