@ganglion/xacpx 0.13.0 → 0.14.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.
@@ -72,6 +72,10 @@ function encodeBridgePromptUsageEvent(event) {
72
72
  return `${JSON.stringify(event)}
73
73
  `;
74
74
  }
75
+ function encodeBridgePromptCommandsEvent(event) {
76
+ return `${JSON.stringify(event)}
77
+ `;
78
+ }
75
79
  function encodeBridgeSessionProgressEvent(event) {
76
80
  return `${JSON.stringify(event)}
77
81
  `;
@@ -429,6 +433,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
429
433
  let onThought;
430
434
  let onPlan;
431
435
  let onUsage;
436
+ let onCommands;
432
437
  let rawStream = false;
433
438
  if (options === undefined) {
434
439
  toolEventMode = "text";
@@ -441,6 +446,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
441
446
  onThought = options.onThought;
442
447
  onPlan = options.onPlan;
443
448
  onUsage = options.onUsage;
449
+ onCommands = options.onCommands;
444
450
  rawStream = options.rawStream ?? false;
445
451
  toolEventMode = resolveToolEventMode({
446
452
  toolEventMode: options.mode,
@@ -454,12 +460,14 @@ function createStreamingPromptState(formatToolCalls = false, options) {
454
460
  pendingLine: "",
455
461
  formatToolCalls,
456
462
  emittedToolCallIds: new Set,
463
+ toolCalls: new Map,
457
464
  toolEventMode,
458
465
  rawStream,
459
466
  onToolEvent,
460
467
  onThought,
461
468
  onPlan,
462
469
  onUsage,
470
+ onCommands,
463
471
  finalize() {
464
472
  if (this.pendingLine.trim().length > 0) {
465
473
  parseStreamingChunks(this, this.pendingLine);
@@ -500,7 +508,8 @@ function parseStreamingChunks(state, line) {
500
508
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
501
509
  const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
502
510
  if (wantsStructured && state.onToolEvent) {
503
- const toolEvent = buildToolUseEvent(update);
511
+ const merged = update.toolCallId ? mergeToolCallUpdate(state, update.toolCallId, update) : update;
512
+ const toolEvent = buildToolUseEvent(merged);
504
513
  if (toolEvent)
505
514
  state.onToolEvent(toolEvent);
506
515
  }
@@ -527,8 +536,17 @@ function parseStreamingChunks(state, line) {
527
536
  if (update.sessionUpdate === "usage_update") {
528
537
  const used = typeof update.used === "number" && Number.isFinite(update.used) ? update.used : undefined;
529
538
  const size = typeof update.size === "number" && Number.isFinite(update.size) ? update.size : undefined;
530
- if (used !== undefined && size !== undefined && size > 0)
531
- state.onUsage?.({ used, size });
539
+ if (used !== undefined && size !== undefined && size > 0) {
540
+ const cost = normalizeUsageCost(update.cost);
541
+ const breakdown = normalizeUsageBreakdown(update._meta?.usage);
542
+ state.onUsage?.({ used, size, ...cost ? { cost } : {}, ...breakdown ? { breakdown } : {} });
543
+ }
544
+ return;
545
+ }
546
+ if (update.sessionUpdate === "available_commands_update") {
547
+ if (Array.isArray(update.availableCommands)) {
548
+ state.onCommands?.(normalizeAgentCommands(update.availableCommands));
549
+ }
532
550
  return;
533
551
  }
534
552
  const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
@@ -578,6 +596,29 @@ function formatToolCallEvent(update, sessionUpdate) {
578
596
  const statusText = status ? ` (${status})` : "";
579
597
  return `${emoji} ${title}${statusText}${summaryText}`;
580
598
  }
599
+ function isEmptyToolField(v) {
600
+ if (v === undefined || v === null)
601
+ return true;
602
+ if (typeof v === "string")
603
+ return v.trim().length === 0;
604
+ if (Array.isArray(v))
605
+ return v.length === 0;
606
+ if (typeof v === "object")
607
+ return Object.keys(v).length === 0;
608
+ return false;
609
+ }
610
+ function mergeToolCallUpdate(state, toolCallId, update) {
611
+ const prev = state.toolCalls.get(toolCallId) ?? { toolCallId };
612
+ const merged = { ...prev };
613
+ for (const key of ["kind", "title", "rawInput", "content", "rawOutput", "locations", "status"]) {
614
+ const next = update[key];
615
+ if (!isEmptyToolField(next))
616
+ merged[key] = next;
617
+ }
618
+ merged.toolCallId = toolCallId;
619
+ state.toolCalls.set(toolCallId, merged);
620
+ return merged;
621
+ }
581
622
  function buildToolUseEvent(update) {
582
623
  if (!update)
583
624
  return null;
@@ -695,6 +736,52 @@ function readFirstStringArray(record, keys) {
695
736
  }
696
737
  return;
697
738
  }
739
+ function asFiniteNumber(value) {
740
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
741
+ }
742
+ function firstFiniteNumber(record, keys) {
743
+ for (const key of keys) {
744
+ const n = asFiniteNumber(record[key]);
745
+ if (n !== undefined)
746
+ return n;
747
+ }
748
+ return;
749
+ }
750
+ function normalizeUsageBreakdown(value) {
751
+ if (!isRecord(value))
752
+ return;
753
+ const out = {};
754
+ for (const [key, aliases] of USAGE_BREAKDOWN_FIELDS) {
755
+ const n = firstFiniteNumber(value, aliases);
756
+ if (n !== undefined)
757
+ out[key] = n;
758
+ }
759
+ return Object.keys(out).length > 0 ? out : undefined;
760
+ }
761
+ function normalizeUsageCost(value) {
762
+ if (!isRecord(value))
763
+ return;
764
+ const amount = asFiniteNumber(value.amount);
765
+ const currency = readString(value, "currency");
766
+ if (amount === undefined && !currency)
767
+ return;
768
+ return { ...amount !== undefined ? { amount } : {}, ...currency ? { currency } : {} };
769
+ }
770
+ function normalizeAgentCommands(value) {
771
+ if (!Array.isArray(value))
772
+ return [];
773
+ const out = [];
774
+ for (const entry of value) {
775
+ if (!isRecord(entry))
776
+ continue;
777
+ const name = readString(entry, "name");
778
+ if (!name)
779
+ continue;
780
+ const description = readString(entry, "description");
781
+ out.push({ name, ...description ? { description } : {}, hasInput: entry.input != null });
782
+ }
783
+ return out;
784
+ }
698
785
  function isRecord(value) {
699
786
  return typeof value === "object" && value !== null && !Array.isArray(value);
700
787
  }
@@ -720,8 +807,17 @@ function isGenericToolTitle(kind, title) {
720
807
  }
721
808
  return false;
722
809
  }
810
+ var USAGE_BREAKDOWN_FIELDS;
723
811
  var init_streaming_prompt = __esm(() => {
724
812
  init_tool_kind_emoji();
813
+ USAGE_BREAKDOWN_FIELDS = [
814
+ ["inputTokens", ["inputTokens", "input_tokens"]],
815
+ ["outputTokens", ["outputTokens", "output_tokens"]],
816
+ ["cachedReadTokens", ["cachedReadTokens", "cacheReadInputTokens", "cache_read_input_tokens"]],
817
+ ["cachedWriteTokens", ["cachedWriteTokens", "cacheCreationInputTokens", "cache_creation_input_tokens"]],
818
+ ["thoughtTokens", ["thoughtTokens", "thought_tokens"]],
819
+ ["totalTokens", ["totalTokens", "total_tokens"]]
820
+ ];
725
821
  });
726
822
 
727
823
  // src/recovery/discover-parent-package-paths.ts
@@ -1874,6 +1970,7 @@ var init_misc = __esm(() => {
1874
1970
  defaultHomeWorkspaceDescription: "Home directory",
1875
1971
  pluginChannelFeishu: "Feishu channel",
1876
1972
  pluginChannelYuanbao: "Tencent Yuanbao channel",
1973
+ pluginChannelRelay: "Relay hub connector (drive this instance from a self-hosted relay hub)",
1877
1974
  pluginChannelInstallHint: (channelType, packageName) => `Channel ${channelType} requires a plugin: xacpx plugin add ${packageName}`,
1878
1975
  orchestrationSuggestion1: "Run /tasks --stuck to locate stuck tasks",
1879
1976
  orchestrationSuggestion2: "/task <id> shows the full timeline to locate errors",
@@ -2970,6 +3067,7 @@ var init_misc2 = __esm(() => {
2970
3067
  defaultHomeWorkspaceDescription: "用户主目录",
2971
3068
  pluginChannelFeishu: "飞书频道",
2972
3069
  pluginChannelYuanbao: "腾讯元宝频道",
3070
+ pluginChannelRelay: "Relay hub 连接器(从自托管 relay hub 遥控这台实例)",
2973
3071
  pluginChannelInstallHint: (channelType, packageName) => `频道 ${channelType} 需要安装插件:xacpx plugin add ${packageName}`,
2974
3072
  orchestrationSuggestion1: "查看 /tasks --stuck 定位卡住的任务",
2975
3073
  orchestrationSuggestion2: "/task <id> 可看完整时间线定位错误点",
@@ -4049,7 +4147,8 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
4049
4147
  ...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {},
4050
4148
  ...onEvent ? { onThought: (chunk) => onEvent({ type: "prompt.thought", text: chunk }) } : {},
4051
4149
  ...onEvent ? { onPlan: (entries) => onEvent({ type: "prompt.plan", entries }) } : {},
4052
- ...onEvent ? { onUsage: (usage) => onEvent({ type: "prompt.usage", used: usage.used, size: usage.size }) } : {}
4150
+ ...onEvent ? { onUsage: (usage) => onEvent({ type: "prompt.usage", used: usage.used, size: usage.size, ...usage.cost ? { cost: usage.cost } : {}, ...usage.breakdown ? { breakdown: usage.breakdown } : {} }) } : {},
4151
+ ...onEvent ? { onCommands: (commands) => onEvent({ type: "prompt.commands", commands }) } : {}
4053
4152
  });
4054
4153
  let lastReplyAt = now();
4055
4154
  const flushBuffer = () => {
@@ -4347,7 +4446,15 @@ class BridgeServer {
4347
4446
  id: requestId,
4348
4447
  event: "prompt.usage",
4349
4448
  used: event.used,
4350
- size: event.size
4449
+ size: event.size,
4450
+ ...event.cost ? { cost: event.cost } : {},
4451
+ ...event.breakdown ? { breakdown: event.breakdown } : {}
4452
+ }));
4453
+ } else if (event.type === "prompt.commands") {
4454
+ writeLine?.(encodeBridgePromptCommandsEvent({
4455
+ id: requestId,
4456
+ event: "prompt.commands",
4457
+ commands: event.commands
4351
4458
  }));
4352
4459
  }
4353
4460
  });