dsh-codex-community 0.0.1
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/CHANGELOG.md +90 -0
- package/CONTRIBUTING.en.md +54 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.en.md +98 -0
- package/README.md +98 -0
- package/SECURITY.md +41 -0
- package/SUPPORT.md +17 -0
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/codex-community.patch.yml +13 -0
- package/dist/client/index.js +1037 -0
- package/dist/host/index.mjs +98 -0
- package/dist/internal/authorization-bridge.mjs +662 -0
- package/dist/internal/authorization-commit-tracker.mjs +49 -0
- package/dist/internal/codex-authorization.mjs +202 -0
- package/dist/internal/codex-credential-store.mjs +164 -0
- package/dist/internal/codex-identifiers.mjs +4 -0
- package/dist/internal/codex-pi-provider.mjs +137 -0
- package/dist/internal/codex-provider-runtime.mjs +256 -0
- package/dist/internal/codex-route-adapter.mjs +133 -0
- package/dist/internal/codex-session-resources.mjs +64 -0
- package/dist/internal/failure-normalizer.mjs +456 -0
- package/dist/internal/image-policy.mjs +45 -0
- package/dist/internal/quota-observer.mjs +142 -0
- package/dist/internal/reliability.mjs +12 -0
- package/dist/internal/remote-image-input.mjs +801 -0
- package/dist/internal/session-preference-command.mjs +52 -0
- package/dist/internal/session-preferences.mjs +93 -0
- package/dist/internal/stream-resilience.mjs +273 -0
- package/docs/README.en.md +15 -0
- package/docs/README.md +15 -0
- package/docs/architecture.en.md +106 -0
- package/docs/architecture.md +106 -0
- package/docs/compatibility.en.md +53 -0
- package/docs/compatibility.md +53 -0
- package/docs/configuration.en.md +61 -0
- package/docs/configuration.md +61 -0
- package/docs/contribution-sources.en.md +35 -0
- package/docs/contribution-sources.md +35 -0
- package/docs/github-about.md +15 -0
- package/docs/releases/v0.0.1.acceptance.json +160 -0
- package/docs/releases/v0.0.1.md +174 -0
- package/docs/releasing.en.md +290 -0
- package/docs/releasing.md +290 -0
- package/docs/testing.en.md +85 -0
- package/docs/testing.md +85 -0
- package/docs/troubleshooting.en.md +47 -0
- package/docs/troubleshooting.md +47 -0
- package/package.json +144 -0
- package/types/client.d.ts +160 -0
- package/types/index.d.ts +22 -0
- package/types/reliability.d.ts +78 -0
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-codex-community",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
const module = { exports: {} }
|
|
5
|
+
const exports = module.exports
|
|
6
|
+
const React = require("react")
|
|
7
|
+
const h = React.createElement
|
|
8
|
+
|
|
9
|
+
const CHANNEL = "/dsh-codex"
|
|
10
|
+
const NS = "settings.codex"
|
|
11
|
+
const CODEX_PROVIDER = "dsh-codex"
|
|
12
|
+
const CODEX_SETTINGS_NS = "dsh-codex"
|
|
13
|
+
const CODEX_SETTINGS_PATH = Object.freeze([])
|
|
14
|
+
const STYLE_ID = "dsh-codex-community/authorization-settings.css"
|
|
15
|
+
const STYLE_REF_KEY = Symbol.for(`${STYLE_ID}/references`)
|
|
16
|
+
|
|
17
|
+
const zh = {
|
|
18
|
+
nav: "OpenAI Codex",
|
|
19
|
+
title: "OpenAI Codex",
|
|
20
|
+
description: "使用 ChatGPT 订阅在 dsh 中调用 Codex 模型,无需 API Key。",
|
|
21
|
+
loading: "正在读取登录状态…",
|
|
22
|
+
signedIn: "已登录",
|
|
23
|
+
signedOut: "尚未登录",
|
|
24
|
+
credentialInvalid: "凭据无效",
|
|
25
|
+
credentialInvalidHelp: "已保存的 Codex 凭据无法使用;请重新登录,或退出后再登录。",
|
|
26
|
+
inProgress: "登录正在进行",
|
|
27
|
+
unavailable: "当前环境没有可用的 Codex 登录流程。",
|
|
28
|
+
writableNo: "当前凭据存储不可写。",
|
|
29
|
+
refresh: "刷新",
|
|
30
|
+
signIn: "登录",
|
|
31
|
+
signInWithChatGPT: "使用 ChatGPT 登录",
|
|
32
|
+
signInAgain: "重新登录",
|
|
33
|
+
cancelling: "正在取消登录",
|
|
34
|
+
cancel: "取消登录",
|
|
35
|
+
signOut: "退出登录",
|
|
36
|
+
signingOut: "正在退出登录",
|
|
37
|
+
confirmSignOut: "确认退出 Codex 登录并删除已保存的授权凭据吗?",
|
|
38
|
+
openPage: "打开授权页面",
|
|
39
|
+
deviceCode: "验证码",
|
|
40
|
+
submit: "继续",
|
|
41
|
+
decline: "取消",
|
|
42
|
+
authorized: "登录成功。",
|
|
43
|
+
cancelled: "登录已取消。",
|
|
44
|
+
commitInProgress: "凭据已开始提交,当前无法取消;登录完成后状态会自动更新。",
|
|
45
|
+
failed: "登录失败,请重试。",
|
|
46
|
+
cancelFailed: "取消登录失败,请重试。",
|
|
47
|
+
logoutFailed: "退出登录失败,请重试。",
|
|
48
|
+
responseFailed: "提交登录信息失败,请重试。",
|
|
49
|
+
requestFailed: "暂时无法连接本机授权服务。",
|
|
50
|
+
quotaTitle: "使用额度",
|
|
51
|
+
quotaProduct: "Codex",
|
|
52
|
+
quotaUnknown: "尚无可验证的额度观测。",
|
|
53
|
+
quotaUnknownHelp: "完成一次 Codex 请求后,此处会更新可验证状态;插件不会调用未公开的账户额度接口。",
|
|
54
|
+
quotaRecentSuccess: "最近一次 Codex 请求成功",
|
|
55
|
+
quotaSuccessCaution: "这只表示最近一次请求成功,不代表账户剩余额度。",
|
|
56
|
+
quotaExhausted: "最近观测到 Codex 账户额度已用尽。",
|
|
57
|
+
quotaObservedAt: "观测时间",
|
|
58
|
+
quotaResetAt: "观测到的重置时间",
|
|
59
|
+
quotaResetIn: "预计还有约",
|
|
60
|
+
quotaMinutes: "分钟",
|
|
61
|
+
quotaNoReset: "未获得通过校验的重置时间;状态会在有限时间后自动回到未知。",
|
|
62
|
+
modelsTitle: "模型选择器中显示的模型",
|
|
63
|
+
modelsDescription: "选择要在模型选择器中显示的 Codex 模型。隐藏后,已有会话中保存的精确模型仍可继续使用。",
|
|
64
|
+
modelsLoading: "正在读取 Codex 模型目录…",
|
|
65
|
+
modelsUnavailable: "当前环境没有可用的 Codex 模型管理接口。",
|
|
66
|
+
modelsReadOnly: "当前设置存储为只读,无法修改启用模型。",
|
|
67
|
+
modelsEmpty: "当前安装没有可管理的 Codex 模型。",
|
|
68
|
+
modelsSave: "保存启用模型",
|
|
69
|
+
modelsSaving: "正在保存…",
|
|
70
|
+
modelsSaved: "模型启用设置已保存。",
|
|
71
|
+
modelsAtLeastOne: "至少需要启用一个 Codex 模型。",
|
|
72
|
+
modelsAllEnabledFollow: "已启用全部当前目录模型;当前覆盖不含额外模型字段,保存后会清除覆盖并自动启用未来新增的目录模型。",
|
|
73
|
+
modelsAllEnabledPreserve: "已启用全部当前目录模型;为保留现有模型参数,保存后会固定当前模型列表,未来新增的目录模型不会自动启用。",
|
|
74
|
+
modelsRetry: "重新读取",
|
|
75
|
+
modelsLoadFailed: "读取 Codex 模型目录失败。",
|
|
76
|
+
modelsSaveFailed: "保存模型启用设置失败。",
|
|
77
|
+
}
|
|
78
|
+
const en = {
|
|
79
|
+
nav: "OpenAI Codex",
|
|
80
|
+
title: "OpenAI Codex",
|
|
81
|
+
description: "Use your ChatGPT subscription to access Codex models in dsh—no API key required.",
|
|
82
|
+
loading: "Reading sign-in status…",
|
|
83
|
+
signedIn: "Signed in",
|
|
84
|
+
signedOut: "Not signed in",
|
|
85
|
+
credentialInvalid: "Invalid credential",
|
|
86
|
+
credentialInvalidHelp: "The saved Codex credential cannot be used. Sign in again, or sign out and then sign in.",
|
|
87
|
+
inProgress: "Sign-in in progress",
|
|
88
|
+
unavailable: "No Codex sign-in flow is available in this environment.",
|
|
89
|
+
writableNo: "The credential store is currently read-only.",
|
|
90
|
+
refresh: "Refresh",
|
|
91
|
+
signIn: "Sign in",
|
|
92
|
+
signInWithChatGPT: "Sign in with ChatGPT",
|
|
93
|
+
signInAgain: "Sign in again",
|
|
94
|
+
cancelling: "Cancelling sign-in",
|
|
95
|
+
cancel: "Cancel sign-in",
|
|
96
|
+
signOut: "Sign out",
|
|
97
|
+
signingOut: "Signing out",
|
|
98
|
+
confirmSignOut: "Sign out of Codex and delete the saved authorization credential?",
|
|
99
|
+
openPage: "Open authorization page",
|
|
100
|
+
deviceCode: "Verification code",
|
|
101
|
+
submit: "Continue",
|
|
102
|
+
decline: "Cancel",
|
|
103
|
+
authorized: "Sign-in complete.",
|
|
104
|
+
cancelled: "Sign-in cancelled.",
|
|
105
|
+
commitInProgress: "Credential commit has started and can no longer be cancelled. Status will update when sign-in completes.",
|
|
106
|
+
failed: "Sign-in failed. Please try again.",
|
|
107
|
+
cancelFailed: "Could not cancel sign-in. Please try again.",
|
|
108
|
+
logoutFailed: "Could not sign out. Please try again.",
|
|
109
|
+
responseFailed: "Could not submit the sign-in response. Please try again.",
|
|
110
|
+
requestFailed: "The local authorization service is temporarily unavailable.",
|
|
111
|
+
quotaTitle: "Usage",
|
|
112
|
+
quotaProduct: "Codex",
|
|
113
|
+
quotaUnknown: "No verifiable quota observation yet.",
|
|
114
|
+
quotaUnknownHelp: "This area updates after a Codex request; the plugin does not call undocumented account-quota endpoints.",
|
|
115
|
+
quotaRecentSuccess: "The latest Codex request succeeded",
|
|
116
|
+
quotaSuccessCaution: "This only describes the latest request; it does not represent remaining account quota.",
|
|
117
|
+
quotaExhausted: "Codex account quota was exhausted in the latest observation.",
|
|
118
|
+
quotaObservedAt: "Observed at",
|
|
119
|
+
quotaResetAt: "Observed reset time",
|
|
120
|
+
quotaResetIn: "Approximately",
|
|
121
|
+
quotaMinutes: "minutes remaining",
|
|
122
|
+
quotaNoReset: "No reset time passed validation; this state automatically returns to unknown after a bounded interval.",
|
|
123
|
+
modelsTitle: "Models shown in the model selector",
|
|
124
|
+
modelsDescription: "Choose which Codex models appear in the model selector. Hidden models remain available to existing conversations that saved an exact model.",
|
|
125
|
+
modelsLoading: "Reading the Codex model catalog…",
|
|
126
|
+
modelsUnavailable: "Codex model management is unavailable in this environment.",
|
|
127
|
+
modelsReadOnly: "The settings store is read-only, so enabled models cannot be changed.",
|
|
128
|
+
modelsEmpty: "This installation has no Codex models to manage.",
|
|
129
|
+
modelsSave: "Save enabled models",
|
|
130
|
+
modelsSaving: "Saving…",
|
|
131
|
+
modelsSaved: "Enabled models saved.",
|
|
132
|
+
modelsAtLeastOne: "At least one Codex model must remain enabled.",
|
|
133
|
+
modelsAllEnabledFollow: "All current catalog models are enabled. Because the override has no additional model fields, saving will clear it and automatically enable future catalog additions.",
|
|
134
|
+
modelsAllEnabledPreserve: "All current catalog models are enabled. To preserve existing model parameters, saving will keep the current model list; future catalog additions will not be enabled automatically.",
|
|
135
|
+
modelsRetry: "Reload",
|
|
136
|
+
modelsLoadFailed: "Could not read the Codex model catalog.",
|
|
137
|
+
modelsSaveFailed: "Could not save the enabled models.",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createAuthorizationClient(connection) {
|
|
141
|
+
const call = async (endpoint, payload, signal) => {
|
|
142
|
+
const result = await connection.rpc.call(CHANNEL, endpoint, payload, signal)
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
const error = new Error(result.error.message)
|
|
145
|
+
error.code = result.error.code
|
|
146
|
+
throw error
|
|
147
|
+
}
|
|
148
|
+
return result.value
|
|
149
|
+
}
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
describe: (signal) => call("status", {}, signal),
|
|
152
|
+
watch: (attemptId, after, signal) => call("status", { attemptId, after }, signal),
|
|
153
|
+
start: (method) => call("start", method === undefined ? {} : { method }),
|
|
154
|
+
answer: (attemptId, promptId, value) => call("respond", {
|
|
155
|
+
attemptId,
|
|
156
|
+
promptId,
|
|
157
|
+
action: "answer",
|
|
158
|
+
value,
|
|
159
|
+
}),
|
|
160
|
+
decline: (attemptId, promptId) => call("respond", {
|
|
161
|
+
attemptId,
|
|
162
|
+
promptId,
|
|
163
|
+
action: "decline",
|
|
164
|
+
}),
|
|
165
|
+
cancel: (attemptId) => call("cancel", attemptId === undefined ? {} : { attemptId }),
|
|
166
|
+
logout: () => call("logout", {}),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resultValue(response) {
|
|
171
|
+
if (response?.result?.ok === true) return response.result.value
|
|
172
|
+
const message = response?.result?.error?.message
|
|
173
|
+
throw new Error(typeof message === "string" && message.length > 0 ? message : "DSH API request failed")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function safeQuotaSnapshot(value, now = Date.now()) {
|
|
177
|
+
if (typeof value !== "object" || value === null || !Number.isSafeInteger(now) || now < 0) {
|
|
178
|
+
return { status: "unknown" }
|
|
179
|
+
}
|
|
180
|
+
if (value.status === "recent-success" && Number.isSafeInteger(value.observedAt) && value.observedAt >= 0) {
|
|
181
|
+
return { status: value.status, observedAt: value.observedAt }
|
|
182
|
+
}
|
|
183
|
+
if (value.status !== "exhausted" || !Number.isSafeInteger(value.observedAt) || value.observedAt < 0) {
|
|
184
|
+
return { status: "unknown" }
|
|
185
|
+
}
|
|
186
|
+
if (value.resetAt === undefined) return { status: value.status, observedAt: value.observedAt }
|
|
187
|
+
if (!Number.isSafeInteger(value.resetAt) || value.resetAt <= now) return { status: "unknown" }
|
|
188
|
+
return {
|
|
189
|
+
status: value.status,
|
|
190
|
+
observedAt: value.observedAt,
|
|
191
|
+
resetAt: value.resetAt,
|
|
192
|
+
remainingMinutes: Math.max(1, Math.ceil((value.resetAt - now) / 60_000)),
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function objectAtPath(root, path) {
|
|
197
|
+
let current = root
|
|
198
|
+
for (const key of path) {
|
|
199
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return undefined
|
|
200
|
+
current = current[key]
|
|
201
|
+
}
|
|
202
|
+
return current
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function modelRows(value) {
|
|
206
|
+
if (!Array.isArray(value)) return []
|
|
207
|
+
return value.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row) && typeof row.id === "string" && row.id.length > 0)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function discoveredModels(value) {
|
|
211
|
+
if (!Array.isArray(value)) return []
|
|
212
|
+
const seen = new Set()
|
|
213
|
+
const rows = []
|
|
214
|
+
for (const candidate of value) {
|
|
215
|
+
if (typeof candidate !== "object" || candidate === null || typeof candidate.id !== "string" || candidate.id.length === 0 || seen.has(candidate.id)) continue
|
|
216
|
+
seen.add(candidate.id)
|
|
217
|
+
rows.push({
|
|
218
|
+
id: candidate.id,
|
|
219
|
+
...(typeof candidate.name === "string" && candidate.name.length > 0 ? { name: candidate.name } : {}),
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
return rows
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function modelSavePlan(snapshot, selectedIds) {
|
|
226
|
+
const selected = new Set(selectedIds)
|
|
227
|
+
const modelIds = snapshot.models.map((model) => model.id)
|
|
228
|
+
const catalogIds = Array.isArray(snapshot.catalogIds)
|
|
229
|
+
? snapshot.catalogIds.filter((id) => typeof id === "string")
|
|
230
|
+
: modelIds
|
|
231
|
+
const catalogSet = new Set(catalogIds)
|
|
232
|
+
const orderedSelected = modelIds.filter((id) => selected.has(id))
|
|
233
|
+
const existing = new Map(snapshot.configuredModels.map((model) => [model.id, model]))
|
|
234
|
+
const models = orderedSelected.map((id) => existing.has(id) ? { ...existing.get(id) } : { id })
|
|
235
|
+
const allCatalogSelected = catalogIds.length > 0 && catalogIds.every((id) => selected.has(id))
|
|
236
|
+
const hasSelectedOutsideCatalog = orderedSelected.some((id) => !catalogSet.has(id))
|
|
237
|
+
const hasAdditionalFields = models.some((model) => Object.keys(model).some((key) => key !== "id"))
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
orderedSelected,
|
|
241
|
+
models,
|
|
242
|
+
allCatalogSelected,
|
|
243
|
+
unsetOverride: allCatalogSelected && !hasSelectedOutsideCatalog && !hasAdditionalFields,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function createModelEnablementClient(connection, settingsFace) {
|
|
248
|
+
const api = connection?.api
|
|
249
|
+
const available = typeof api?.llm?.discoverModels === "function"
|
|
250
|
+
&& typeof api?.settings?.mutate === "function"
|
|
251
|
+
&& typeof settingsFace?.ensure === "function"
|
|
252
|
+
&& typeof settingsFace?.getSnapshot === "function"
|
|
253
|
+
&& typeof settingsFace?.subscribe === "function"
|
|
254
|
+
&& typeof settingsFace?.acceptView === "function"
|
|
255
|
+
|
|
256
|
+
const load = async (signal) => {
|
|
257
|
+
if (!available) return { kind: "unavailable" }
|
|
258
|
+
|
|
259
|
+
const [catalogResponse] = await Promise.all([
|
|
260
|
+
api.llm.discoverModels({ settingsNs: CODEX_SETTINGS_NS, provider: CODEX_PROVIDER }, signal),
|
|
261
|
+
settingsFace.ensure(),
|
|
262
|
+
])
|
|
263
|
+
const discovered = discoveredModels(resultValue(catalogResponse).models)
|
|
264
|
+
const mirror = settingsFace.getSnapshot()
|
|
265
|
+
if (mirror?.status !== "ready" || mirror.view === undefined) return { kind: "unavailable" }
|
|
266
|
+
const settings = mirror.view
|
|
267
|
+
const namespace = Array.isArray(settings.namespaces)
|
|
268
|
+
? settings.namespaces.find((entry) => entry?.ns === CODEX_SETTINGS_NS)
|
|
269
|
+
: undefined
|
|
270
|
+
if (namespace === undefined || typeof namespace.revision !== "number") return { kind: "unavailable" }
|
|
271
|
+
|
|
272
|
+
const modelsPath = [...CODEX_SETTINGS_PATH, "models"]
|
|
273
|
+
const userModelsValue = objectAtPath(namespace.user, modelsPath)
|
|
274
|
+
const resolvedModelsValue = objectAtPath(namespace.value, modelsPath)
|
|
275
|
+
const hasUserModelOverride = Array.isArray(userModelsValue)
|
|
276
|
+
const hasResolvedModelOverride = Array.isArray(resolvedModelsValue)
|
|
277
|
+
const configuredModels = hasUserModelOverride
|
|
278
|
+
? modelRows(userModelsValue)
|
|
279
|
+
: hasResolvedModelOverride
|
|
280
|
+
? modelRows(resolvedModelsValue)
|
|
281
|
+
: []
|
|
282
|
+
const hasModelOverride = hasUserModelOverride || hasResolvedModelOverride
|
|
283
|
+
const discoveredIds = new Set(discovered.map((model) => model.id))
|
|
284
|
+
const customModels = configuredModels
|
|
285
|
+
.filter((model) => !discoveredIds.has(model.id))
|
|
286
|
+
.map((model) => ({
|
|
287
|
+
id: model.id,
|
|
288
|
+
...(typeof model.name === "string" && model.name.length > 0 ? { name: model.name } : {}),
|
|
289
|
+
}))
|
|
290
|
+
const catalog = [...discovered, ...customModels]
|
|
291
|
+
const configuredIds = new Set(configuredModels.map((model) => model.id))
|
|
292
|
+
const selectedIds = hasModelOverride
|
|
293
|
+
? catalog.filter((model) => configuredIds.has(model.id)).map((model) => model.id)
|
|
294
|
+
: catalog.map((model) => model.id)
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
kind: "ready",
|
|
298
|
+
writable: settings.writable === true,
|
|
299
|
+
revision: namespace.revision,
|
|
300
|
+
settingsNs: CODEX_SETTINGS_NS,
|
|
301
|
+
settingsPath: [...CODEX_SETTINGS_PATH],
|
|
302
|
+
models: catalog,
|
|
303
|
+
catalogIds: discovered.map((model) => model.id),
|
|
304
|
+
selectedIds,
|
|
305
|
+
configuredModels: configuredModels.map((model) => ({ ...model })),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const save = async (snapshot, selectedIds, signal) => {
|
|
310
|
+
if (!available || snapshot?.kind !== "ready") throw new Error("Codex model management is unavailable")
|
|
311
|
+
const plan = modelSavePlan(snapshot, selectedIds)
|
|
312
|
+
const { orderedSelected, models } = plan
|
|
313
|
+
if (orderedSelected.length === 0) {
|
|
314
|
+
const error = new Error("At least one Codex model must remain enabled")
|
|
315
|
+
error.code = "AT_LEAST_ONE_MODEL"
|
|
316
|
+
throw error
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const modelsPath = [...snapshot.settingsPath, "models"]
|
|
320
|
+
const response = await api.settings.mutate({
|
|
321
|
+
ns: snapshot.settingsNs,
|
|
322
|
+
ops: [plan.unsetOverride
|
|
323
|
+
? { op: "unset", path: modelsPath }
|
|
324
|
+
: { op: "set", path: modelsPath, value: models }],
|
|
325
|
+
expectedRevision: snapshot.revision,
|
|
326
|
+
}, signal)
|
|
327
|
+
const namespace = resultValue(response)
|
|
328
|
+
settingsFace.acceptView(namespace)
|
|
329
|
+
return {
|
|
330
|
+
...snapshot,
|
|
331
|
+
revision: typeof namespace.revision === "number" ? namespace.revision : snapshot.revision,
|
|
332
|
+
selectedIds: orderedSelected,
|
|
333
|
+
configuredModels: plan.unsetOverride ? [] : models,
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const subscribe = (listener) => available ? settingsFace.subscribe(listener) : () => undefined
|
|
338
|
+
return Object.freeze({ available, load, save, subscribe })
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function AuthorizationSettings({ client, t }) {
|
|
342
|
+
const [snapshot, setSnapshot] = React.useState({ status: "loading" })
|
|
343
|
+
const [attempt, setAttempt] = React.useState(null)
|
|
344
|
+
const [notices, setNotices] = React.useState([])
|
|
345
|
+
const [prompt, setPrompt] = React.useState(null)
|
|
346
|
+
const [answer, setAnswer] = React.useState("")
|
|
347
|
+
const [outcome, setOutcome] = React.useState(null)
|
|
348
|
+
const [failure, setFailure] = React.useState(null)
|
|
349
|
+
const [responding, setResponding] = React.useState(false)
|
|
350
|
+
const [cancelling, setCancelling] = React.useState(false)
|
|
351
|
+
const [loggingOut, setLoggingOut] = React.useState(false)
|
|
352
|
+
const attemptRef = React.useRef(null)
|
|
353
|
+
const pendingStartRef = React.useRef(null)
|
|
354
|
+
const mountedRef = React.useRef(false)
|
|
355
|
+
const attemptGenerationRef = React.useRef(0)
|
|
356
|
+
const responseGenerationRef = React.useRef(0)
|
|
357
|
+
const refreshGenerationRef = React.useRef(0)
|
|
358
|
+
const refreshControllerRef = React.useRef(null)
|
|
359
|
+
|
|
360
|
+
const refresh = React.useCallback(async () => {
|
|
361
|
+
if (!mountedRef.current) return
|
|
362
|
+
const generation = ++refreshGenerationRef.current
|
|
363
|
+
refreshControllerRef.current?.abort()
|
|
364
|
+
const controller = new AbortController()
|
|
365
|
+
refreshControllerRef.current = controller
|
|
366
|
+
setSnapshot((current) => current.status === "ready" ? current : { status: "loading" })
|
|
367
|
+
try {
|
|
368
|
+
const value = await client.describe(controller.signal)
|
|
369
|
+
if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
|
|
370
|
+
setSnapshot({ status: "ready", value })
|
|
371
|
+
} catch {
|
|
372
|
+
if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
|
|
373
|
+
setSnapshot({ status: "error" })
|
|
374
|
+
} finally {
|
|
375
|
+
if (generation === refreshGenerationRef.current && refreshControllerRef.current === controller) {
|
|
376
|
+
refreshControllerRef.current = null
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}, [client])
|
|
380
|
+
|
|
381
|
+
React.useEffect(() => {
|
|
382
|
+
mountedRef.current = true
|
|
383
|
+
void refresh()
|
|
384
|
+
const timer = typeof window.setInterval === "function"
|
|
385
|
+
? window.setInterval(() => void refresh(), 60_000)
|
|
386
|
+
: undefined
|
|
387
|
+
return () => {
|
|
388
|
+
mountedRef.current = false
|
|
389
|
+
refreshGenerationRef.current += 1
|
|
390
|
+
refreshControllerRef.current?.abort()
|
|
391
|
+
refreshControllerRef.current = null
|
|
392
|
+
attemptGenerationRef.current += 1
|
|
393
|
+
responseGenerationRef.current += 1
|
|
394
|
+
pendingStartRef.current = null
|
|
395
|
+
const current = attemptRef.current
|
|
396
|
+
attemptRef.current = null
|
|
397
|
+
current?.controller.abort()
|
|
398
|
+
if (current !== null && !current.done) void client.cancel(current.id).catch(() => undefined)
|
|
399
|
+
if (timer !== undefined) window.clearInterval(timer)
|
|
400
|
+
}
|
|
401
|
+
}, [client])
|
|
402
|
+
|
|
403
|
+
const consume = async (current) => {
|
|
404
|
+
const isCurrent = () => mountedRef.current
|
|
405
|
+
&& attemptGenerationRef.current === current.generation
|
|
406
|
+
&& attemptRef.current === current
|
|
407
|
+
&& !current.controller.signal.aborted
|
|
408
|
+
let after = 0
|
|
409
|
+
try {
|
|
410
|
+
while (isCurrent()) {
|
|
411
|
+
const page = await client.watch(current.id, after, current.controller.signal)
|
|
412
|
+
if (!isCurrent()) return
|
|
413
|
+
after = page.nextSeq
|
|
414
|
+
for (const event of page.events) {
|
|
415
|
+
if (!isCurrent()) return
|
|
416
|
+
if (event.type === "notice") {
|
|
417
|
+
setNotices((items) => [...items, event.notice].slice(-32))
|
|
418
|
+
} else if (event.type === "prompt") {
|
|
419
|
+
setAnswer("")
|
|
420
|
+
setPrompt({ id: event.promptId, value: event.prompt })
|
|
421
|
+
} else if (event.type === "prompt-closed") {
|
|
422
|
+
setPrompt((currentPrompt) => currentPrompt?.id === event.promptId ? null : currentPrompt)
|
|
423
|
+
setAnswer("")
|
|
424
|
+
} else if (event.type === "settled") {
|
|
425
|
+
current.done = true
|
|
426
|
+
responseGenerationRef.current += 1
|
|
427
|
+
setResponding(false)
|
|
428
|
+
setPrompt(null)
|
|
429
|
+
setFailure(null)
|
|
430
|
+
setOutcome(event.status)
|
|
431
|
+
} else if (event.type === "failed") {
|
|
432
|
+
current.done = true
|
|
433
|
+
responseGenerationRef.current += 1
|
|
434
|
+
setResponding(false)
|
|
435
|
+
setPrompt(null)
|
|
436
|
+
setOutcome(null)
|
|
437
|
+
setFailure(event.error.code)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (page.done) {
|
|
441
|
+
current.done = true
|
|
442
|
+
break
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
if (isCurrent()) setFailure("TRANSPORT")
|
|
447
|
+
} finally {
|
|
448
|
+
if (mountedRef.current
|
|
449
|
+
&& attemptGenerationRef.current === current.generation
|
|
450
|
+
&& attemptRef.current === current) {
|
|
451
|
+
attemptRef.current = null
|
|
452
|
+
setAttempt(null)
|
|
453
|
+
void refresh()
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const begin = async (method) => {
|
|
459
|
+
if (!mountedRef.current
|
|
460
|
+
|| pendingStartRef.current !== null
|
|
461
|
+
|| attemptRef.current !== null
|
|
462
|
+
|| cancelling
|
|
463
|
+
|| loggingOut
|
|
464
|
+
|| responding) return
|
|
465
|
+
const generation = ++attemptGenerationRef.current
|
|
466
|
+
const pending = { generation }
|
|
467
|
+
pendingStartRef.current = pending
|
|
468
|
+
setAttempt("starting")
|
|
469
|
+
setNotices([])
|
|
470
|
+
setPrompt(null)
|
|
471
|
+
setOutcome(null)
|
|
472
|
+
setFailure(null)
|
|
473
|
+
try {
|
|
474
|
+
const started = await client.start(method)
|
|
475
|
+
if (!mountedRef.current
|
|
476
|
+
|| attemptGenerationRef.current !== generation
|
|
477
|
+
|| pendingStartRef.current !== pending) {
|
|
478
|
+
void client.cancel(started.attemptId).catch(() => undefined)
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
pendingStartRef.current = null
|
|
482
|
+
const current = {
|
|
483
|
+
id: started.attemptId,
|
|
484
|
+
controller: new AbortController(),
|
|
485
|
+
generation,
|
|
486
|
+
done: false,
|
|
487
|
+
}
|
|
488
|
+
attemptRef.current = current
|
|
489
|
+
setAttempt(current.id)
|
|
490
|
+
void consume(current)
|
|
491
|
+
} catch {
|
|
492
|
+
if (!mountedRef.current
|
|
493
|
+
|| attemptGenerationRef.current !== generation
|
|
494
|
+
|| pendingStartRef.current !== pending) return
|
|
495
|
+
pendingStartRef.current = null
|
|
496
|
+
setAttempt(null)
|
|
497
|
+
setFailure("START_FAILED")
|
|
498
|
+
void refresh()
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const cancel = async () => {
|
|
503
|
+
if (!mountedRef.current || cancelling || loggingOut || responding) return
|
|
504
|
+
const current = attemptRef.current
|
|
505
|
+
const generation = current?.generation ?? attemptGenerationRef.current
|
|
506
|
+
const isCurrentCancellation = () => mountedRef.current
|
|
507
|
+
&& attemptGenerationRef.current === generation
|
|
508
|
+
&& (current === null
|
|
509
|
+
? attemptRef.current === null
|
|
510
|
+
: attemptRef.current === current && !current.done)
|
|
511
|
+
if (pendingStartRef.current !== null) {
|
|
512
|
+
attemptGenerationRef.current += 1
|
|
513
|
+
pendingStartRef.current = null
|
|
514
|
+
setAttempt(null)
|
|
515
|
+
}
|
|
516
|
+
setCancelling(true)
|
|
517
|
+
setFailure(null)
|
|
518
|
+
setOutcome(null)
|
|
519
|
+
try {
|
|
520
|
+
const result = await client.cancel(current?.id)
|
|
521
|
+
if (!mountedRef.current) return
|
|
522
|
+
if (current !== null
|
|
523
|
+
&& isCurrentCancellation()
|
|
524
|
+
&& result?.accepted === false
|
|
525
|
+
&& result.reason === "commit-in-progress") {
|
|
526
|
+
setOutcome("commit-in-progress")
|
|
527
|
+
}
|
|
528
|
+
await refresh()
|
|
529
|
+
} catch {
|
|
530
|
+
if (isCurrentCancellation()) setFailure("CANCEL_FAILED")
|
|
531
|
+
} finally {
|
|
532
|
+
if (mountedRef.current) setCancelling(false)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const logout = async () => {
|
|
537
|
+
if (!mountedRef.current || loggingOut || cancelling || responding || attemptRef.current !== null || pendingStartRef.current !== null) return
|
|
538
|
+
if (typeof window.confirm === "function" && !window.confirm(t("confirmSignOut"))) return
|
|
539
|
+
setLoggingOut(true)
|
|
540
|
+
setFailure(null)
|
|
541
|
+
setOutcome(null)
|
|
542
|
+
try {
|
|
543
|
+
await client.logout()
|
|
544
|
+
if (!mountedRef.current) return
|
|
545
|
+
setNotices([])
|
|
546
|
+
setPrompt(null)
|
|
547
|
+
setAnswer("")
|
|
548
|
+
await refresh()
|
|
549
|
+
} catch {
|
|
550
|
+
if (mountedRef.current) setFailure("LOGOUT_FAILED")
|
|
551
|
+
} finally {
|
|
552
|
+
if (mountedRef.current) setLoggingOut(false)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const reply = async (action) => {
|
|
557
|
+
const current = attemptRef.current
|
|
558
|
+
if (!mountedRef.current || current === null || prompt === null || responding || cancelling || loggingOut) return
|
|
559
|
+
const generation = current.generation
|
|
560
|
+
const responseGeneration = ++responseGenerationRef.current
|
|
561
|
+
const promptId = prompt.id
|
|
562
|
+
const isCurrentResponse = () => mountedRef.current
|
|
563
|
+
&& responseGenerationRef.current === responseGeneration
|
|
564
|
+
setResponding(true)
|
|
565
|
+
setFailure(null)
|
|
566
|
+
try {
|
|
567
|
+
if (action === "decline") await client.decline(current.id, prompt.id)
|
|
568
|
+
else await client.answer(current.id, prompt.id, answer)
|
|
569
|
+
if (!isCurrentResponse()
|
|
570
|
+
|| attemptGenerationRef.current !== generation
|
|
571
|
+
|| attemptRef.current !== current) return
|
|
572
|
+
setPrompt((currentPrompt) => currentPrompt?.id === promptId ? null : currentPrompt)
|
|
573
|
+
setAnswer("")
|
|
574
|
+
} catch {
|
|
575
|
+
if (isCurrentResponse()
|
|
576
|
+
&& attemptGenerationRef.current === generation
|
|
577
|
+
&& attemptRef.current === current) setFailure("RESPONSE_FAILED")
|
|
578
|
+
} finally {
|
|
579
|
+
if (isCurrentResponse()) setResponding(false)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (snapshot.status === "loading") {
|
|
584
|
+
return h("section", { className: "dshCodexPanel dshCodexCard dshCodexAuthorizationCard", "aria-busy": "true" },
|
|
585
|
+
h("p", { className: "dshCodexMuted" }, t("loading")))
|
|
586
|
+
}
|
|
587
|
+
if (snapshot.status === "error") {
|
|
588
|
+
return h("section", { className: "dshCodexPanel dshCodexCard dshCodexAuthorizationCard" },
|
|
589
|
+
h("p", { role: "alert", className: "dshCodexError" }, t("requestFailed")),
|
|
590
|
+
h("button", { type: "button", className: "dshCodexButton", onClick: refresh }, t("refresh")))
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const view = snapshot.value
|
|
594
|
+
const flow = view.flow
|
|
595
|
+
const authorizationBusy = attempt !== null || flow?.inFlight === true
|
|
596
|
+
const actionBusy = cancelling || loggingOut || responding
|
|
597
|
+
const configured = view.credential.configured === true
|
|
598
|
+
const credentialState = new Set(["signed-in", "signed-out", "invalid"])
|
|
599
|
+
.has(view.credential.state)
|
|
600
|
+
? view.credential.state
|
|
601
|
+
: configured ? "signed-in" : "signed-out"
|
|
602
|
+
const signedIn = credentialState === "signed-in"
|
|
603
|
+
const statusText = loggingOut
|
|
604
|
+
? t("signingOut")
|
|
605
|
+
: cancelling
|
|
606
|
+
? t("cancelling")
|
|
607
|
+
: authorizationBusy
|
|
608
|
+
? t("inProgress")
|
|
609
|
+
: credentialState === "invalid"
|
|
610
|
+
? t("credentialInvalid")
|
|
611
|
+
: signedIn
|
|
612
|
+
? t("signedIn")
|
|
613
|
+
: t("signedOut")
|
|
614
|
+
const quota = safeQuotaSnapshot(view.quota)
|
|
615
|
+
const statusTone = authorizationBusy || cancelling || loggingOut
|
|
616
|
+
? "busy"
|
|
617
|
+
: credentialState === "invalid"
|
|
618
|
+
? "invalid"
|
|
619
|
+
: signedIn
|
|
620
|
+
? "active"
|
|
621
|
+
: "inactive"
|
|
622
|
+
const authorizationActions = flow === undefined
|
|
623
|
+
? null
|
|
624
|
+
: h("div", { className: "dshCodexActions dshCodexAuthActions" },
|
|
625
|
+
...flow.methods.map((method) => h("button", {
|
|
626
|
+
key: method.id,
|
|
627
|
+
type: "button",
|
|
628
|
+
className: configured ? "dshCodexButton" : "dshCodexButton dshCodexPrimary",
|
|
629
|
+
disabled: authorizationBusy || actionBusy || view.credential.writable === false,
|
|
630
|
+
onClick: () => void begin(method.id),
|
|
631
|
+
}, configured
|
|
632
|
+
? t("signInAgain")
|
|
633
|
+
: flow.methods.length === 1
|
|
634
|
+
? t("signInWithChatGPT")
|
|
635
|
+
: `${t("signIn")} · ${method.label}`)),
|
|
636
|
+
authorizationBusy ? h("button", {
|
|
637
|
+
type: "button",
|
|
638
|
+
className: "dshCodexButton",
|
|
639
|
+
disabled: actionBusy,
|
|
640
|
+
onClick: () => void cancel(),
|
|
641
|
+
}, t("cancel")) : null,
|
|
642
|
+
configured && !authorizationBusy ? h("button", {
|
|
643
|
+
type: "button",
|
|
644
|
+
className: "dshCodexButton",
|
|
645
|
+
disabled: actionBusy,
|
|
646
|
+
onClick: () => void logout(),
|
|
647
|
+
}, loggingOut ? t("signingOut") : t("signOut")) : null)
|
|
648
|
+
|
|
649
|
+
let quotaBody
|
|
650
|
+
if (quota.status === "recent-success") {
|
|
651
|
+
quotaBody = h(React.Fragment, null,
|
|
652
|
+
h("p", { className: "dshCodexQuotaState" }, `${t("quotaRecentSuccess")} · ${new Date(quota.observedAt).toLocaleString()}`),
|
|
653
|
+
h("p", { className: "dshCodexMuted" }, t("quotaSuccessCaution")))
|
|
654
|
+
} else if (quota.status === "exhausted") {
|
|
655
|
+
quotaBody = h(React.Fragment, null,
|
|
656
|
+
h("p", { className: "dshCodexQuotaState dshCodexQuotaExhausted" }, t("quotaExhausted")),
|
|
657
|
+
h("p", { className: "dshCodexMuted" }, `${t("quotaObservedAt")} · ${new Date(quota.observedAt).toLocaleString()}`),
|
|
658
|
+
quota.resetAt === undefined
|
|
659
|
+
? h("p", { className: "dshCodexMuted" }, t("quotaNoReset"))
|
|
660
|
+
: h("p", { className: "dshCodexMuted" }, `${t("quotaResetAt")} · ${new Date(quota.resetAt).toLocaleString()} · ${t("quotaResetIn")} ${quota.remainingMinutes} ${t("quotaMinutes")}`))
|
|
661
|
+
} else {
|
|
662
|
+
quotaBody = h(React.Fragment, null,
|
|
663
|
+
h("p", { className: "dshCodexQuotaState dshCodexQuotaUnknown" }, t("quotaUnknown")),
|
|
664
|
+
h("p", { className: "dshCodexMuted" }, t("quotaUnknownHelp")))
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return h("section", {
|
|
668
|
+
className: "dshCodexPanel dshCodexCard dshCodexAuthorizationCard",
|
|
669
|
+
"aria-busy": authorizationBusy || actionBusy ? "true" : undefined,
|
|
670
|
+
},
|
|
671
|
+
h("div", { className: "dshCodexAuthToolbar" },
|
|
672
|
+
h("div", {
|
|
673
|
+
className: "dshCodexStatus",
|
|
674
|
+
"data-state": statusTone,
|
|
675
|
+
role: "status",
|
|
676
|
+
"aria-live": "polite",
|
|
677
|
+
},
|
|
678
|
+
h("span", { className: "dshCodexStatusDot", "aria-hidden": "true" }),
|
|
679
|
+
h("strong", null, statusText)),
|
|
680
|
+
authorizationActions),
|
|
681
|
+
credentialState === "invalid"
|
|
682
|
+
? h("p", { role: "alert", className: "dshCodexError" }, t("credentialInvalidHelp"))
|
|
683
|
+
: null,
|
|
684
|
+
flow === undefined
|
|
685
|
+
? h("p", { role: "status", className: "dshCodexMuted" }, t("unavailable"))
|
|
686
|
+
: null,
|
|
687
|
+
view.credential.writable === false
|
|
688
|
+
? h("p", { className: "dshCodexMuted" }, t("writableNo"))
|
|
689
|
+
: null,
|
|
690
|
+
notices.length > 0
|
|
691
|
+
? h("ol", { className: "dshCodexNotices", "aria-live": "polite" },
|
|
692
|
+
...notices.map((notice, index) => h("li", { key: `${index}-${notice.message}` },
|
|
693
|
+
h("p", null, notice.message),
|
|
694
|
+
notice.url === undefined ? null : h("a", {
|
|
695
|
+
href: notice.url,
|
|
696
|
+
target: "_blank",
|
|
697
|
+
rel: "noopener noreferrer",
|
|
698
|
+
}, t("openPage")),
|
|
699
|
+
notice.code === undefined ? null : h("div", { className: "dshCodexCode" },
|
|
700
|
+
h("span", null, t("deviceCode")), h("code", null, notice.code)))))
|
|
701
|
+
: null,
|
|
702
|
+
prompt === null ? null : h("form", {
|
|
703
|
+
className: "dshCodexPrompt",
|
|
704
|
+
onSubmit: (event) => {
|
|
705
|
+
event.preventDefault()
|
|
706
|
+
void reply("answer")
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
h("label", null,
|
|
710
|
+
h("span", null, prompt.value.message),
|
|
711
|
+
prompt.value.kind === "select"
|
|
712
|
+
? h("select", {
|
|
713
|
+
value: answer,
|
|
714
|
+
required: true,
|
|
715
|
+
onChange: (event) => setAnswer(event.currentTarget.value),
|
|
716
|
+
}, h("option", { value: "", disabled: true }, "—"),
|
|
717
|
+
...prompt.value.options.map((option) => h("option", { key: option.id, value: option.id }, option.label)))
|
|
718
|
+
: h("input", {
|
|
719
|
+
type: prompt.value.kind === "secret" ? "password" : "text",
|
|
720
|
+
value: answer,
|
|
721
|
+
placeholder: prompt.value.placeholder,
|
|
722
|
+
autoComplete: "off",
|
|
723
|
+
required: true,
|
|
724
|
+
onChange: (event) => setAnswer(event.currentTarget.value),
|
|
725
|
+
})),
|
|
726
|
+
h("div", { className: "dshCodexActions" },
|
|
727
|
+
h("button", { type: "submit", className: "dshCodexButton dshCodexPrimary", disabled: actionBusy }, t("submit")),
|
|
728
|
+
h("button", { type: "button", className: "dshCodexButton", disabled: actionBusy, onClick: () => void reply("decline") }, t("decline")))),
|
|
729
|
+
outcome === "authorized" ? h("p", { role: "status", className: "dshCodexSuccess" }, t("authorized")) : null,
|
|
730
|
+
outcome === "cancelled" ? h("p", { role: "status", className: "dshCodexMuted" }, t("cancelled")) : null,
|
|
731
|
+
outcome === "commit-in-progress" ? h("p", { role: "status", className: "dshCodexMuted" }, t("commitInProgress")) : null,
|
|
732
|
+
failure === null ? null : h("p", { role: "alert", className: "dshCodexError", "data-error-code": failure }, t(failureCopyKey(failure))),
|
|
733
|
+
h("section", {
|
|
734
|
+
className: "dshCodexQuota",
|
|
735
|
+
"data-quota-status": quota.status,
|
|
736
|
+
"aria-live": "polite",
|
|
737
|
+
"aria-labelledby": "dsh-codex-quota-title",
|
|
738
|
+
},
|
|
739
|
+
h("div", { className: "dshCodexQuotaHeader" },
|
|
740
|
+
h("h3", { id: "dsh-codex-quota-title" }, t("quotaTitle")),
|
|
741
|
+
h("button", {
|
|
742
|
+
type: "button",
|
|
743
|
+
className: "dshCodexButton dshCodexRefreshButton",
|
|
744
|
+
disabled: actionBusy,
|
|
745
|
+
onClick: refresh,
|
|
746
|
+
}, t("refresh"))),
|
|
747
|
+
h("p", { className: "dshCodexQuotaProduct" }, t("quotaProduct")),
|
|
748
|
+
quotaBody))
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function failureCopyKey(failure) {
|
|
752
|
+
if (failure === "CANCEL_FAILED") return "cancelFailed"
|
|
753
|
+
if (failure === "LOGOUT_FAILED") return "logoutFailed"
|
|
754
|
+
if (failure === "RESPONSE_FAILED") return "responseFailed"
|
|
755
|
+
return "failed"
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function ModelEnablementSettings({ client, t }) {
|
|
759
|
+
const [snapshot, setSnapshot] = React.useState(client.available ? { kind: "loading" } : { kind: "unavailable" })
|
|
760
|
+
const [selectedIds, setSelectedIds] = React.useState([])
|
|
761
|
+
const [saving, setSaving] = React.useState(false)
|
|
762
|
+
const [saved, setSaved] = React.useState(false)
|
|
763
|
+
const [failure, setFailure] = React.useState(null)
|
|
764
|
+
const generationRef = React.useRef(0)
|
|
765
|
+
const mountedRef = React.useRef(false)
|
|
766
|
+
|
|
767
|
+
const refresh = React.useCallback(async () => {
|
|
768
|
+
const generation = ++generationRef.current
|
|
769
|
+
setSaved(false)
|
|
770
|
+
setFailure(null)
|
|
771
|
+
setSnapshot({ kind: "loading" })
|
|
772
|
+
try {
|
|
773
|
+
const next = await client.load()
|
|
774
|
+
if (generation !== generationRef.current) return
|
|
775
|
+
setSnapshot(next)
|
|
776
|
+
setSelectedIds(next.kind === "ready" ? next.selectedIds : [])
|
|
777
|
+
} catch (error) {
|
|
778
|
+
if (generation !== generationRef.current) return
|
|
779
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
780
|
+
setSnapshot({ kind: "error", detail })
|
|
781
|
+
}
|
|
782
|
+
}, [client])
|
|
783
|
+
|
|
784
|
+
React.useEffect(() => {
|
|
785
|
+
mountedRef.current = true
|
|
786
|
+
const unsubscribe = client.subscribe(() => void refresh())
|
|
787
|
+
if (client.available) void refresh()
|
|
788
|
+
return () => {
|
|
789
|
+
mountedRef.current = false
|
|
790
|
+
generationRef.current += 1
|
|
791
|
+
unsubscribe()
|
|
792
|
+
}
|
|
793
|
+
}, [client, refresh])
|
|
794
|
+
|
|
795
|
+
const toggle = (id) => {
|
|
796
|
+
const selected = selectedIds.includes(id)
|
|
797
|
+
if (selected && selectedIds.length === 1) {
|
|
798
|
+
setFailure(t("modelsAtLeastOne"))
|
|
799
|
+
return
|
|
800
|
+
}
|
|
801
|
+
setSaved(false)
|
|
802
|
+
setFailure(null)
|
|
803
|
+
setSelectedIds(selected
|
|
804
|
+
? selectedIds.filter((candidate) => candidate !== id)
|
|
805
|
+
: [...selectedIds, id])
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const save = async () => {
|
|
809
|
+
if (!mountedRef.current || snapshot.kind !== "ready") return
|
|
810
|
+
if (selectedIds.length === 0) {
|
|
811
|
+
setFailure(t("modelsAtLeastOne"))
|
|
812
|
+
return
|
|
813
|
+
}
|
|
814
|
+
setSaving(true)
|
|
815
|
+
setSaved(false)
|
|
816
|
+
setFailure(null)
|
|
817
|
+
try {
|
|
818
|
+
const next = await client.save(snapshot, selectedIds)
|
|
819
|
+
if (!mountedRef.current) return
|
|
820
|
+
setSnapshot(next)
|
|
821
|
+
setSelectedIds(next.selectedIds)
|
|
822
|
+
setSaved(true)
|
|
823
|
+
} catch (error) {
|
|
824
|
+
if (!mountedRef.current) return
|
|
825
|
+
const message = error?.code === "AT_LEAST_ONE_MODEL"
|
|
826
|
+
? t("modelsAtLeastOne")
|
|
827
|
+
: `${t("modelsSaveFailed")} ${error instanceof Error ? error.message : String(error)}`
|
|
828
|
+
setFailure(message)
|
|
829
|
+
} finally {
|
|
830
|
+
if (mountedRef.current) setSaving(false)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
let content
|
|
835
|
+
if (snapshot.kind === "loading") {
|
|
836
|
+
content = h("p", { className: "dshCodexMuted", "aria-busy": "true" }, t("modelsLoading"))
|
|
837
|
+
} else if (snapshot.kind === "unavailable") {
|
|
838
|
+
content = h(React.Fragment, null,
|
|
839
|
+
h("p", { role: "status", className: "dshCodexMuted" }, t("modelsUnavailable")),
|
|
840
|
+
client.available ? h("button", { type: "button", className: "dshCodexButton", onClick: refresh }, t("modelsRetry")) : null)
|
|
841
|
+
} else if (snapshot.kind === "error") {
|
|
842
|
+
content = h(React.Fragment, null,
|
|
843
|
+
h("p", { role: "alert", className: "dshCodexError" }, `${t("modelsLoadFailed")} ${snapshot.detail}`),
|
|
844
|
+
h("button", { type: "button", className: "dshCodexButton", onClick: refresh }, t("modelsRetry")))
|
|
845
|
+
} else if (snapshot.models.length === 0) {
|
|
846
|
+
content = h(React.Fragment, null,
|
|
847
|
+
h("p", { role: "status", className: "dshCodexMuted" }, t("modelsEmpty")),
|
|
848
|
+
h("button", { type: "button", className: "dshCodexButton", onClick: refresh }, t("modelsRetry")))
|
|
849
|
+
} else {
|
|
850
|
+
const selected = new Set(selectedIds)
|
|
851
|
+
const original = new Set(snapshot.selectedIds)
|
|
852
|
+
const dirty = selected.size !== original.size || [...selected].some((id) => !original.has(id))
|
|
853
|
+
const plan = modelSavePlan(snapshot, selectedIds)
|
|
854
|
+
const modelItems = snapshot.models.map((model) => h("li", { key: model.id },
|
|
855
|
+
h("label", {
|
|
856
|
+
className: "dshCodexModelOption",
|
|
857
|
+
"data-selected": selected.has(model.id) ? "true" : undefined,
|
|
858
|
+
},
|
|
859
|
+
h("input", {
|
|
860
|
+
type: "checkbox",
|
|
861
|
+
checked: selected.has(model.id),
|
|
862
|
+
disabled: saving || !snapshot.writable,
|
|
863
|
+
onChange: () => toggle(model.id),
|
|
864
|
+
}),
|
|
865
|
+
h("span", null,
|
|
866
|
+
h("strong", null, model.name ?? model.id),
|
|
867
|
+
model.name === undefined || model.name === model.id ? null : h("code", null, model.id)))))
|
|
868
|
+
content = h(React.Fragment, null,
|
|
869
|
+
snapshot.writable ? null : h("p", { className: "dshCodexMuted" }, t("modelsReadOnly")),
|
|
870
|
+
h("ul", {
|
|
871
|
+
className: "dshCodexModelList",
|
|
872
|
+
"aria-labelledby": "dsh-codex-models-title",
|
|
873
|
+
}, ...modelItems),
|
|
874
|
+
plan.allCatalogSelected ? h("p", { className: "dshCodexMuted" }, t(plan.unsetOverride
|
|
875
|
+
? "modelsAllEnabledFollow"
|
|
876
|
+
: "modelsAllEnabledPreserve")) : null,
|
|
877
|
+
h("div", { className: "dshCodexActions dshCodexModelActions" },
|
|
878
|
+
h("button", {
|
|
879
|
+
type: "button",
|
|
880
|
+
className: "dshCodexButton dshCodexPrimary",
|
|
881
|
+
disabled: saving || !snapshot.writable || selected.size === 0 || !dirty,
|
|
882
|
+
onClick: () => void save(),
|
|
883
|
+
}, saving ? t("modelsSaving") : t("modelsSave")),
|
|
884
|
+
h("button", {
|
|
885
|
+
type: "button",
|
|
886
|
+
className: "dshCodexButton",
|
|
887
|
+
disabled: saving,
|
|
888
|
+
onClick: () => void refresh(),
|
|
889
|
+
}, t("modelsRetry"))),
|
|
890
|
+
saved ? h("p", { role: "status", className: "dshCodexSuccess" }, t("modelsSaved")) : null,
|
|
891
|
+
failure === null ? null : h("p", { role: "alert", className: "dshCodexError" }, failure))
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return h("section", { className: "dshCodexPanel dshCodexCard dshCodexModelPanel" },
|
|
895
|
+
h("header", { className: "dshCodexHeader" },
|
|
896
|
+
h("div", null,
|
|
897
|
+
h("h3", { id: "dsh-codex-models-title" }, t("modelsTitle")),
|
|
898
|
+
h("p", { className: "dshCodexWarning" }, t("modelsDescription")))),
|
|
899
|
+
content)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function CodexSettings({ client, modelClient, t }) {
|
|
903
|
+
return h("div", { className: "dshCodexPage" },
|
|
904
|
+
h("header", { className: "dshCodexHero" },
|
|
905
|
+
h("h2", null, t("title")),
|
|
906
|
+
h("p", null, t("description"))),
|
|
907
|
+
h(AuthorizationSettings, { client, t }),
|
|
908
|
+
h(ModelEnablementSettings, { client: modelClient, t }))
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const inject = ["slots", "locale", "connection", "settingsScope"]
|
|
912
|
+
|
|
913
|
+
function apply(ctx) {
|
|
914
|
+
ctx.effect(installStyle, "dsh-codex: client styles")
|
|
915
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-codex: client dictionaries")
|
|
916
|
+
const t = ctx.locale.bind(NS)
|
|
917
|
+
const client = createAuthorizationClient(ctx.connection)
|
|
918
|
+
const modelClient = createModelEnablementClient(ctx.connection, ctx.settingsScope?.describe?.())
|
|
919
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
920
|
+
name: "settings.section",
|
|
921
|
+
id: "codex",
|
|
922
|
+
order: 15,
|
|
923
|
+
label: () => t("nav"),
|
|
924
|
+
locale: NS,
|
|
925
|
+
inject: () => ({ client, modelClient, t }),
|
|
926
|
+
}, CodexSettings))
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function installStyle() {
|
|
930
|
+
if (typeof document === "undefined") return undefined
|
|
931
|
+
let style = document.querySelector(`style[data-plugin-css="${STYLE_ID}"]`)
|
|
932
|
+
if (style === null) {
|
|
933
|
+
style = document.createElement("style")
|
|
934
|
+
style.dataset.plugin = "dsh-codex-community"
|
|
935
|
+
style.dataset.pluginCss = STYLE_ID
|
|
936
|
+
document.head.appendChild(style)
|
|
937
|
+
}
|
|
938
|
+
style.textContent = [
|
|
939
|
+
"[role=dialog]:has(.dshCodexPage){width:min(1180px,calc(100vw - 48px))}",
|
|
940
|
+
".dshCodexPage{box-sizing:border-box;display:flex;width:100%;max-width:1120px;min-width:0;flex-direction:column;gap:28px;padding:6px 0 40px;color:var(--dsw-alias-label-primary)}",
|
|
941
|
+
".dshCodexPage *{box-sizing:border-box}",
|
|
942
|
+
".dshCodexHero{display:flex;min-width:0;flex-direction:column;gap:8px}",
|
|
943
|
+
".dshCodexHero h2,.dshCodexHero p{margin:0}",
|
|
944
|
+
".dshCodexHero h2{font-size:16px;font-weight:500;line-height:24px}",
|
|
945
|
+
".dshCodexHero p{max-width:760px;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
|
|
946
|
+
".dshCodexPanel{display:flex;width:100%;min-width:0;max-width:none;color:var(--dsw-alias-label-primary);flex-direction:column;gap:18px}",
|
|
947
|
+
".dshCodexPanel p,.dshCodexPanel label{overflow-wrap:anywhere;word-break:break-word}",
|
|
948
|
+
".dshCodexCard{border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-3));padding:clamp(24px,3vw,34px)}",
|
|
949
|
+
".dshCodexHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}",
|
|
950
|
+
".dshCodexHeader>div{min-width:0}",
|
|
951
|
+
".dshCodexHeader h3,.dshCodexHeader p,.dshCodexNotices p,.dshCodexQuota p{margin:0}",
|
|
952
|
+
".dshCodexHeader h3{font-size:16px;font-weight:500;line-height:24px}",
|
|
953
|
+
".dshCodexAuthToolbar{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:18px}",
|
|
954
|
+
".dshCodexStatus{display:flex;min-width:0;align-items:center;gap:10px;font-size:14px;line-height:22px}",
|
|
955
|
+
".dshCodexStatus strong{font-weight:500;white-space:nowrap}",
|
|
956
|
+
".dshCodexStatusDot{width:11px;height:11px;flex:none;border-radius:50%;background:var(--dsw-alias-label-tertiary)}",
|
|
957
|
+
".dshCodexStatus[data-state=active] .dshCodexStatusDot{background:var(--dsw-alias-state-success-primary)}",
|
|
958
|
+
".dshCodexStatus[data-state=invalid] .dshCodexStatusDot{background:var(--dsw-alias-state-error-primary)}",
|
|
959
|
+
".dshCodexStatus[data-state=busy] .dshCodexStatusDot{background:var(--dsw-alias-state-business-primary)}",
|
|
960
|
+
".dshCodexQuota{display:flex;min-width:0;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:24px}",
|
|
961
|
+
".dshCodexQuotaHeader{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:12px}",
|
|
962
|
+
".dshCodexQuota h3{margin:0;font-size:16px;font-weight:500;line-height:24px}",
|
|
963
|
+
".dshCodexQuotaProduct{font-size:14px;font-weight:500;line-height:22px}",
|
|
964
|
+
".dshCodexQuotaState{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px}",
|
|
965
|
+
".dshCodexQuotaState::before{width:8px;height:8px;flex:none;border-radius:50%;background:var(--dsw-alias-state-success-primary);content:\"\"}",
|
|
966
|
+
".dshCodexQuotaUnknown::before{background:var(--dsw-alias-label-tertiary)}",
|
|
967
|
+
".dshCodexQuotaExhausted::before{background:var(--dsw-alias-state-error-primary)}",
|
|
968
|
+
".dshCodexQuotaExhausted{color:var(--dsw-alias-state-error-primary)}",
|
|
969
|
+
".dshCodexMuted{color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
|
|
970
|
+
".dshCodexWarning{max-width:820px;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}",
|
|
971
|
+
".dshCodexActions{display:flex;min-width:0;flex-wrap:wrap;gap:8px}",
|
|
972
|
+
".dshCodexAuthActions{margin-left:auto;justify-content:flex-end}",
|
|
973
|
+
".dshCodexAuthActions .dshCodexButton{height:36px;min-height:36px;border-radius:18px;padding:0 14px;font-size:14px;line-height:22px}",
|
|
974
|
+
".dshCodexQuotaHeader .dshCodexRefreshButton{min-height:28px;border:none;border-radius:14px;background:transparent;color:var(--dsw-alias-label-tertiary);padding:0 10px;font-size:12px;line-height:18px}",
|
|
975
|
+
".dshCodexButton{box-sizing:border-box;max-width:100%;white-space:normal;overflow-wrap:anywhere;word-break:break-word;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;padding:8px 14px;transition:background-color .15s ease,border-color .15s ease}",
|
|
976
|
+
".dshCodexButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
|
|
977
|
+
".dshCodexButton:focus-visible,.dshCodexModelOption:has(input:focus-visible){outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-border-l3));outline-offset:2px}",
|
|
978
|
+
".dshCodexButton:disabled{cursor:not-allowed;opacity:.5}",
|
|
979
|
+
".dshCodexPrimary{border-color:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}",
|
|
980
|
+
".dshCodexPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-state-business-primary))}",
|
|
981
|
+
".dshCodexModelList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:36px;row-gap:8px;margin:0;padding:0;list-style:none}",
|
|
982
|
+
".dshCodexModelOption{display:flex;min-width:0;align-items:flex-start;gap:12px;border-radius:10px;padding:10px 8px;cursor:pointer;transition:background-color .15s ease}",
|
|
983
|
+
".dshCodexModelOption:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
|
|
984
|
+
".dshCodexModelOption:has(input:disabled){cursor:not-allowed;opacity:.65}",
|
|
985
|
+
".dshCodexModelOption:has(input:disabled):hover{background:transparent}",
|
|
986
|
+
".dshCodexModelOption input{width:18px;height:18px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-state-business-primary)}",
|
|
987
|
+
".dshCodexModelOption span{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:2px}",
|
|
988
|
+
".dshCodexModelOption strong{overflow-wrap:anywhere;font-size:14px;font-weight:500;line-height:22px}",
|
|
989
|
+
".dshCodexModelOption code{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:12px;line-height:18px}",
|
|
990
|
+
".dshCodexModelActions{justify-content:flex-end;border-top:1px solid var(--dsw-alias-border-l2);padding-top:20px}",
|
|
991
|
+
".dshCodexNotices{display:flex;min-width:0;flex-direction:column;gap:10px;margin:0;padding:0;list-style:none}",
|
|
992
|
+
".dshCodexNotices li,.dshCodexPrompt{box-sizing:border-box;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px}",
|
|
993
|
+
".dshCodexNotices a{display:inline-block;max-width:100%;margin-top:8px;color:var(--dsw-alias-state-business-primary)}",
|
|
994
|
+
".dshCodexCode{display:flex;min-width:0;flex-wrap:wrap;align-items:center;gap:8px;margin-top:8px;color:var(--dsw-alias-label-secondary);font-size:12px}",
|
|
995
|
+
".dshCodexCode code{min-width:0;max-width:100%;overflow-wrap:anywhere;word-break:break-all;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:22px;user-select:all}",
|
|
996
|
+
".dshCodexPrompt{display:flex;flex-direction:column;gap:12px}",
|
|
997
|
+
".dshCodexPrompt label{display:flex;min-width:0;flex-direction:column;gap:8px}",
|
|
998
|
+
".dshCodexPrompt input,.dshCodexPrompt select{box-sizing:border-box;width:100%;min-width:0;height:38px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;padding:0 10px}",
|
|
999
|
+
".dshCodexError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
|
|
1000
|
+
".dshCodexSuccess{margin:0;color:var(--dsw-alias-state-success-primary);font-size:12px;line-height:18px}",
|
|
1001
|
+
"@media(max-width:760px){.dshCodexCard{padding:24px}.dshCodexModelList{grid-template-columns:1fr}.dshCodexModelActions{justify-content:flex-start}}",
|
|
1002
|
+
"@media(max-width:640px){.dshCodexAuthToolbar,.dshCodexHeader{align-items:stretch;flex-direction:column}.dshCodexAuthActions{margin-left:0;justify-content:flex-start}}",
|
|
1003
|
+
"@media(max-width:480px){[role=dialog]:has(.dshCodexPage){flex-direction:column}[role=dialog]:has(.dshCodexPage)>nav{box-sizing:border-box;width:100%;height:auto;flex:none;gap:0;overflow:hidden;padding:10px 12px 8px}[role=dialog]:has(.dshCodexPage)>nav>div:first-child{display:none}[role=dialog]:has(.dshCodexPage)>nav>div:last-child{display:flex;width:100%;flex-direction:row;gap:4px;overflow-x:auto;padding-bottom:2px}[role=dialog]:has(.dshCodexPage)>nav>div:last-child>*{flex:none}[role=dialog]:has(.dshCodexPage)>:not(nav){width:100%;min-width:0;min-height:0;overflow:hidden;flex:1 1 0%}.dshCodexPage{gap:20px;padding:0 0 28px}.dshCodexCard{border-radius:14px;padding:18px}.dshCodexActions{align-items:stretch;flex-direction:column}.dshCodexButton{width:100%}}",
|
|
1004
|
+
].join("")
|
|
1005
|
+
const references = style[STYLE_REF_KEY]
|
|
1006
|
+
style[STYLE_REF_KEY] = Number.isSafeInteger(references) && references > 0
|
|
1007
|
+
? references + 1
|
|
1008
|
+
: 1
|
|
1009
|
+
let active = true
|
|
1010
|
+
return () => {
|
|
1011
|
+
if (!active) return
|
|
1012
|
+
active = false
|
|
1013
|
+
const remaining = style[STYLE_REF_KEY]
|
|
1014
|
+
if (Number.isSafeInteger(remaining) && remaining > 1) {
|
|
1015
|
+
style[STYLE_REF_KEY] = remaining - 1
|
|
1016
|
+
return
|
|
1017
|
+
}
|
|
1018
|
+
delete style[STYLE_REF_KEY]
|
|
1019
|
+
if (typeof style.remove === "function") style.remove()
|
|
1020
|
+
else style.parentNode?.removeChild(style)
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
exports.AuthorizationSettings = AuthorizationSettings
|
|
1025
|
+
exports.CHANNEL = CHANNEL
|
|
1026
|
+
exports.CODEX_PROVIDER = CODEX_PROVIDER
|
|
1027
|
+
exports.CodexSettings = CodexSettings
|
|
1028
|
+
exports.ModelEnablementSettings = ModelEnablementSettings
|
|
1029
|
+
exports.NS = NS
|
|
1030
|
+
exports.apply = apply
|
|
1031
|
+
exports.createAuthorizationClient = createAuthorizationClient
|
|
1032
|
+
exports.createModelEnablementClient = createModelEnablementClient
|
|
1033
|
+
exports.inject = inject
|
|
1034
|
+
exports.safeQuotaSnapshot = safeQuotaSnapshot
|
|
1035
|
+
return module.exports
|
|
1036
|
+
},
|
|
1037
|
+
})
|