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 +125 -12
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
2795
|
-
|
|
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
|
}
|