Skip to content

Commit 3c1140f

Browse files
author
Claude Agent
committed
fix: adapt multi-account features to new modular settings structure
- Move auth endpoints to provider routes - Add delete account functionality to dialog-auth-usage - Fix duplicate imports in layout.tsx - Fix message-v2.ts convertToModelMessages call - Regenerate SDK types
1 parent b541aca commit 3c1140f

File tree

6 files changed

+612
-206
lines changed

6 files changed

+612
-206
lines changed

packages/app/src/components/dialog-auth-usage.tsx

Lines changed: 125 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Dialog } from "@opencode-ai/ui/dialog"
22
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
33
import type { IconName } from "@opencode-ai/ui/icons/provider"
4+
import { Icon } from "@opencode-ai/ui/icon"
45
import { Spinner } from "@opencode-ai/ui/spinner"
56
import { createResource, For, Show, createMemo, createSignal } from "solid-js"
67
import { useGlobalSDK } from "@/context/global-sdk"
8+
import { usePlatform } from "@/context/platform"
79

810
interface AccountUsage {
911
id: string
@@ -73,7 +75,12 @@ function UsageBarPercent(props: { label: string; utilization: number; resetsAt?:
7375

7476
export function DialogAuthUsage() {
7577
const globalSDK = useGlobalSDK()
78+
const platform = usePlatform()
7679
const [switching, setSwitching] = createSignal<string | null>(null)
80+
const [deleting, setDeleting] = createSignal<string | null>(null)
81+
const [confirmDelete, setConfirmDelete] = createSignal<string | null>(null)
82+
83+
const doFetch = platform.fetch ?? fetch
7784

7885
const [usage, { refetch, mutate }] = createResource(async () => {
7986
const result = await globalSDK.client.auth.usage({})
@@ -89,13 +96,13 @@ export function DialogAuthUsage() {
8996
const switchAccount = async (providerID: string, recordID: string) => {
9097
setSwitching(recordID)
9198
try {
92-
const result = await fetch(`${globalSDK.url}/auth/active`, {
99+
const result = await doFetch(`${globalSDK.url}/provider/auth/active`, {
93100
method: "POST",
94101
headers: { "Content-Type": "application/json" },
95102
body: JSON.stringify({ providerID, recordID }),
96103
}).then((r) => r.json())
97104

98-
if (result.success) {
105+
if (result) {
99106
const current = usage()
100107
if (current && current[providerID]) {
101108
mutate({
@@ -106,7 +113,6 @@ export function DialogAuthUsage() {
106113
...acc,
107114
isActive: acc.id === recordID,
108115
})),
109-
anthropicUsage: result.anthropicUsage ?? current[providerID].anthropicUsage,
110116
},
111117
})
112118
}
@@ -116,6 +122,45 @@ export function DialogAuthUsage() {
116122
}
117123
}
118124

125+
const deleteAccount = async (providerID: string, recordID: string) => {
126+
setDeleting(recordID)
127+
try {
128+
const result = await doFetch(`${globalSDK.url}/provider/auth/account`, {
129+
method: "DELETE",
130+
headers: { "Content-Type": "application/json" },
131+
body: JSON.stringify({ providerID, recordID }),
132+
}).then((r) => r.json())
133+
134+
if (result.success) {
135+
if (result.remaining === 0) {
136+
// Provider was disconnected, refresh to remove from list
137+
await refetch()
138+
} else {
139+
// Update local state
140+
const current = usage()
141+
if (current && current[providerID]) {
142+
const remaining = current[providerID].accounts.filter((acc) => acc.id !== recordID)
143+
// If deleted account was active, mark first remaining as active
144+
const hadActive = current[providerID].accounts.find((acc) => acc.id === recordID)?.isActive
145+
if (hadActive && remaining.length > 0) {
146+
remaining[0].isActive = true
147+
}
148+
mutate({
149+
...current,
150+
[providerID]: {
151+
...current[providerID],
152+
accounts: remaining,
153+
},
154+
})
155+
}
156+
}
157+
}
158+
} finally {
159+
setDeleting(null)
160+
setConfirmDelete(null)
161+
}
162+
}
163+
119164
return (
120165
<Dialog title="Rate Limits & Usage">
121166
<div class="flex flex-col gap-6 px-4 pb-4 min-w-[420px]">
@@ -198,51 +243,96 @@ export function DialogAuthUsage() {
198243
return secs > 60 ? `${Math.ceil(secs / 60)}m` : `${secs}s`
199244
}
200245
const isSwitching = () => switching() === account.id
246+
const isDeleting = () => deleting() === account.id
247+
const isConfirming = () => confirmDelete() === account.id
201248
const canSwitch = () => info.accounts.length > 1 && !account.isActive && !isSwitching()
202249

203250
return (
204-
<button
205-
type="button"
206-
disabled={!canSwitch() && !account.isActive}
207-
onClick={() => canSwitch() && switchAccount(providerID, account.id)}
208-
class="flex flex-col gap-2 p-3 rounded-lg text-left transition-all"
251+
<div
252+
class="flex items-center gap-2 p-3 rounded-lg transition-all"
209253
classList={{
210-
"bg-fill-ghost-base hover:bg-fill-ghost-strong cursor-pointer": canSwitch(),
211-
"bg-fill-ghost-base opacity-60": !canSwitch() && !account.isActive,
254+
"bg-fill-ghost-base": !account.isActive,
212255
"bg-fill-success-ghost border border-fill-success-base": account.isActive,
213256
}}
214257
>
215-
<div class="flex justify-between items-center w-full">
216-
<div class="flex items-center gap-2">
217-
<Show when={isSwitching()}>
218-
<Spinner class="size-3" />
219-
</Show>
220-
<span class="text-13-medium text-text-base">
221-
Account {index() + 1}
222-
<Show when={account.label && account.label !== "default"}>
223-
<span class="text-text-muted"> ({account.label})</span>
258+
<button
259+
type="button"
260+
disabled={!canSwitch() && !account.isActive}
261+
onClick={() => canSwitch() && switchAccount(providerID, account.id)}
262+
class="flex-1 flex flex-col gap-2 text-left transition-all"
263+
classList={{
264+
"hover:opacity-80 cursor-pointer": canSwitch(),
265+
"opacity-60": !canSwitch() && !account.isActive,
266+
}}
267+
>
268+
<div class="flex justify-between items-center w-full">
269+
<div class="flex items-center gap-2">
270+
<Show when={isSwitching()}>
271+
<Spinner class="size-3" />
224272
</Show>
225-
</span>
226-
<Show when={account.isActive}>
227-
<span class="text-10-medium text-fill-success-base bg-fill-success-ghost px-1.5 py-0.5 rounded">
228-
Active
229-
</span>
230-
</Show>
231-
<Show when={isInCooldown()}>
232-
<span class="text-10-medium text-fill-danger-base bg-fill-danger-ghost px-1.5 py-0.5 rounded">
233-
Cooldown {cooldownRemaining()}
273+
<span class="text-13-medium text-text-base">
274+
Account {index() + 1}
275+
<Show when={account.label && account.label !== "default"}>
276+
<span class="text-text-muted"> ({account.label})</span>
277+
</Show>
234278
</span>
235-
</Show>
279+
<Show when={account.isActive}>
280+
<span class="text-10-medium text-fill-success-base bg-fill-success-ghost px-1.5 py-0.5 rounded">
281+
Active
282+
</span>
283+
</Show>
284+
<Show when={isInCooldown()}>
285+
<span class="text-10-medium text-fill-danger-base bg-fill-danger-ghost px-1.5 py-0.5 rounded">
286+
Cooldown {cooldownRemaining()}
287+
</span>
288+
</Show>
289+
</div>
290+
<span class="text-11-regular text-text-muted">{account.health.successCount} requests</span>
236291
</div>
237-
<span class="text-11-regular text-text-muted">{account.health.successCount} requests</span>
238-
</div>
239292

240-
<Show when={account.health.failureCount > 0}>
241-
<div class="text-11-regular text-fill-danger-base">
242-
{account.health.failureCount} failed requests
293+
<Show when={account.health.failureCount > 0}>
294+
<div class="text-11-regular text-fill-danger-base">
295+
{account.health.failureCount} failed requests
296+
</div>
297+
</Show>
298+
</button>
299+
300+
{/* Delete button */}
301+
<Show when={isConfirming()}>
302+
<div class="flex items-center gap-1">
303+
<button
304+
type="button"
305+
onClick={() => deleteAccount(providerID, account.id)}
306+
disabled={isDeleting()}
307+
class="px-2 py-1 rounded text-10-medium bg-fill-danger-base text-white hover:bg-fill-danger-strong transition-colors disabled:opacity-50"
308+
>
309+
<Show when={isDeleting()} fallback="Confirm">
310+
<Spinner class="size-3" />
311+
</Show>
312+
</button>
313+
<button
314+
type="button"
315+
onClick={() => setConfirmDelete(null)}
316+
class="px-2 py-1 rounded text-10-medium bg-fill-ghost-strong text-text-base hover:bg-fill-ghost-base transition-colors"
317+
>
318+
Cancel
319+
</button>
243320
</div>
244321
</Show>
245-
</button>
322+
<Show when={!isConfirming()}>
323+
<button
324+
type="button"
325+
onClick={(e) => {
326+
e.stopPropagation()
327+
setConfirmDelete(account.id)
328+
}}
329+
class="p-1 rounded hover:bg-fill-danger-ghost text-icon-muted hover:text-fill-danger-base transition-colors"
330+
title="Remove account"
331+
>
332+
<Icon name="close" class="size-4" />
333+
</button>
334+
</Show>
335+
</div>
246336
)
247337
}}
248338
</For>

packages/app/src/pages/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
6262
import { DialogSelectProvider } from "@/components/dialog-select-provider"
6363
import { DialogSelectServer } from "@/components/dialog-select-server"
6464
import { DialogSettings } from "@/components/dialog-settings"
65+
import { DialogAuthUsage } from "@/components/dialog-auth-usage"
6566
import { useCommand, type CommandOption } from "@/context/command"
6667
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
6768
import { navStart } from "@/utils/perf"
6869
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
6970
import { DialogEditProject } from "@/components/dialog-edit-project"
70-
import { DialogSettings } from "@/components/dialog-settings"
7171
import { Titlebar } from "@/components/titlebar"
7272
import { useServer } from "@/context/server"
7373
import { useLanguage, type Locale } from "@/context/language"

packages/opencode/src/server/routes/provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ export const ProviderRoutes = lazy(() =>
266266
),
267267
async (c) => {
268268
const { providerID, recordID } = c.req.valid("json")
269-
const remaining = await Auth.OAuthPool.deleteRecord(providerID, recordID)
270-
return c.json({ success: true, remaining })
269+
const result = await Auth.OAuthPool.removeRecord(providerID, recordID)
270+
return c.json({ success: result.removed, remaining: result.remaining })
271271
},
272272
),
273273
)

packages/opencode/src/session/message-v2.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,7 @@ export namespace MessageV2 {
566566
}
567567
}
568568

569-
return convertToModelMessages(
570-
result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")),
571-
{ tools: options?.tools },
572-
)
569+
return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")))
573570
}
574571

575572
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {

0 commit comments

Comments
 (0)