opencode-tbot 0.1.4 → 0.1.5

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/plugin.js CHANGED
@@ -337,14 +337,15 @@ var OpenCodeClient = class {
337
337
  }, SDK_OPTIONS));
338
338
  const finishedAt = Date.now();
339
339
  const bodyMd = input.structured ? extractStructuredMarkdown(data.info?.structured) : null;
340
- const fallbackText = extractTextFromParts(data.parts) || bodyMd || EMPTY_RESPONSE_TEXT;
340
+ const responseParts = Array.isArray(data.parts) ? data.parts : [];
341
+ const fallbackText = extractTextFromParts(responseParts) || bodyMd || EMPTY_RESPONSE_TEXT;
341
342
  return {
342
343
  assistantError: data.info?.error ?? null,
343
344
  bodyMd,
344
345
  fallbackText,
345
346
  info: data.info ?? null,
346
347
  metrics: extractPromptMetrics(data.info, startedAt, finishedAt),
347
- parts: data.parts,
348
+ parts: responseParts,
348
349
  structured: data.info?.structured ?? null
349
350
  };
350
351
  }
@@ -446,6 +447,7 @@ function normalizeVariants(variants) {
446
447
  }));
447
448
  }
448
449
  function extractTextFromParts(parts) {
450
+ if (!Array.isArray(parts)) return "";
449
451
  return parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
450
452
  }
451
453
  function extractStructuredMarkdown(structured) {
@@ -778,7 +780,10 @@ var OpenRouterVoiceTranscriptionClient = class {
778
780
  temperature: 0
779
781
  } }, { timeoutMs: this.timeoutMs });
780
782
  } catch (error) {
781
- throw new VoiceTranscriptionFailedError(extractErrorMessage(error) ?? "Failed to reach OpenRouter voice transcription.");
783
+ throw new VoiceTranscriptionFailedError(buildTranscriptionErrorMessage(error, {
784
+ format,
785
+ model: this.model
786
+ }));
782
787
  }
783
788
  return { text: extractTranscript(response) };
784
789
  }
@@ -841,8 +846,108 @@ function extractTranscript(response) {
841
846
  function isTextContentItem(value) {
842
847
  return !!value && typeof value === "object" && "type" in value && value.type === "text" && "text" in value && typeof value.text === "string";
843
848
  }
849
+ function buildTranscriptionErrorMessage(error, context) {
850
+ const parsedBody = parseJsonBody(extractStringField(error, "body"));
851
+ const rawMessages = dedupeNonEmptyStrings([
852
+ extractErrorMessage(error),
853
+ extractErrorMessage(readField(error, "error")),
854
+ extractErrorMessage(readField(error, "data")),
855
+ extractErrorMessage(readField(readField(error, "data"), "error")),
856
+ extractErrorMessage(parsedBody),
857
+ extractErrorMessage(readField(parsedBody, "error")),
858
+ extractMetadataRawMessage(error),
859
+ extractMetadataRawMessage(parsedBody)
860
+ ]);
861
+ const messages = rawMessages.some((message) => !isGenericProviderMessage(message)) ? rawMessages.filter((message) => !isGenericProviderMessage(message)) : rawMessages;
862
+ const providerName = extractProviderName(error) ?? extractProviderName(parsedBody);
863
+ const statusCode = extractNumericField(error, "statusCode") ?? extractNumericField(parsedBody, "statusCode");
864
+ const errorCode = extractErrorCode(error, parsedBody);
865
+ return joinNonEmptyParts$1([
866
+ ...messages,
867
+ `model: ${context.model}`,
868
+ `format: ${context.format}`,
869
+ providerName ? `provider: ${providerName}` : null,
870
+ statusCode !== null ? `status: ${statusCode}` : null,
871
+ errorCode !== null ? `code: ${errorCode}` : null
872
+ ]) ?? "Failed to reach OpenRouter voice transcription.";
873
+ }
844
874
  function extractErrorMessage(error) {
845
- return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : null;
875
+ if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
876
+ return extractStringField(error, "message");
877
+ }
878
+ function extractMetadataRawMessage(value) {
879
+ const raw = extractStringField(readField(value, "metadata") ?? readField(readField(value, "error"), "metadata") ?? readField(readField(readField(value, "data"), "error"), "metadata"), "raw");
880
+ if (!raw) return null;
881
+ return raw.length <= 280 ? raw : `${raw.slice(0, 277)}...`;
882
+ }
883
+ function extractProviderName(value) {
884
+ const candidates = [
885
+ readField(value, "metadata"),
886
+ readField(readField(value, "error"), "metadata"),
887
+ readField(readField(readField(value, "data"), "error"), "metadata")
888
+ ];
889
+ for (const candidate of candidates) {
890
+ const providerName = extractStringField(candidate, "provider_name") ?? extractStringField(candidate, "providerName") ?? extractStringField(candidate, "provider");
891
+ if (providerName) return providerName;
892
+ }
893
+ return null;
894
+ }
895
+ function extractErrorCode(...values) {
896
+ for (const value of values) {
897
+ const candidates = [
898
+ value,
899
+ readField(value, "error"),
900
+ readField(value, "data"),
901
+ readField(readField(value, "data"), "error")
902
+ ];
903
+ for (const candidate of candidates) {
904
+ const code = extractNumericField(candidate, "code");
905
+ if (code !== null) return code;
906
+ }
907
+ }
908
+ return null;
909
+ }
910
+ function extractNumericField(value, fieldName) {
911
+ if (!value || typeof value !== "object" || !(fieldName in value)) return null;
912
+ const fieldValue = value[fieldName];
913
+ return typeof fieldValue === "number" && Number.isFinite(fieldValue) ? fieldValue : null;
914
+ }
915
+ function extractStringField(value, fieldName) {
916
+ if (!value || typeof value !== "object" || !(fieldName in value)) return null;
917
+ const fieldValue = value[fieldName];
918
+ return typeof fieldValue === "string" && fieldValue.trim().length > 0 ? fieldValue.trim() : null;
919
+ }
920
+ function readField(value, fieldName) {
921
+ return value && typeof value === "object" && fieldName in value ? value[fieldName] : null;
922
+ }
923
+ function parseJsonBody(body) {
924
+ if (!body) return null;
925
+ try {
926
+ return JSON.parse(body);
927
+ } catch {
928
+ return null;
929
+ }
930
+ }
931
+ function dedupeNonEmptyStrings(values) {
932
+ const seen = /* @__PURE__ */ new Set();
933
+ const result = [];
934
+ for (const value of values) {
935
+ const normalized = value?.trim();
936
+ if (!normalized) continue;
937
+ const key = normalized.toLowerCase();
938
+ if (seen.has(key)) continue;
939
+ seen.add(key);
940
+ result.push(normalized);
941
+ }
942
+ return result;
943
+ }
944
+ function joinNonEmptyParts$1(parts) {
945
+ const filtered = parts.map((part) => part?.trim()).filter((part) => !!part);
946
+ return filtered.length > 0 ? filtered.join(" | ") : null;
947
+ }
948
+ function isGenericProviderMessage(message) {
949
+ const normalized = message.trim().toLowerCase();
950
+ return normalized === "provider returned error" || normalized === "failed to reach openrouter voice transcription.";
846
951
  }
847
952
  //#endregion
848
953
  //#region src/services/voice-transcription/voice-transcription.service.ts
@@ -962,14 +1067,14 @@ var GetPathUseCase = class {
962
1067
  //#endregion
963
1068
  //#region src/use-cases/get-status.usecase.ts
964
1069
  var GetStatusUseCase = class {
965
- constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceTranscriptionEnabled) {
1070
+ constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceRecognitionStatus) {
966
1071
  this.getHealthUseCase = getHealthUseCase;
967
1072
  this.getPathUseCase = getPathUseCase;
968
1073
  this.listLspUseCase = listLspUseCase;
969
1074
  this.listMcpUseCase = listMcpUseCase;
970
1075
  this.listSessionsUseCase = listSessionsUseCase;
971
1076
  this.sessionRepo = sessionRepo;
972
- this.voiceTranscriptionEnabled = voiceTranscriptionEnabled;
1077
+ this.voiceRecognitionStatus = voiceRecognitionStatus;
973
1078
  }
974
1079
  async execute(input) {
975
1080
  const [health, path, lsp, mcp] = await Promise.allSettled([
@@ -984,7 +1089,7 @@ var GetStatusUseCase = class {
984
1089
  health: mapSettledResult(health),
985
1090
  path: pathResult,
986
1091
  plugins,
987
- voiceRecognition: { enabled: this.voiceTranscriptionEnabled },
1092
+ voiceRecognition: this.voiceRecognitionStatus,
988
1093
  workspace,
989
1094
  lsp: mapSettledResult(lsp),
990
1095
  mcp: mapSettledResult(mcp)
@@ -1617,7 +1722,10 @@ function createContainer(config, opencodeClient, logger) {
1617
1722
  const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
1618
1723
  const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
1619
1724
  const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
1620
- const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, config.openrouter.configured);
1725
+ const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, {
1726
+ status: config.openrouter.configured ? "configured" : "not_configured",
1727
+ model: config.openrouter.configured ? config.openrouter.model : null
1728
+ });
1621
1729
  const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
1622
1730
  const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
1623
1731
  const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger, foregroundSessionTracker);
@@ -2678,7 +2786,7 @@ function presentStatusMarkdownSection(title, lines) {
2678
2786
  return [`## ${title}`, ...lines].join("\n");
2679
2787
  }
2680
2788
  function presentStatusPlainOverviewLines(input, copy, layout) {
2681
- const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition.enabled))];
2789
+ const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
2682
2790
  if (input.health.status === "error") return [
2683
2791
  ...lines,
2684
2792
  ...presentStatusPlainErrorDetailLines(input.health.error, copy, layout),
@@ -2691,7 +2799,7 @@ function presentStatusPlainOverviewLines(input, copy, layout) {
2691
2799
  ];
2692
2800
  }
2693
2801
  function presentStatusMarkdownOverviewLines(input, copy, layout) {
2694
- const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition.enabled))];
2802
+ const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
2695
2803
  if (input.health.status === "error") return [
2696
2804
  ...lines,
2697
2805
  ...presentStatusMarkdownErrorDetailLines(input.health.error, copy, layout),
@@ -2791,8 +2899,9 @@ function splitStatusLines(text) {
2791
2899
  function formatHealthBadge(healthy, layout) {
2792
2900
  return healthy ? "🟢" : layout.errorStatus;
2793
2901
  }
2794
- function formatVoiceRecognitionBadge(enabled) {
2795
- return enabled ? "🟢" : "⚪";
2902
+ function formatVoiceRecognitionBadge(status, layout) {
2903
+ if (status.status === "configured") return status.model ? `\uD83D\uDFE1 ${layout.voiceRecognitionConfiguredLabel} (${status.model})` : `\uD83D\uDFE1 ${layout.voiceRecognitionConfiguredLabel}`;
2904
+ return `\u26AA ${layout.voiceRecognitionNotConfiguredLabel}`;
2796
2905
  }
2797
2906
  function formatLspStatusBadge(status) {
2798
2907
  switch (status.status) {
@@ -2862,7 +2971,9 @@ function getStatusLayoutCopy(copy) {
2862
2971
  rootLabel: "Root",
2863
2972
  statusLabel: "Status",
2864
2973
  tbotVersionLabel: "opencode-tbot Version",
2974
+ voiceRecognitionConfiguredLabel: "configured",
2865
2975
  voiceRecognitionLabel: "Voice Recognition",
2976
+ voiceRecognitionNotConfiguredLabel: "not configured",
2866
2977
  workspaceTitle: "📁 Workspace"
2867
2978
  };
2868
2979
  return {
@@ -2885,7 +2996,9 @@ function getStatusLayoutCopy(copy) {
2885
2996
  rootLabel: "根目录",
2886
2997
  statusLabel: "状态",
2887
2998
  tbotVersionLabel: "opencode-tbot版本",
2999
+ voiceRecognitionConfiguredLabel: "已配置",
2888
3000
  voiceRecognitionLabel: "语音识别",
3001
+ voiceRecognitionNotConfiguredLabel: "未配置",
2889
3002
  workspaceTitle: "📁 工作区"
2890
3003
  };
2891
3004
  }