summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-15 23:50:47 -0400
committerGitHub <[email protected]>2026-04-16 03:50:47 +0000
commit8aa0f9fe9515ba0234ab6a0a58c868068913bb05 (patch)
treed079fdd75796492449157256dafe7e309e04da63 /packages
parentc802695ee9555ccfd8b0a6ae2215f750bccda712 (diff)
downloadopencode-8aa0f9fe9515ba0234ab6a0a58c868068913bb05.tar.gz
opencode-8aa0f9fe9515ba0234ab6a0a58c868068913bb05.zip
feat: enable type-aware no-base-to-string rule, fix 56 violations (#22750)
Diffstat (limited to 'packages')
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx22
-rw-r--r--packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx8
-rw-r--r--packages/console/app/src/routes/workspace/[id]/members/member-section.tsx22
-rw-r--r--packages/console/app/src/routes/workspace/[id]/model-section.tsx8
-rw-r--r--packages/console/app/src/routes/workspace/[id]/provider-section.tsx17
-rw-r--r--packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx4
-rwxr-xr-xpackages/opencode/script/schema.ts2
-rw-r--r--packages/opencode/src/effect/logger.ts2
-rw-r--r--packages/opencode/src/plugin/github-copilot/copilot.ts2
-rw-r--r--packages/opencode/src/provider/transform.ts4
-rw-r--r--packages/opencode/src/pty/service.ts2
-rw-r--r--packages/opencode/src/snapshot/snapshot.ts2
-rw-r--r--packages/opencode/src/tool/apply_patch.ts10
-rw-r--r--packages/opencode/src/util/error.ts1
-rw-r--r--packages/opencode/src/v2/session-entry.ts1
-rw-r--r--packages/opencode/test/config/config.test.ts4
-rw-r--r--packages/shared/src/util/retry.ts1
-rw-r--r--packages/shared/test/util/effect-flock.test.ts1
-rw-r--r--packages/storybook/.storybook/mocks/app/context/language.ts1
-rw-r--r--packages/ui/src/components/file.tsx3
-rw-r--r--packages/ui/src/components/session-turn.tsx1
-rw-r--r--packages/web/src/components/share/part.tsx8
25 files changed, 83 insertions, 55 deletions
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
index b8f089864..5b4389466 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setUseBalance = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const useBalance = form.get("useBalance")?.toString() === "true"
+ const useBalance = (form.get("useBalance") as string | null) === "true"
return json(
await withActor(async () => {
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
index ef54b8409..7da1de238 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
const setMonthlyLimit = action(async (form: FormData) => {
"use server"
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
if (!limit) return { error: formError.limitRequired }
const numericLimit = parseInt(limit)
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
index a25963ab0..c9a72c087 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
const reload = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Billing.reload(), workspaceID), {
revalidate: queryBillingInfo.key,
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
const setReload = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const reloadValue = form.get("reload")?.toString() === "true"
- const amountStr = form.get("reloadAmount")?.toString()
- const triggerStr = form.get("reloadTrigger")?.toString()
+ const reloadValue = (form.get("reload") as string | null) === "true"
+ const amountStr = form.get("reloadAmount") as string | null
+ const triggerStr = form.get("reloadTrigger") as string | null
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
@@ -91,8 +91,8 @@ export function ReloadSection() {
const info = billingInfo()!
setStore("show", true)
setStore("reload", true)
- setStore("reloadAmount", info.reloadAmount.toString())
- setStore("reloadTrigger", info.reloadTrigger.toString())
+ setStore("reloadAmount", String(info.reloadAmount))
+ setStore("reloadTrigger", String(info.reloadTrigger))
}
function hide() {
@@ -152,11 +152,11 @@ export function ReloadSection() {
data-component="input"
name="reloadAmount"
type="number"
- min={billingInfo()?.reloadAmountMin.toString()}
+ min={String(billingInfo()?.reloadAmountMin ?? "")}
step="1"
value={store.reloadAmount}
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
- placeholder={billingInfo()?.reloadAmount.toString()}
+ placeholder={String(billingInfo()?.reloadAmount ?? "")}
disabled={!store.reload}
/>
</div>
@@ -166,11 +166,11 @@ export function ReloadSection() {
data-component="input"
name="reloadTrigger"
type="number"
- min={billingInfo()?.reloadTriggerMin.toString()}
+ min={String(billingInfo()?.reloadTriggerMin ?? "")}
step="1"
value={store.reloadTrigger}
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
- placeholder={billingInfo()?.reloadTrigger.toString()}
+ placeholder={String(billingInfo()?.reloadTrigger ?? "")}
disabled={!store.reload}
/>
</div>
diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
index 95ff7af2b..d0f812182 100644
--- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const useBalance = form.get("useBalance")?.toString() === "true"
+ const useBalance = (form.get("useBalance") as string | null) === "true"
return json(
await withActor(async () => {
diff --git a/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx b/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
index 837ab743a..cb273a422 100644
--- a/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
const removeKey = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
}, "key.remove")
const createKey = action(async (form: FormData) => {
"use server"
- const name = form.get("name")?.toString().trim()
+ const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.nameRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
index 5a440632f..00edb400c 100644
--- a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
const inviteMember = action(async (form: FormData) => {
"use server"
- const email = form.get("email")?.toString().trim()
+ const email = (form.get("email") as string | null)?.trim()
if (!email) return { error: formError.emailRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const role = form.get("role")?.toString() as (typeof UserRole)[number]
+ const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired }
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
return json(
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
const removeMember = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
const updateMember = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const role = form.get("role")?.toString() as (typeof UserRole)[number]
+ const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired }
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
@@ -118,7 +118,7 @@ function MemberRow(props: {
}
setStore("editing", true)
setStore("selectedRole", props.member.role)
- setStore("limit", props.member.monthlyLimit?.toString() ?? "")
+ setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
}
function hide() {
diff --git a/packages/console/app/src/routes/workspace/[id]/model-section.tsx b/packages/console/app/src/routes/workspace/[id]/model-section.tsx
index bf19f81cd..b9cdf3bc3 100644
--- a/packages/console/app/src/routes/workspace/[id]/model-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/model-section.tsx
@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
const updateModel = action(async (form: FormData) => {
"use server"
- const model = form.get("model")?.toString()
+ const model = form.get("model") as string | null
if (!model) return { error: formError.modelRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const enabled = form.get("enabled")?.toString() === "true"
+ const enabled = (form.get("enabled") as string | null) === "true"
return json(
withActor(async () => {
if (enabled) {
@@ -163,7 +163,7 @@ export function ModelSection() {
<form action={updateModel} method="post">
<input type="hidden" name="model" value={id} />
<input type="hidden" name="workspaceID" value={params.id} />
- <input type="hidden" name="enabled" value={isEnabled().toString()} />
+ <input type="hidden" name="enabled" value={String(isEnabled())} />
<label data-slot="model-toggle-label">
<input
type="checkbox"
diff --git a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
index fe04bf7c0..acdd89742 100644
--- a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
const removeProvider = action(async (form: FormData) => {
"use server"
- const provider = form.get("provider")?.toString()
+ const provider = form.get("provider") as string | null
if (!provider) return { error: formError.providerRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
revalidate: listProviders.key,
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
const saveProvider = action(async (form: FormData) => {
"use server"
- const provider = form.get("provider")?.toString()
- const credentials = form.get("credentials")?.toString()
+ const provider = form.get("provider") as string | null
+ const credentials = form.get("credentials") as string | null
if (!provider) return { error: formError.providerRequired }
if (!credentials) return { error: formError.apiKeyRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
const params = useParams()
const i18n = useI18n()
const providers = createAsync(() => listProviders(params.id!))
- const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
+ const saveSubmission = useSubmission(
+ saveProvider,
+ ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
+ )
const removeSubmission = useSubmission(
removeProvider,
- ([fd]) => fd.get("provider")?.toString() === props.provider.key,
+ ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
)
const [store, setStore] = createStore({ editing: false })
diff --git a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
index 5e8331e26..a9950c41e 100644
--- a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
const updateWorkspace = action(async (form: FormData) => {
"use server"
- const name = form.get("name")?.toString().trim()
+ const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.workspaceNameRequired }
if (name.length > 255) return { error: formError.nameTooLong }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/opencode/script/schema.ts b/packages/opencode/script/schema.ts
index 4aa27423b..cf63d6743 100755
--- a/packages/opencode/script/schema.ts
+++ b/packages/opencode/script/schema.ts
@@ -33,7 +33,7 @@ function generate(schema: z.ZodType) {
schema.examples = [schema.default]
}
- schema.description = [schema.description || "", `default: \`${schema.default}\``]
+ schema.description = [schema.description || "", `default: \`${String(schema.default)}\``]
.filter(Boolean)
.join("\n\n")
.trim()
diff --git a/packages/opencode/src/effect/logger.ts b/packages/opencode/src/effect/logger.ts
index 97b614fc0..21e0fc43a 100644
--- a/packages/opencode/src/effect/logger.ts
+++ b/packages/opencode/src/effect/logger.ts
@@ -15,7 +15,9 @@ const clean = (input?: Fields): Fields =>
Object.fromEntries(Object.entries(input ?? {}).filter((entry) => entry[1] !== undefined && entry[1] !== null))
const text = (input: unknown): string => {
+ // oxlint-disable-next-line no-base-to-string
if (Array.isArray(input)) return input.map((item) => String(item)).join(" ")
+ // oxlint-disable-next-line no-base-to-string
return input === undefined ? "" : String(input)
}
diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts
index eeea21924..c1318287c 100644
--- a/packages/opencode/src/plugin/github-copilot/copilot.ts
+++ b/packages/opencode/src/plugin/github-copilot/copilot.ts
@@ -93,7 +93,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
const info = await getAuth()
if (info.type !== "oauth") return fetch(request, init)
- const url = request instanceof URL ? request.href : request.toString()
+ const url = request instanceof URL ? request.href : typeof request === "string" ? request : request.url
const { isVision, isAgent } = iife(() => {
try {
const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body
diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index a0d3e2258..5fa39441c 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -274,7 +274,7 @@ export namespace ProviderTransform {
// Check for empty base64 image data
if (part.type === "image") {
- const imageStr = part.image.toString()
+ const imageStr = String(part.image)
if (imageStr.startsWith("data:")) {
const match = imageStr.match(/^data:([^;]+);base64,(.*)$/)
if (match && (!match[2] || match[2].length === 0)) {
@@ -286,7 +286,7 @@ export namespace ProviderTransform {
}
}
- const mime = part.type === "image" ? part.image.toString().split(";")[0].replace("data:", "") : part.mediaType
+ const mime = part.type === "image" ? String(part.image).split(";")[0].replace("data:", "") : part.mediaType
const filename = part.type === "file" ? part.filename : undefined
const modality = mimeToModality(mime)
if (!modality) return part
diff --git a/packages/opencode/src/pty/service.ts b/packages/opencode/src/pty/service.ts
index a4ebd0696..0c810be88 100644
--- a/packages/opencode/src/pty/service.ts
+++ b/packages/opencode/src/pty/service.ts
@@ -346,7 +346,7 @@ export const layer = Layer.effect(
return {
onMessage: (message: string | ArrayBuffer) => {
- session.process.write(String(message))
+ session.process.write(typeof message === "string" ? message : new TextDecoder().decode(message))
},
onClose: () => {
log.info("client disconnected from session", { id })
diff --git a/packages/opencode/src/snapshot/snapshot.ts b/packages/opencode/src/snapshot/snapshot.ts
index 8d8118131..7a5c0a4dc 100644
--- a/packages/opencode/src/snapshot/snapshot.ts
+++ b/packages/opencode/src/snapshot/snapshot.ts
@@ -116,7 +116,7 @@ export const layer: Layer.Layer<
Effect.succeed({
code: ChildProcessSpawner.ExitCode(1),
text: "",
- stderr: String(err),
+ stderr: err instanceof Error ? err.message : String(err),
}),
),
)
diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts
index b9877d8fe..2d8a50101 100644
--- a/packages/opencode/src/tool/apply_patch.ts
+++ b/packages/opencode/src/tool/apply_patch.ts
@@ -145,7 +145,15 @@ export const ApplyPatchTool = Tool.define(
case "delete": {
const contentToDelete = yield* afs
.readFileString(filePath)
- .pipe(Effect.catch((error) => Effect.fail(new Error(`apply_patch verification failed: ${error}`))))
+ .pipe(
+ Effect.catch((error) =>
+ Effect.fail(
+ new Error(
+ `apply_patch verification failed: ${error instanceof Error ? error.message : String(error)}`,
+ ),
+ ),
+ ),
+ )
const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, ""))
const deletions = contentToDelete.split("\n").length
diff --git a/packages/opencode/src/util/error.ts b/packages/opencode/src/util/error.ts
index ea1c79178..75fef9fc9 100644
--- a/packages/opencode/src/util/error.ts
+++ b/packages/opencode/src/util/error.ts
@@ -60,6 +60,7 @@ export function errorData(error: unknown) {
acc[key] = value
return acc
}
+ // oxlint-disable-next-line no-base-to-string -- intentional coercion of arbitrary error properties
acc[key] = value instanceof Error ? value.message : String(value)
return acc
}, {})
diff --git a/packages/opencode/src/v2/session-entry.ts b/packages/opencode/src/v2/session-entry.ts
index a60d8c6c7..490f18422 100644
--- a/packages/opencode/src/v2/session-entry.ts
+++ b/packages/opencode/src/v2/session-entry.ts
@@ -202,6 +202,7 @@ export namespace SessionEntry {
case "tool.input.delta": {
if (!pendingAssistant) break
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
+ // oxlint-disable-next-line no-base-to-string -- event.delta is a Schema.String (runtime string)
if (match) match.state.input += event.delta
break
}
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index bc9fe5b01..6a79cf644 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -1801,7 +1801,7 @@ test("project config overrides remote well-known config", async () => {
const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => {
- const urlStr = url.toString()
+ const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr
return Promise.resolve(
@@ -1858,7 +1858,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => {
- const urlStr = url.toString()
+ const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr
return Promise.resolve(
diff --git a/packages/shared/src/util/retry.ts b/packages/shared/src/util/retry.ts
index 0014a604c..831d23800 100644
--- a/packages/shared/src/util/retry.ts
+++ b/packages/shared/src/util/retry.ts
@@ -19,6 +19,7 @@ const TRANSIENT_MESSAGES = [
function isTransientError(error: unknown): boolean {
if (!error) return false
+ // oxlint-disable-next-line no-base-to-string -- error is unknown, intentional coercion for message matching
const message = String(error instanceof Error ? error.message : error).toLowerCase()
return TRANSIENT_MESSAGES.some((m) => message.includes(m))
}
diff --git a/packages/shared/test/util/effect-flock.test.ts b/packages/shared/test/util/effect-flock.test.ts
index 6e094c2e1..bd71e4f02 100644
--- a/packages/shared/test/util/effect-flock.test.ts
+++ b/packages/shared/test/util/effect-flock.test.ts
@@ -317,6 +317,7 @@ describe("util.effect-flock", () => {
})
const result = yield* flock.withLock(Effect.void, "eflock:perm", dir).pipe(Effect.exit)
+ // oxlint-disable-next-line no-base-to-string -- Exit has a useful toString for test assertions
expect(String(result)).toContain("PermissionDenied")
yield* Effect.promise(() => fs.chmod(dir, 0o700).then(() => fs.rm(tmp, { recursive: true, force: true })))
}),
diff --git a/packages/storybook/.storybook/mocks/app/context/language.ts b/packages/storybook/.storybook/mocks/app/context/language.ts
index f75240b9c..c3317ca2e 100644
--- a/packages/storybook/.storybook/mocks/app/context/language.ts
+++ b/packages/storybook/.storybook/mocks/app/context/language.ts
@@ -60,6 +60,7 @@ function render(template: string, params?: Record<string, unknown>) {
return template.replace(/\{\{([^}]+)\}\}/g, (_, key: string) => {
const value = params[key.trim()]
if (value === undefined || value === null) return ""
+ // oxlint-disable-next-line no-base-to-string -- value is Record<string, unknown>, always coerced intentionally
return String(value)
})
}
diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx
index fd902b2e0..633b23b70 100644
--- a/packages/ui/src/components/file.tsx
+++ b/packages/ui/src/components/file.tsx
@@ -698,6 +698,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value
if (Array.isArray(value)) return value.join("\n")
if (value == null) return ""
+ // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value)
}
@@ -712,11 +713,13 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value.length
if (Array.isArray(value)) {
return value.reduce(
+ // oxlint-disable-next-line no-base-to-string -- array parts coerced intentionally
(sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
0,
)
}
if (value == null) return 0
+ // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value).length
})
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 2e5f9e38b..75279a90e 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -313,6 +313,7 @@ export function SessionTurn(
const msg = error()?.data?.message
if (typeof msg === "string") return unwrap(msg)
if (msg === undefined || msg === null) return ""
+ // oxlint-disable-next-line no-base-to-string -- msg is unknown from error data, coercion is intentional
return unwrap(String(msg))
})
diff --git a/packages/web/src/components/share/part.tsx b/packages/web/src/components/share/part.tsx
index c7d177df7..34cfe3f42 100644
--- a/packages/web/src/components/share/part.tsx
+++ b/packages/web/src/components/share/part.tsx
@@ -730,7 +730,13 @@ export function FallbackTool(props: ToolProps) {
<>
<div></div>
<div>{arg[0]}</div>
- <div>{String(arg[1] ?? "")}</div>
+ <div>
+ {typeof arg[1] === "string" || typeof arg[1] === "number" || typeof arg[1] === "boolean"
+ ? String(arg[1])
+ : arg[1] == null
+ ? ""
+ : JSON.stringify(arg[1])}
+ </div>
</>
)}
</For>