forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror.ts
More file actions
130 lines (112 loc) · 5.47 KB
/
Copy patherror.ts
File metadata and controls
130 lines (112 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { NamedError } from "@opencode-ai/core/util/error"
import { errorFormat } from "@/util/error"
import { isRecord } from "@/util/record"
type ConfigIssue = { message: string; path: string[] }
function isTaggedError(error: unknown, tag: string): error is Record<string, unknown> {
return isRecord(error) && error._tag === tag
}
function configData(input: unknown, tag: string): Record<string, unknown> | undefined {
if (!isRecord(input)) return undefined
if (input.name === tag && isRecord(input.data)) return input.data
if (input._tag === tag) return input
return undefined
}
function stringField(input: Record<string, unknown>, key: string): string | undefined {
return typeof input[key] === "string" ? input[key] : undefined
}
function configIssues(input: Record<string, unknown>): ConfigIssue[] {
return Array.isArray(input.issues)
? input.issues.filter((issue): issue is ConfigIssue => {
if (!isRecord(issue)) return false
return (
typeof issue.message === "string" &&
Array.isArray(issue.path) &&
issue.path.every((x) => typeof x === "string")
)
})
: []
}
export function FormatError(input: unknown): string | undefined {
if (input instanceof Error && isRecord(input.cause) && "body" in input.cause) {
const formatted = FormatError(input.cause.body)
if (formatted) return formatted
}
// CliError: domain failure surfaced from an effectCmd handler via fail("...")
if (isTaggedError(input, "CliError")) {
if (typeof input.exitCode === "number") process.exitCode = input.exitCode
return stringField(input, "message") ?? ""
}
// MCPFailed: { name: string }
if (NamedError.hasName(input, "MCPFailed")) {
const data = isRecord(input) && isRecord(input.data) ? stringField(input.data, "name") : undefined
return `MCP server "${data}" failed. Note, opencode does not support MCP authentication yet.`
}
// AccountServiceError, AccountTransportError: TaggedErrorClass
if (isTaggedError(input, "AccountServiceError") || isTaggedError(input, "AccountTransportError")) {
return stringField(input, "message") ?? ""
}
// ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
const providerModelNotFound = configData(input, "ProviderModelNotFoundError")
if (providerModelNotFound) {
const suggestions = Array.isArray(providerModelNotFound.suggestions)
? providerModelNotFound.suggestions.filter((x) => typeof x === "string")
: []
return [
`Model not found: ${stringField(providerModelNotFound, "providerID")}/${stringField(providerModelNotFound, "modelID")}`,
...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
`Try: \`opencode models\` to list available models`,
`Or check your config (opencode.json) provider/model names`,
].join("\n")
}
// ProviderInitError: { providerID: string }
const providerInit = configData(input, "ProviderInitError")
if (providerInit) {
return `Failed to initialize provider "${stringField(providerInit, "providerID")}". Check credentials and configuration.`
}
// ConfigJsonError: { path: string, message?: string }
const configJson = configData(input, "ConfigJsonError")
if (configJson) {
const message = stringField(configJson, "message")
return `Config file at ${stringField(configJson, "path")} is not valid JSON(C)` + (message ? `: ${message}` : "")
}
// ConfigDirectoryTypoError: { dir: string, path: string, suggestion: string }
const configDirectoryTypo = configData(input, "ConfigDirectoryTypoError")
if (configDirectoryTypo) {
return `Directory "${stringField(configDirectoryTypo, "dir")}" in ${stringField(configDirectoryTypo, "path")} is not valid. Rename the directory to "${stringField(configDirectoryTypo, "suggestion")}" or remove it. This is a common typo.`
}
// ConfigFrontmatterError: { message: string }
const configFrontmatter = configData(input, "ConfigFrontmatterError")
if (configFrontmatter) {
return stringField(configFrontmatter, "message") ?? ""
}
// ConfigRemoteAuthError: { url: string, remote: string }
const remoteAuth = configData(input, "ConfigRemoteAuthError")
if (remoteAuth) {
const url = stringField(remoteAuth, "url")
const remote = stringField(remoteAuth, "remote")
return [
`Failed to load remote config${remote ? ` from ${remote}` : ""}: the server returned a login page instead of JSON.`,
`Authentication is missing or has expired (the endpoint is likely behind an SSO or identity-aware proxy).`,
...(url ? [`Run \`opencode auth login ${url}\` to re-authenticate.`] : []),
].join("\n")
}
// ConfigInvalidError: { path?: string, message?: string, issues?: Array<{ message: string, path: string[] }> }
const configInvalid = configData(input, "ConfigInvalidError")
if (configInvalid) {
const path = stringField(configInvalid, "path")
const message = stringField(configInvalid, "message")
const issues = configIssues(configInvalid)
return [
`Configuration is invalid${path && path !== "config" ? ` at ${path}` : ""}` + (message ? `: ${message}` : ""),
...issues.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")),
].join("\n")
}
// UICancelledError: user cancelled an interactive CLI prompt
if (isTaggedError(input, "UICancelledError") || NamedError.hasName(input, "UICancelledError")) {
return ""
}
return undefined
}
export function FormatUnknownError(input: unknown): string {
return errorFormat(input)
}