opencode-auth-load-balancer 0.6.0 → 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 +59 -9
- package/dist/index.d.ts +7 -12
- package/dist/index.js +46285 -16486
- package/dist/legacy.d.ts +25 -0
- package/dist/pending/coordinator.d.ts +5 -1
- package/dist/pending/v2.d.ts +3 -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-v2.ts +10 -0
- package/tui/auth-load-balancer-v2.view.tsx +242 -0
- package/tui.ts +2 -0
package/dist/v2-rpc.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Display and management API; its status output never contains credentials. */
|
|
3
|
+
export declare const loadBalancerRpc: {
|
|
4
|
+
readonly id: 'auth-load-balancer';
|
|
5
|
+
readonly methods: {
|
|
6
|
+
readonly status: {
|
|
7
|
+
readonly input: z.ZodObject<{
|
|
8
|
+
sessionID: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
readonly output: z.ZodArray<z.ZodObject<{
|
|
11
|
+
providerID: z.ZodString;
|
|
12
|
+
accounts: z.ZodArray<z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
label: z.ZodString;
|
|
15
|
+
weeklyUtil: z.ZodNullable<z.ZodNumber>;
|
|
16
|
+
hourlyUtil: z.ZodNullable<z.ZodNumber>;
|
|
17
|
+
weeklyResetAt: z.ZodNumber;
|
|
18
|
+
available: z.ZodBoolean;
|
|
19
|
+
cooldownUntil: z.ZodNumber;
|
|
20
|
+
modelCooldownsUntil: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
21
|
+
disabledReason: z.ZodNullable<z.ZodString>;
|
|
22
|
+
login: z.ZodString;
|
|
23
|
+
warnings: z.ZodArray<z.ZodString>;
|
|
24
|
+
lostNotes: z.ZodArray<z.ZodString>;
|
|
25
|
+
current: z.ZodBoolean;
|
|
26
|
+
rank: z.ZodNumber;
|
|
27
|
+
}, z.core.$strip>>;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
};
|
|
30
|
+
readonly manage: {
|
|
31
|
+
readonly input: z.ZodObject<{
|
|
32
|
+
account: z.ZodString;
|
|
33
|
+
action: z.ZodEnum<{
|
|
34
|
+
"clear-relogin": "clear-relogin";
|
|
35
|
+
delete: "delete";
|
|
36
|
+
disable: "disable";
|
|
37
|
+
enable: "enable";
|
|
38
|
+
relogin: "relogin";
|
|
39
|
+
rename: "rename";
|
|
40
|
+
}>;
|
|
41
|
+
name: z.ZodOptional<z.ZodString>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
readonly output: z.ZodString;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
readonly events: {
|
|
47
|
+
readonly toast: {
|
|
48
|
+
readonly schema: z.ZodObject<{
|
|
49
|
+
title: z.ZodOptional<z.ZodString>;
|
|
50
|
+
message: z.ZodString;
|
|
51
|
+
variant: z.ZodEnum<{
|
|
52
|
+
error: "error";
|
|
53
|
+
info: "info";
|
|
54
|
+
success: "success";
|
|
55
|
+
warning: "warning";
|
|
56
|
+
}>;
|
|
57
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type ProviderStatus = z.infer<typeof loadBalancerRpc.methods.status.output>[number];
|
package/dist/v2.d.ts
ADDED
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-auth-load-balancer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Load-balance opencode requests across multiple Anthropic (Claude) and OpenAI (Codex) OAuth accounts and Kimi Code subscriptions, weighted primarily by weekly usage with drain-before-reset scheduling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,16 +20,25 @@
|
|
|
20
20
|
".": {
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"default": "./dist/index.js"
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"./v1": {
|
|
25
|
+
"types": "./dist/v1.d.ts",
|
|
26
|
+
"default": "./dist/v1.js"
|
|
27
|
+
},
|
|
28
|
+
"./server": "./dist/index.js",
|
|
29
|
+
"./tui": "./tui.ts",
|
|
30
|
+
"./tui-v1": "./tui/auth-load-balancer-tui.ts"
|
|
24
31
|
},
|
|
25
32
|
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"tui.ts",
|
|
26
35
|
"dist",
|
|
27
36
|
"tui"
|
|
28
37
|
],
|
|
29
38
|
"scripts": {
|
|
30
|
-
"sync:tui": "bun -e \"await Bun.write('tui/auth-load-balancer-scoring.ts', Bun.file('src/scheduler/score-core.ts'))\"",
|
|
31
|
-
"build": "bun run sync:tui && bun build ./src/index.ts --outdir dist --target node --format esm && tsc -p tsconfig.build.json",
|
|
32
|
-
"dev": "bun build ./src/index.ts --outdir dist --target node --format esm --watch",
|
|
39
|
+
"sync:tui": "bun -e \"await Bun.write('tui/auth-load-balancer-scoring.ts', Bun.file('src/scheduler/score-core.ts')); await Bun.write('tui/auth-load-balancer-rpc.ts', Bun.file('src/v2-rpc.ts'))\"",
|
|
40
|
+
"build": "bun run sync:tui && bun build ./src/index.ts ./src/v1.ts --outdir dist --target node --format esm --external @opencode/plugin --external effect --external 'effect/*' && tsc -p tsconfig.build.json",
|
|
41
|
+
"dev": "bun build ./src/index.ts ./src/v1.ts --outdir dist --target node --format esm --external @opencode/plugin --external effect --external 'effect/*' --watch",
|
|
33
42
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.tui.json --noEmit",
|
|
34
43
|
"typecheck:tui": "tsc -p tsconfig.tui.json --noEmit",
|
|
35
44
|
"lint": "oxlint",
|
|
@@ -41,12 +50,35 @@
|
|
|
41
50
|
"prepare": "husky"
|
|
42
51
|
},
|
|
43
52
|
"peerDependencies": {
|
|
44
|
-
"@opencode-ai/plugin": ">=1.18.26"
|
|
53
|
+
"@opencode-ai/plugin": ">=1.18.26 <2",
|
|
54
|
+
"@opencode/plugin": "^2.0.27",
|
|
55
|
+
"@opentui/core": ">=0.5.17",
|
|
56
|
+
"@opentui/solid": ">=0.5.17",
|
|
57
|
+
"solid-js": ">=1.9.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@opencode-ai/plugin": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@opencode/plugin": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@opentui/core": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@opentui/solid": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"solid-js": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
45
75
|
},
|
|
46
76
|
"devDependencies": {
|
|
47
77
|
"@opencode-ai/plugin": "^1.18.26",
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
78
|
+
"@opencode/plugin": "2.0.27",
|
|
79
|
+
"@opencode/theme": "2.0.27",
|
|
80
|
+
"@opentui/core": "0.5.17",
|
|
81
|
+
"@opentui/solid": "0.5.17",
|
|
50
82
|
"@types/bun": "^1.4.0",
|
|
51
83
|
"@types/node": "^26.4.1",
|
|
52
84
|
"eslint-plugin-devup": "^2.1.0",
|
|
@@ -73,5 +105,11 @@
|
|
|
73
105
|
},
|
|
74
106
|
"publishConfig": {
|
|
75
107
|
"access": "public"
|
|
108
|
+
},
|
|
109
|
+
"dependencies": {
|
|
110
|
+
"@ai-sdk/anthropic": "3.0.129",
|
|
111
|
+
"@ai-sdk/openai": "3.0.125",
|
|
112
|
+
"@ai-sdk/openai-compatible": "2.0.81",
|
|
113
|
+
"zod": "^4.3"
|
|
76
114
|
}
|
|
77
115
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const accountStatus = z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
label: z.string(),
|
|
6
|
+
weeklyUtil: z.number().nullable(),
|
|
7
|
+
hourlyUtil: z.number().nullable(),
|
|
8
|
+
weeklyResetAt: z.number(),
|
|
9
|
+
available: z.boolean(),
|
|
10
|
+
cooldownUntil: z.number(),
|
|
11
|
+
modelCooldownsUntil: z.record(z.string(), z.number()),
|
|
12
|
+
disabledReason: z.string().nullable(),
|
|
13
|
+
login: z.string(),
|
|
14
|
+
warnings: z.array(z.string()),
|
|
15
|
+
lostNotes: z.array(z.string()),
|
|
16
|
+
current: z.boolean(),
|
|
17
|
+
rank: z.number(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/** Display and management API; its status output never contains credentials. */
|
|
21
|
+
export const loadBalancerRpc = {
|
|
22
|
+
id: 'auth-load-balancer',
|
|
23
|
+
methods: {
|
|
24
|
+
status: {
|
|
25
|
+
input: z.object({ sessionID: z.string().optional() }),
|
|
26
|
+
output: z.array(
|
|
27
|
+
z.object({ providerID: z.string(), accounts: z.array(accountStatus) }),
|
|
28
|
+
),
|
|
29
|
+
},
|
|
30
|
+
manage: {
|
|
31
|
+
input: z.object({
|
|
32
|
+
account: z.string(),
|
|
33
|
+
action: z.enum([
|
|
34
|
+
'rename',
|
|
35
|
+
'disable',
|
|
36
|
+
'enable',
|
|
37
|
+
'delete',
|
|
38
|
+
'relogin',
|
|
39
|
+
'clear-relogin',
|
|
40
|
+
]),
|
|
41
|
+
name: z.string().optional(),
|
|
42
|
+
}),
|
|
43
|
+
output: z.string(),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
events: {
|
|
47
|
+
toast: {
|
|
48
|
+
schema: z.object({
|
|
49
|
+
title: z.string().optional(),
|
|
50
|
+
message: z.string(),
|
|
51
|
+
variant: z.enum(['info', 'success', 'warning', 'error']),
|
|
52
|
+
duration: z.number().optional(),
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} as const
|
|
57
|
+
|
|
58
|
+
export type ProviderStatus = z.infer<
|
|
59
|
+
typeof loadBalancerRpc.methods.status.output
|
|
60
|
+
>[number]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin } from '@opencode/plugin/tui'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
id: 'auth-load-balancer-tui',
|
|
5
|
+
async setup(ctx: Plugin.Context) {
|
|
6
|
+
// The host installs its Solid transform before evaluating this view.
|
|
7
|
+
const { registerV2Slots } = await import('./auth-load-balancer-v2.view.tsx')
|
|
8
|
+
return registerV2Slots(ctx)
|
|
9
|
+
},
|
|
10
|
+
}
|
|
@@ -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