opencode-auth-load-balancer 0.5.1 → 0.7.0
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/README.md +152 -12
- package/dist/accounts.d.ts +22 -2
- package/dist/index.d.ts +7 -12
- package/dist/index.js +46026 -14563
- package/dist/legacy.d.ts +25 -0
- package/dist/login-health.d.ts +12 -0
- package/dist/notify.d.ts +6 -0
- package/dist/pairing.d.ts +22 -0
- package/dist/pending/coordinator.d.ts +5 -1
- package/dist/pending/v2.d.ts +3 -0
- package/dist/pool/paths.d.ts +8 -0
- package/dist/providers/anthropic/constants.d.ts +21 -0
- package/dist/providers/anthropic/oauth.d.ts +9 -0
- package/dist/providers/anthropic/setup-token.d.ts +14 -0
- package/dist/providers/anthropic/usage.d.ts +13 -0
- package/dist/providers/types.d.ts +27 -0
- package/dist/refresh.d.ts +12 -0
- package/dist/status.d.ts +6 -0
- package/dist/sync/apply.d.ts +66 -0
- package/dist/sync/crypto.d.ts +19 -0
- package/dist/sync/cycle.d.ts +19 -0
- package/dist/sync/engine.d.ts +44 -0
- package/dist/sync/errors.d.ts +9 -0
- package/dist/sync/gist.d.ts +33 -0
- package/dist/sync/github-auth.d.ts +15 -0
- package/dist/sync/intent.d.ts +29 -0
- package/dist/sync/loop.d.ts +40 -0
- package/dist/sync/merge.d.ts +49 -0
- package/dist/sync/origin.d.ts +2 -0
- package/dist/sync/own.d.ts +31 -0
- package/dist/sync/payload.d.ts +50 -0
- package/dist/sync/start.d.ts +5 -0
- package/dist/sync/state.d.ts +79 -0
- package/dist/sync/timing.d.ts +4 -0
- package/dist/token-mint.d.ts +25 -0
- package/dist/types.d.ts +75 -0
- package/dist/util.d.ts +7 -0
- package/dist/v1.d.ts +1 -0
- package/dist/v1.js +18393 -0
- package/dist/v2-rpc.d.ts +62 -0
- package/dist/v2.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +46 -8
- package/tui/auth-load-balancer-rpc.ts +60 -0
- package/tui/auth-load-balancer-tui.logic.ts +363 -7
- package/tui/auth-load-balancer-tui.view.tsx +299 -107
- package/tui/auth-load-balancer-v2.ts +10 -0
- package/tui/auth-load-balancer-v2.view.tsx +242 -0
- package/tui.ts +2 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { Plugin } from '@opencode/plugin/tui'
|
|
3
|
+
import { createSignal, For, onCleanup, Show } from 'solid-js'
|
|
4
|
+
|
|
5
|
+
import { loadBalancerRpc, type ProviderStatus } from './auth-load-balancer-rpc'
|
|
6
|
+
|
|
7
|
+
const names: Record<string, string> = {
|
|
8
|
+
anthropic: 'Claude',
|
|
9
|
+
openai: 'Codex',
|
|
10
|
+
'kimi-code-plan-cn': 'Kimi',
|
|
11
|
+
'kimi-code-plan-global': 'Kimi (kimi.ai)',
|
|
12
|
+
}
|
|
13
|
+
const pct = (value: number | null) =>
|
|
14
|
+
value === null ? '?' : `${Math.round(value * 100)}%`
|
|
15
|
+
|
|
16
|
+
function StatusView(props: {
|
|
17
|
+
ctx: Plugin.Context
|
|
18
|
+
sessionID?: string
|
|
19
|
+
compact?: boolean
|
|
20
|
+
}) {
|
|
21
|
+
const rpc = props.ctx.client.rpc(loadBalancerRpc)
|
|
22
|
+
const [providers, setProviders] = createSignal<ProviderStatus[]>([])
|
|
23
|
+
const [error, setError] = createSignal('')
|
|
24
|
+
let disposed = false
|
|
25
|
+
const [refreshing, setRefreshing] = createSignal(false)
|
|
26
|
+
const refresh = async () => {
|
|
27
|
+
if (refreshing()) return
|
|
28
|
+
setRefreshing(true)
|
|
29
|
+
try {
|
|
30
|
+
const result = await rpc.status(
|
|
31
|
+
{ sessionID: props.sessionID },
|
|
32
|
+
{ location: props.ctx.location },
|
|
33
|
+
)
|
|
34
|
+
if (!disposed) {
|
|
35
|
+
setProviders(result)
|
|
36
|
+
setError('')
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
if (!disposed) setError('Auth Load Balancer unavailable')
|
|
40
|
+
} finally {
|
|
41
|
+
setRefreshing(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const timer = setInterval(() => void refresh(), 3000)
|
|
45
|
+
onCleanup(() => {
|
|
46
|
+
disposed = true
|
|
47
|
+
clearInterval(timer)
|
|
48
|
+
})
|
|
49
|
+
void refresh()
|
|
50
|
+
|
|
51
|
+
const login = async (
|
|
52
|
+
provider: ProviderStatus,
|
|
53
|
+
account: ProviderStatus['accounts'][number],
|
|
54
|
+
) => {
|
|
55
|
+
const result = await props.ctx.client.integration.get({
|
|
56
|
+
integrationID: provider.providerID,
|
|
57
|
+
location: props.ctx.location,
|
|
58
|
+
})
|
|
59
|
+
const methods = result.data.methods.flatMap((method) =>
|
|
60
|
+
method.type === 'oauth' && method.id.startsWith('auth-lb-')
|
|
61
|
+
? [method]
|
|
62
|
+
: [],
|
|
63
|
+
)
|
|
64
|
+
const methodID = await props.ctx.ui.dialog.select({
|
|
65
|
+
title: `Sign in to ${names[provider.providerID] ?? provider.providerID}`,
|
|
66
|
+
options: methods.map((method) => ({
|
|
67
|
+
title: method.label,
|
|
68
|
+
value: method.id,
|
|
69
|
+
})),
|
|
70
|
+
})
|
|
71
|
+
if (!methodID) return
|
|
72
|
+
await rpc.manage(
|
|
73
|
+
{ account: account.id, action: 'relogin' },
|
|
74
|
+
{ location: props.ctx.location },
|
|
75
|
+
)
|
|
76
|
+
let attemptID: string | undefined
|
|
77
|
+
try {
|
|
78
|
+
const { data } = await props.ctx.client.integration.oauth.connect({
|
|
79
|
+
integrationID: provider.providerID,
|
|
80
|
+
methodID,
|
|
81
|
+
location: props.ctx.location,
|
|
82
|
+
})
|
|
83
|
+
attemptID = data.attemptID
|
|
84
|
+
const code =
|
|
85
|
+
data.mode === 'code'
|
|
86
|
+
? await props.ctx.ui.dialog.prompt({
|
|
87
|
+
title: 'Authorize account',
|
|
88
|
+
description: `${data.url}\n${data.instructions}`,
|
|
89
|
+
})
|
|
90
|
+
: await props.ctx.ui.dialog.confirm({
|
|
91
|
+
title: 'Authorize account',
|
|
92
|
+
message: `${data.url}\n${data.instructions}\nContinue after approving in your browser.`,
|
|
93
|
+
})
|
|
94
|
+
if (code === undefined || code === false) return
|
|
95
|
+
await props.ctx.client.integration.oauth.complete({
|
|
96
|
+
integrationID: provider.providerID,
|
|
97
|
+
attemptID,
|
|
98
|
+
...(typeof code === 'string' ? { code } : {}),
|
|
99
|
+
location: props.ctx.location,
|
|
100
|
+
})
|
|
101
|
+
attemptID = undefined
|
|
102
|
+
props.ctx.ui.toast.show({
|
|
103
|
+
message: 'Account added to load balancer',
|
|
104
|
+
variant: 'success',
|
|
105
|
+
})
|
|
106
|
+
} finally {
|
|
107
|
+
if (attemptID)
|
|
108
|
+
await props.ctx.client.integration.oauth
|
|
109
|
+
.cancel({
|
|
110
|
+
integrationID: provider.providerID,
|
|
111
|
+
attemptID,
|
|
112
|
+
location: props.ctx.location,
|
|
113
|
+
})
|
|
114
|
+
.catch(() => {})
|
|
115
|
+
await rpc
|
|
116
|
+
.manage(
|
|
117
|
+
{ account: account.id, action: 'clear-relogin' },
|
|
118
|
+
{ location: props.ctx.location },
|
|
119
|
+
)
|
|
120
|
+
.catch(() => {})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const manage = async (
|
|
124
|
+
provider: ProviderStatus,
|
|
125
|
+
account: ProviderStatus['accounts'][number],
|
|
126
|
+
) => {
|
|
127
|
+
try {
|
|
128
|
+
const disabled = account.disabledReason === 'manually disabled'
|
|
129
|
+
const action = await props.ctx.ui.dialog.select({
|
|
130
|
+
title: account.label,
|
|
131
|
+
options: [
|
|
132
|
+
{ title: 'Rename', value: 'rename' as const },
|
|
133
|
+
{
|
|
134
|
+
title: disabled ? 'Enable' : 'Disable',
|
|
135
|
+
value: disabled ? ('enable' as const) : ('disable' as const),
|
|
136
|
+
},
|
|
137
|
+
{ title: 'Re-login / pair credential', value: 'relogin' as const },
|
|
138
|
+
{ title: 'Delete', value: 'delete' as const },
|
|
139
|
+
],
|
|
140
|
+
})
|
|
141
|
+
if (!action) return
|
|
142
|
+
if (action === 'relogin') {
|
|
143
|
+
await login(provider, account)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
const name =
|
|
147
|
+
action === 'rename'
|
|
148
|
+
? await props.ctx.ui.dialog.prompt({
|
|
149
|
+
title: 'Rename account',
|
|
150
|
+
value: account.label,
|
|
151
|
+
})
|
|
152
|
+
: undefined
|
|
153
|
+
if (action === 'rename' && name === undefined) return
|
|
154
|
+
if (
|
|
155
|
+
action === 'delete' &&
|
|
156
|
+
!(await props.ctx.ui.dialog.confirm({
|
|
157
|
+
title: 'Delete account',
|
|
158
|
+
message: `Remove ${account.label} from the pool?`,
|
|
159
|
+
}))
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
await rpc.manage(
|
|
163
|
+
{ account: account.id, action, name },
|
|
164
|
+
{ location: props.ctx.location },
|
|
165
|
+
)
|
|
166
|
+
} catch (cause) {
|
|
167
|
+
props.ctx.ui.toast.show({
|
|
168
|
+
message:
|
|
169
|
+
cause instanceof Error ? cause.message : 'Account update failed',
|
|
170
|
+
variant: 'error',
|
|
171
|
+
})
|
|
172
|
+
} finally {
|
|
173
|
+
await refresh()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<box flexDirection={props.compact ? 'row' : 'column'} gap={1}>
|
|
179
|
+
<Show when={error()}>
|
|
180
|
+
<text fg={props.ctx.theme.text.muted}>{error()}</text>
|
|
181
|
+
</Show>
|
|
182
|
+
<For each={providers()}>
|
|
183
|
+
{(provider) => (
|
|
184
|
+
<box flexDirection="column">
|
|
185
|
+
<Show when={!props.compact}>
|
|
186
|
+
<text fg={props.ctx.theme.text.base}>
|
|
187
|
+
<b>{names[provider.providerID] ?? provider.providerID}</b>
|
|
188
|
+
</text>
|
|
189
|
+
</Show>
|
|
190
|
+
<For
|
|
191
|
+
each={
|
|
192
|
+
props.compact
|
|
193
|
+
? provider.accounts.filter((account) => account.current)
|
|
194
|
+
: provider.accounts
|
|
195
|
+
}
|
|
196
|
+
>
|
|
197
|
+
{(account) => (
|
|
198
|
+
<box
|
|
199
|
+
flexDirection="column"
|
|
200
|
+
onMouseDown={() => void manage(provider, account)}
|
|
201
|
+
>
|
|
202
|
+
<text fg={props.ctx.theme.text.base} wrapMode="word">
|
|
203
|
+
{`${account.current ? '●' : `#${account.rank}`} ${props.compact ? `${names[provider.providerID] ?? provider.providerID}: ` : ''}${account.label} · weekly ${pct(account.weeklyUtil)} · 5h ${pct(account.hourlyUtil)}${account.disabledReason ? ' · disabled' : account.available ? '' : ' · waiting'}`}
|
|
204
|
+
</text>
|
|
205
|
+
<Show when={!props.compact && account.warnings.length > 0}>
|
|
206
|
+
<text fg={props.ctx.theme.text.muted}>
|
|
207
|
+
{account.warnings.join(' · ')}
|
|
208
|
+
</text>
|
|
209
|
+
</Show>
|
|
210
|
+
</box>
|
|
211
|
+
)}
|
|
212
|
+
</For>
|
|
213
|
+
</box>
|
|
214
|
+
)}
|
|
215
|
+
</For>
|
|
216
|
+
</box>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function registerV2Slots(ctx: Plugin.Context): () => void {
|
|
221
|
+
const stopToast = ctx.client
|
|
222
|
+
.rpc(loadBalancerRpc)
|
|
223
|
+
.events.on('toast', (event) => {
|
|
224
|
+
if (event.location?.directory === ctx.location?.directory)
|
|
225
|
+
ctx.ui.toast.show(event.data)
|
|
226
|
+
})
|
|
227
|
+
const stopFooter = ctx.ui.slot({
|
|
228
|
+
after: 'prompt.footer',
|
|
229
|
+
render: (props) => (
|
|
230
|
+
<StatusView compact ctx={ctx} sessionID={props.sessionID} />
|
|
231
|
+
),
|
|
232
|
+
})
|
|
233
|
+
const stopSidebar = ctx.ui.slot({
|
|
234
|
+
append: 'sidebar.content',
|
|
235
|
+
render: (props) => <StatusView ctx={ctx} sessionID={props.sessionID} />,
|
|
236
|
+
})
|
|
237
|
+
return () => {
|
|
238
|
+
stopToast()
|
|
239
|
+
stopFooter()
|
|
240
|
+
stopSidebar()
|
|
241
|
+
}
|
|
242
|
+
}
|
package/tui.ts
ADDED