@edge-sky/dsh-oauth-adapter 0.1.2-beta.1 → 0.1.2-dev.2
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.i18n.yaml +2 -2
- package/README.md +63 -28
- package/README.zh.md +63 -28
- package/lib/client.js +500 -7
- package/lib/index.d.ts +7 -3
- package/lib/index.js +82 -78
- package/lib/model-auth.d.ts +8 -0
- package/lib/model-auth.js +48 -0
- package/lib/model-catalog.d.ts +27 -0
- package/lib/model-catalog.js +145 -0
- package/lib/model-discovery.d.ts +22 -0
- package/lib/model-discovery.js +147 -0
- package/lib/model-service.d.ts +64 -0
- package/lib/model-service.js +371 -0
- package/lib/model-types.d.ts +92 -0
- package/lib/model-types.js +25 -0
- package/lib/protocol.d.ts +3 -2
- package/lib/protocol.js +27 -0
- package/lib/types/client/OAuthModelsSection.d.ts +11 -0
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/store.d.ts +17 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/model-types.d.ts +92 -0
- package/lib/types/protocol.d.ts +3 -2
- package/package.json +62 -35
package/lib/client.js
CHANGED
|
@@ -6,8 +6,37 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/model-types.ts
|
|
10
|
+
const MODEL_PROTOCOLS = {
|
|
11
|
+
"github-copilot": [
|
|
12
|
+
"openai-completions",
|
|
13
|
+
"openai-responses",
|
|
14
|
+
"anthropic-messages"
|
|
15
|
+
],
|
|
16
|
+
openrouter: ["openai-completions", "anthropic-messages"],
|
|
17
|
+
"openai-codex": ["openai-codex-responses"],
|
|
18
|
+
xai: ["openai-responses"],
|
|
19
|
+
anthropic: ["anthropic-messages"],
|
|
20
|
+
"kimi-coding": ["anthropic-messages"]
|
|
21
|
+
};
|
|
22
|
+
function modelText(value) {
|
|
23
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
24
|
+
}
|
|
25
|
+
/** Validate at the wire and durable-data boundaries; fixed-protocol routes accept no override. */
|
|
26
|
+
function validManual(provider, value) {
|
|
27
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
28
|
+
const m = value;
|
|
29
|
+
if (Object.keys(m).some((k) => ![
|
|
30
|
+
"id",
|
|
31
|
+
"name",
|
|
32
|
+
"api"
|
|
33
|
+
].includes(k)) || !modelText(m.id)) return false;
|
|
34
|
+
if (m.name !== void 0 && !modelText(m.name)) return false;
|
|
35
|
+
const choices = MODEL_PROTOCOLS[provider];
|
|
36
|
+
return choices.length === 1 ? m.api === void 0 : typeof m.api === "string" && choices.includes(m.api);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
9
39
|
//#region src/protocol.ts
|
|
10
|
-
/** Shared browser/Host wire vocabulary for the OAuth account surface. */
|
|
11
40
|
const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
|
|
12
41
|
const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
|
|
13
42
|
const PROVIDERS = [
|
|
@@ -49,6 +78,262 @@ window.__ModuleLoader__.load({
|
|
|
49
78
|
}
|
|
50
79
|
];
|
|
51
80
|
//#endregion
|
|
81
|
+
//#region src/client/OAuthModelsSection.tsx
|
|
82
|
+
/** OAuth-specific model management mounted inside the existing Models settings page. */
|
|
83
|
+
function ModelForm({ provider, initial, busy, t, save, close }) {
|
|
84
|
+
const [id, setId] = (0, react.useState)(initial?.id ?? "");
|
|
85
|
+
const [name, setName] = (0, react.useState)(initial?.name ?? "");
|
|
86
|
+
const choices = MODEL_PROTOCOLS[provider];
|
|
87
|
+
const [api, setApi] = (0, react.useState)(initial?.api ?? choices[0]);
|
|
88
|
+
const model = {
|
|
89
|
+
id: id.trim(),
|
|
90
|
+
...name.trim() ? { name: name.trim() } : {},
|
|
91
|
+
...choices.length > 1 ? { api } : {}
|
|
92
|
+
};
|
|
93
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
94
|
+
className: "dsh-oauth-model-form",
|
|
95
|
+
onSubmit: (event) => {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
if (validManual(provider, model)) save(model);
|
|
98
|
+
},
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelId"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
101
|
+
required: true,
|
|
102
|
+
maxLength: 256,
|
|
103
|
+
value: id,
|
|
104
|
+
readOnly: initial !== void 0,
|
|
105
|
+
onChange: (event) => setId(event.target.value)
|
|
106
|
+
})] }),
|
|
107
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelName"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
108
|
+
maxLength: 256,
|
|
109
|
+
value: name,
|
|
110
|
+
onChange: (event) => setName(event.target.value),
|
|
111
|
+
placeholder: id
|
|
112
|
+
})] }),
|
|
113
|
+
choices.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("modelProtocol"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
114
|
+
value: api,
|
|
115
|
+
onChange: (event) => setApi(event.target.value),
|
|
116
|
+
children: choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
117
|
+
value: choice,
|
|
118
|
+
children: choice === "openai-completions" ? "Chat Completions" : choice === "openai-responses" ? "Responses" : choice === "openai-codex-responses" ? "Codex Responses" : "Anthropic Messages"
|
|
119
|
+
}, choice))
|
|
120
|
+
})] }),
|
|
121
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
122
|
+
className: "dsh-oauth-muted",
|
|
123
|
+
children: t("manualModelHint")
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
126
|
+
type: "submit",
|
|
127
|
+
disabled: busy || !validManual(provider, model),
|
|
128
|
+
children: t("saveModel")
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
131
|
+
type: "button",
|
|
132
|
+
disabled: busy,
|
|
133
|
+
onClick: close,
|
|
134
|
+
children: t("cancel")
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function ProviderModels({ page, controller, busy, error, t }) {
|
|
140
|
+
const [editing, setEditing] = (0, react.useState)();
|
|
141
|
+
const [submitted, setSubmitted] = (0, react.useState)(false);
|
|
142
|
+
const [confirmMigration, setConfirmMigration] = (0, react.useState)(false);
|
|
143
|
+
(0, react.useEffect)(() => {
|
|
144
|
+
if (submitted && !busy) {
|
|
145
|
+
if (!error) setEditing(void 0);
|
|
146
|
+
setSubmitted(false);
|
|
147
|
+
}
|
|
148
|
+
}, [
|
|
149
|
+
busy,
|
|
150
|
+
error,
|
|
151
|
+
submitted
|
|
152
|
+
]);
|
|
153
|
+
const errorCode = error?.code ?? page.errorCode;
|
|
154
|
+
const errorLabel = errorCode === "settings-conflict" ? "modelConflict" : errorCode === "discovery-unsupported" ? "modelUnsupported" : errorCode === "discovery-auth" ? "modelAuthFailed" : errorCode === "discovery-timeout" ? "modelTimedOut" : errorCode === "route-conflict" ? "modelRouteConflict" : "modelOperationFailed";
|
|
155
|
+
const disabled = busy || !page.connected || !page.writable;
|
|
156
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
157
|
+
className: "dsh-oauth-model-provider",
|
|
158
|
+
children: [
|
|
159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: PROVIDERS.find((p) => p.id === page.provider)?.modelGroupLabel }),
|
|
160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
161
|
+
page.connected ? t("modelConnected") : t("disconnected"),
|
|
162
|
+
" · ",
|
|
163
|
+
page.source === "account" ? t("accountCatalog") : t("builtinCatalog")
|
|
164
|
+
] }),
|
|
165
|
+
page.syncedAt && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
166
|
+
t("lastModelSync"),
|
|
167
|
+
": ",
|
|
168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
169
|
+
dateTime: page.syncedAt,
|
|
170
|
+
children: new Date(page.syncedAt).toLocaleString()
|
|
171
|
+
})
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
174
|
+
t("matchedModels"),
|
|
175
|
+
": ",
|
|
176
|
+
page.counts.matched,
|
|
177
|
+
" · ",
|
|
178
|
+
t("pendingModels"),
|
|
179
|
+
": ",
|
|
180
|
+
page.counts.pending,
|
|
181
|
+
" · ",
|
|
182
|
+
t("manualModels"),
|
|
183
|
+
": ",
|
|
184
|
+
page.counts.manual
|
|
185
|
+
] }),
|
|
186
|
+
(error || page.error) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
187
|
+
role: "alert",
|
|
188
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(errorLabel) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("technicalDetails") }), error?.message ?? page.error] })]
|
|
189
|
+
}),
|
|
190
|
+
page.migration ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
191
|
+
disabled,
|
|
192
|
+
onClick: () => setConfirmMigration(!confirmMigration),
|
|
193
|
+
children: t("reviewMigration")
|
|
194
|
+
}), confirmMigration && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("migrationDescription") }),
|
|
196
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
197
|
+
page.migration.fields.join(", "),
|
|
198
|
+
" · ",
|
|
199
|
+
page.migration.models,
|
|
200
|
+
" ",
|
|
201
|
+
t("modelsTitle")
|
|
202
|
+
] }),
|
|
203
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
204
|
+
disabled,
|
|
205
|
+
onClick: () => controller.migrateModels(page.provider),
|
|
206
|
+
children: t("confirmMigration")
|
|
207
|
+
})
|
|
208
|
+
] })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
209
|
+
disabled: disabled || !page.managed,
|
|
210
|
+
onClick: () => controller.syncModels(page.provider),
|
|
211
|
+
children: busy ? t("modelWorking") : t("syncModels")
|
|
212
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
213
|
+
disabled: disabled || !page.managed,
|
|
214
|
+
onClick: () => setEditing(null),
|
|
215
|
+
children: t("addModel")
|
|
216
|
+
})] }),
|
|
217
|
+
editing !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelForm, {
|
|
218
|
+
provider: page.provider,
|
|
219
|
+
...editing === null ? {} : { initial: editing },
|
|
220
|
+
busy,
|
|
221
|
+
t,
|
|
222
|
+
save: (model) => {
|
|
223
|
+
setSubmitted(true);
|
|
224
|
+
controller.saveModel(page.provider, model);
|
|
225
|
+
},
|
|
226
|
+
close: () => setEditing(void 0)
|
|
227
|
+
}, editing?.id ?? "new"),
|
|
228
|
+
[
|
|
229
|
+
"matched",
|
|
230
|
+
"pending",
|
|
231
|
+
"manual"
|
|
232
|
+
].map((source) => {
|
|
233
|
+
const rows = page.rows.filter((row) => (row.source === "catalog" ? "matched" : row.source) === source);
|
|
234
|
+
if (!rows.length) return null;
|
|
235
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
236
|
+
"aria-label": t(source === "pending" ? "pendingModels" : source === "manual" ? "manualModels" : "matchedModels"),
|
|
237
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t(source === "pending" ? "pendingModels" : source === "manual" ? "manualModels" : "matchedModels") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
238
|
+
className: "dsh-oauth-model-list",
|
|
239
|
+
children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.name }),
|
|
242
|
+
" ",
|
|
243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: row.id }),
|
|
244
|
+
" ",
|
|
245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(row.source === "pending" ? "pendingModels" : row.source === "manual" ? "manualModels" : "matchedModels") })
|
|
246
|
+
] }),
|
|
247
|
+
row.source === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
248
|
+
disabled: disabled || !page.managed,
|
|
249
|
+
onClick: () => setEditing({
|
|
250
|
+
id: row.id,
|
|
251
|
+
name: row.name
|
|
252
|
+
}),
|
|
253
|
+
children: t("configureModel")
|
|
254
|
+
}),
|
|
255
|
+
row.source === "manual" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
256
|
+
disabled,
|
|
257
|
+
onClick: () => setEditing({
|
|
258
|
+
id: row.id,
|
|
259
|
+
name: row.name,
|
|
260
|
+
...MODEL_PROTOCOLS[page.provider].length > 1 ? { api: row.api } : {}
|
|
261
|
+
}),
|
|
262
|
+
children: t("editModel")
|
|
263
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
264
|
+
disabled,
|
|
265
|
+
onClick: () => controller.deleteModel(page.provider, row.id),
|
|
266
|
+
children: t("deleteModel")
|
|
267
|
+
})] })
|
|
268
|
+
] }, row.id))
|
|
269
|
+
})]
|
|
270
|
+
}, source);
|
|
271
|
+
}),
|
|
272
|
+
page.total === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("noModels") }),
|
|
273
|
+
page.total > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
|
|
274
|
+
"aria-label": t("modelsPagination"),
|
|
275
|
+
children: [
|
|
276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
277
|
+
disabled: page.offset === 0 || busy,
|
|
278
|
+
onClick: () => controller.readModels(page.provider, Math.max(0, page.offset - 10)),
|
|
279
|
+
children: t("previousModels")
|
|
280
|
+
}),
|
|
281
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
282
|
+
page.offset + 1,
|
|
283
|
+
"–",
|
|
284
|
+
Math.min(page.offset + 10, page.total),
|
|
285
|
+
" / ",
|
|
286
|
+
page.total
|
|
287
|
+
] }),
|
|
288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
289
|
+
disabled: page.offset + 10 >= page.total || busy,
|
|
290
|
+
onClick: () => controller.readModels(page.provider, page.offset + 10),
|
|
291
|
+
children: t("nextModels")
|
|
292
|
+
})
|
|
293
|
+
]
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function OAuthModelsSection(props) {
|
|
299
|
+
const { controller, t } = props;
|
|
300
|
+
if (!controller || !t) return null;
|
|
301
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OAuthModels, {
|
|
302
|
+
controller,
|
|
303
|
+
t
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function OAuthModels({ controller, t }) {
|
|
307
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
308
|
+
(0, react.useEffect)(() => {
|
|
309
|
+
controller.loadModels();
|
|
310
|
+
}, [controller]);
|
|
311
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
312
|
+
className: "dsh-oauth-section",
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("modelsTitle") }),
|
|
315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("modelsIntro") }),
|
|
316
|
+
snapshot.connection !== "open" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
317
|
+
role: "status",
|
|
318
|
+
children: t("hostDisconnected")
|
|
319
|
+
}),
|
|
320
|
+
PROVIDERS.map((provider) => {
|
|
321
|
+
const page = snapshot.modelPages?.[provider.id];
|
|
322
|
+
if (!page) return null;
|
|
323
|
+
if (!page.connected && !page.managed && page.counts.manual === 0) return null;
|
|
324
|
+
const error = snapshot.modelErrors?.[provider.id];
|
|
325
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderModels, {
|
|
326
|
+
page,
|
|
327
|
+
controller,
|
|
328
|
+
t,
|
|
329
|
+
busy: snapshot.connection !== "open" || snapshot.modelBusy?.[provider.id] === true || page.busy,
|
|
330
|
+
...error ? { error } : {}
|
|
331
|
+
}, provider.id);
|
|
332
|
+
})
|
|
333
|
+
]
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
52
337
|
//#region src/client/OAuthAccountsSection.tsx
|
|
53
338
|
/** OAuth account settings page. */
|
|
54
339
|
function safeHttpUrl(value) {
|
|
@@ -179,7 +464,7 @@ window.__ModuleLoader__.load({
|
|
|
179
464
|
t: props.t
|
|
180
465
|
}, props.attempt.prompt.id),
|
|
181
466
|
props.attempt.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorPanel, {
|
|
182
|
-
message: props.attempt.error,
|
|
467
|
+
message: props.attempt.phase === "authorized" ? props.t("loginSyncFailed") : props.attempt.error,
|
|
183
468
|
...props.attempt.errorDetail === void 0 ? {} : { detail: props.attempt.errorDetail },
|
|
184
469
|
t: props.t
|
|
185
470
|
}),
|
|
@@ -297,6 +582,40 @@ window.__ModuleLoader__.load({
|
|
|
297
582
|
//#region src/client/locales.ts
|
|
298
583
|
/** English copy for the OAuth accounts page. */
|
|
299
584
|
const en = {
|
|
585
|
+
modelUnsupported: "This provider does not support account discovery with the current credential. You can still add models manually.",
|
|
586
|
+
modelAuthFailed: "Model discovery was denied. Reconnect or check this account’s permissions. The previous list was retained.",
|
|
587
|
+
modelTimedOut: "Model synchronization timed out. The previous list was retained.",
|
|
588
|
+
modelRouteConflict: "This provider is owned by an existing configuration. Review the details before migration.",
|
|
589
|
+
loginSyncFailed: "Sign-in succeeded, but model synchronization failed. Open Models to retry.",
|
|
590
|
+
modelsTitle: "OAuth models",
|
|
591
|
+
modelsIntro: "Synchronize account-visible models or add a model manually. Manual models survive synchronization.",
|
|
592
|
+
modelId: "Model ID",
|
|
593
|
+
modelName: "Display name",
|
|
594
|
+
modelProtocol: "Request protocol",
|
|
595
|
+
manualModelHint: "Saving configures a request; it does not verify account access or model capabilities.",
|
|
596
|
+
saveModel: "Save model",
|
|
597
|
+
modelConnected: "Connected",
|
|
598
|
+
accountCatalog: "Account-synchronized list",
|
|
599
|
+
builtinCatalog: "Built-in list · account access unverified",
|
|
600
|
+
lastModelSync: "Last successful synchronization",
|
|
601
|
+
matchedModels: "Matched",
|
|
602
|
+
pendingModels: "Needs configuration",
|
|
603
|
+
manualModels: "Manual",
|
|
604
|
+
modelConflict: "Models changed elsewhere. The list was refreshed; review it and retry.",
|
|
605
|
+
modelOperationFailed: "The model operation failed. Review the details before retrying.",
|
|
606
|
+
reviewMigration: "Review migration",
|
|
607
|
+
migrationDescription: "Move this OAuth provider configuration to plugin management while preserving its ID and credentials. The following configuration fields will be retained:",
|
|
608
|
+
confirmMigration: "Migrate this provider",
|
|
609
|
+
modelWorking: "Working…",
|
|
610
|
+
syncModels: "Synchronize models",
|
|
611
|
+
addModel: "Add model",
|
|
612
|
+
configureModel: "Configure",
|
|
613
|
+
editModel: "Edit",
|
|
614
|
+
deleteModel: "Delete manual entry",
|
|
615
|
+
noModels: "No selectable models. Configure an unmatched model or add one manually.",
|
|
616
|
+
modelsPagination: "OAuth model pages",
|
|
617
|
+
previousModels: "Previous",
|
|
618
|
+
nextModels: "Next",
|
|
300
619
|
nav: "OAuth Accounts",
|
|
301
620
|
title: "OAuth Accounts",
|
|
302
621
|
intro: "Connect subscription-backed providers. Their models appear in separate OAuth groups, apart from API-key providers.",
|
|
@@ -329,6 +648,40 @@ window.__ModuleLoader__.load({
|
|
|
329
648
|
};
|
|
330
649
|
/** Simplified Chinese copy for the OAuth accounts page. */
|
|
331
650
|
const zh = {
|
|
651
|
+
modelUnsupported: "当前凭据不支持查询该供应商的账号模型列表,仍可手动添加模型。",
|
|
652
|
+
modelAuthFailed: "模型查询被拒绝,请重新连接或检查账号权限。已保留之前的列表。",
|
|
653
|
+
modelTimedOut: "模型同步超时,已保留之前的列表。",
|
|
654
|
+
modelRouteConflict: "该供应商由现有配置管理,请查看详情后再迁移。",
|
|
655
|
+
loginSyncFailed: "登录成功,但模型同步失败。请前往模型页面重试。",
|
|
656
|
+
modelsTitle: "OAuth 模型",
|
|
657
|
+
modelsIntro: "同步账号可见模型,或手动添加模型。同步时会保留手动模型。",
|
|
658
|
+
modelId: "模型 ID",
|
|
659
|
+
modelName: "显示名称",
|
|
660
|
+
modelProtocol: "调用协议",
|
|
661
|
+
manualModelHint: "保存后可以尝试调用,但不代表已经验证账号权限或模型能力。",
|
|
662
|
+
saveModel: "保存模型",
|
|
663
|
+
modelConnected: "已连接",
|
|
664
|
+
accountCatalog: "已同步账号列表",
|
|
665
|
+
builtinCatalog: "内置列表 · 尚未验证账号权限",
|
|
666
|
+
lastModelSync: "最近成功同步",
|
|
667
|
+
matchedModels: "已匹配",
|
|
668
|
+
pendingModels: "待配置",
|
|
669
|
+
manualModels: "手动模型",
|
|
670
|
+
modelConflict: "模型已被其他操作修改。列表已刷新,请确认后重试。",
|
|
671
|
+
modelOperationFailed: "模型操作失败,请查看详情后重试。",
|
|
672
|
+
reviewMigration: "预览迁移",
|
|
673
|
+
migrationDescription: "将此 OAuth 供应商配置迁移到插件管理,保留供应商 ID 和凭据。以下配置字段将被保留:",
|
|
674
|
+
confirmMigration: "迁移此供应商",
|
|
675
|
+
modelWorking: "正在处理…",
|
|
676
|
+
syncModels: "同步模型",
|
|
677
|
+
addModel: "手动添加",
|
|
678
|
+
configureModel: "补充配置",
|
|
679
|
+
editModel: "编辑",
|
|
680
|
+
deleteModel: "删除手动项",
|
|
681
|
+
noModels: "暂无可选模型,请补充待配置项或手动添加。",
|
|
682
|
+
modelsPagination: "OAuth 模型分页",
|
|
683
|
+
previousModels: "上一页",
|
|
684
|
+
nextModels: "下一页",
|
|
332
685
|
nav: "OAuth 账户",
|
|
333
686
|
title: "OAuth 账户",
|
|
334
687
|
intro: "连接订阅型模型服务。OAuth 模型会以独立分组显示,与 API 密钥提供商分开。",
|
|
@@ -395,6 +748,9 @@ window.__ModuleLoader__.load({
|
|
|
395
748
|
reconnectTimer;
|
|
396
749
|
reconnectDelay = 500;
|
|
397
750
|
disposed = false;
|
|
751
|
+
modelsVisible = false;
|
|
752
|
+
modelRequests = /* @__PURE__ */ new Map();
|
|
753
|
+
latestModelReads = /* @__PURE__ */ new Map();
|
|
398
754
|
constructor() {
|
|
399
755
|
this.connect();
|
|
400
756
|
}
|
|
@@ -453,6 +809,69 @@ window.__ModuleLoader__.load({
|
|
|
453
809
|
provider
|
|
454
810
|
});
|
|
455
811
|
}
|
|
812
|
+
loadModels() {
|
|
813
|
+
this.modelsVisible = true;
|
|
814
|
+
for (const provider of this.snapshot.accounts) this.readModels(provider.provider);
|
|
815
|
+
}
|
|
816
|
+
readModels(provider, offset = 0) {
|
|
817
|
+
if (this.snapshot.connection !== "open") return;
|
|
818
|
+
const requestId = crypto.randomUUID();
|
|
819
|
+
this.latestModelReads.set(provider, requestId);
|
|
820
|
+
this.modelRequests.set(requestId, {
|
|
821
|
+
provider,
|
|
822
|
+
write: false
|
|
823
|
+
});
|
|
824
|
+
this.send({
|
|
825
|
+
type: "models-list",
|
|
826
|
+
requestId,
|
|
827
|
+
provider,
|
|
828
|
+
offset
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
syncModels(provider) {
|
|
832
|
+
this.writeModels(provider, { type: "models-sync" });
|
|
833
|
+
}
|
|
834
|
+
migrateModels(provider) {
|
|
835
|
+
this.writeModels(provider, { type: "models-migrate" });
|
|
836
|
+
}
|
|
837
|
+
saveModel(provider, model) {
|
|
838
|
+
this.writeModels(provider, {
|
|
839
|
+
type: "models-save",
|
|
840
|
+
model
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
deleteModel(provider, id) {
|
|
844
|
+
this.writeModels(provider, {
|
|
845
|
+
type: "models-delete",
|
|
846
|
+
id
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
writeModels(provider, operation) {
|
|
850
|
+
const page = this.snapshot.modelPages?.[provider];
|
|
851
|
+
if (!page || this.snapshot.modelBusy?.[provider] || this.snapshot.connection !== "open") return;
|
|
852
|
+
const requestId = crypto.randomUUID();
|
|
853
|
+
this.modelRequests.set(requestId, {
|
|
854
|
+
provider,
|
|
855
|
+
write: true
|
|
856
|
+
});
|
|
857
|
+
this.update({
|
|
858
|
+
...this.snapshot,
|
|
859
|
+
modelBusy: {
|
|
860
|
+
...this.snapshot.modelBusy,
|
|
861
|
+
[provider]: true
|
|
862
|
+
},
|
|
863
|
+
modelErrors: {
|
|
864
|
+
...this.snapshot.modelErrors,
|
|
865
|
+
[provider]: void 0
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
this.send({
|
|
869
|
+
...operation,
|
|
870
|
+
requestId,
|
|
871
|
+
provider,
|
|
872
|
+
revision: page.revision
|
|
873
|
+
});
|
|
874
|
+
}
|
|
456
875
|
retry() {
|
|
457
876
|
if (this.socket?.readyState === WebSocket.OPEN || this.socket?.readyState === WebSocket.CONNECTING) return;
|
|
458
877
|
if (this.reconnectTimer !== void 0) window.clearTimeout(this.reconnectTimer);
|
|
@@ -483,6 +902,12 @@ window.__ModuleLoader__.load({
|
|
|
483
902
|
...current,
|
|
484
903
|
connection: "open"
|
|
485
904
|
});
|
|
905
|
+
this.modelRequests.clear();
|
|
906
|
+
this.latestModelReads.clear();
|
|
907
|
+
this.update({
|
|
908
|
+
...this.snapshot,
|
|
909
|
+
modelBusy: {}
|
|
910
|
+
});
|
|
486
911
|
this.send({
|
|
487
912
|
type: "refresh",
|
|
488
913
|
requestId: crypto.randomUUID()
|
|
@@ -551,6 +976,60 @@ window.__ModuleLoader__.load({
|
|
|
551
976
|
}
|
|
552
977
|
receive(message) {
|
|
553
978
|
switch (message.type) {
|
|
979
|
+
case "models-changed":
|
|
980
|
+
if (this.modelsVisible) this.readModels(message.provider, this.snapshot.modelPages?.[message.provider]?.offset ?? 0);
|
|
981
|
+
return;
|
|
982
|
+
case "models-page": {
|
|
983
|
+
const request = this.modelRequests.get(message.requestId);
|
|
984
|
+
this.modelRequests.delete(message.requestId);
|
|
985
|
+
if (!request) return;
|
|
986
|
+
if (!request.write && this.latestModelReads.get(request.provider) !== message.requestId) return;
|
|
987
|
+
const current = this.snapshot.modelPages?.[request.provider];
|
|
988
|
+
if (current && message.page.revision < current.revision) {
|
|
989
|
+
if (request.write) this.update({
|
|
990
|
+
...this.snapshot,
|
|
991
|
+
modelBusy: {
|
|
992
|
+
...this.snapshot.modelBusy,
|
|
993
|
+
[request.provider]: false
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
this.update({
|
|
999
|
+
...this.snapshot,
|
|
1000
|
+
modelPages: {
|
|
1001
|
+
...this.snapshot.modelPages,
|
|
1002
|
+
[request.provider]: message.page
|
|
1003
|
+
},
|
|
1004
|
+
modelBusy: {
|
|
1005
|
+
...this.snapshot.modelBusy,
|
|
1006
|
+
...request.write ? { [request.provider]: false } : {}
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
case "models-error": {
|
|
1012
|
+
const request = this.modelRequests.get(message.requestId);
|
|
1013
|
+
this.modelRequests.delete(message.requestId);
|
|
1014
|
+
if (!request) return;
|
|
1015
|
+
if (!request.write && this.latestModelReads.get(request.provider) !== message.requestId) return;
|
|
1016
|
+
this.update({
|
|
1017
|
+
...this.snapshot,
|
|
1018
|
+
modelBusy: {
|
|
1019
|
+
...this.snapshot.modelBusy,
|
|
1020
|
+
...request.write ? { [request.provider]: false } : {}
|
|
1021
|
+
},
|
|
1022
|
+
modelErrors: {
|
|
1023
|
+
...this.snapshot.modelErrors,
|
|
1024
|
+
[request.provider]: {
|
|
1025
|
+
code: message.code,
|
|
1026
|
+
message: message.message
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
if (request.write) this.readModels(request.provider);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
554
1033
|
case "snapshot":
|
|
555
1034
|
const accounts = [...message.accounts];
|
|
556
1035
|
const reconciled = reconcileAttemptView(this.snapshot.attempt, accounts);
|
|
@@ -561,6 +1040,7 @@ window.__ModuleLoader__.load({
|
|
|
561
1040
|
if (reconciled === void 0) delete nextSnapshot.attempt;
|
|
562
1041
|
else nextSnapshot.attempt = reconciled;
|
|
563
1042
|
this.update(nextSnapshot);
|
|
1043
|
+
if (this.modelsVisible) for (const account of accounts) this.readModels(account.provider, this.snapshot.modelPages?.[account.provider]?.offset ?? 0);
|
|
564
1044
|
return;
|
|
565
1045
|
case "started":
|
|
566
1046
|
const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
|
|
@@ -671,11 +1151,19 @@ window.__ModuleLoader__.load({
|
|
|
671
1151
|
.dsh-oauth-actions{display:flex;gap:6px;margin-left:auto}.dsh-oauth-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}.dsh-oauth-button[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]{border-color:transparent;color:var(--dsw-alias-state-error-primary)}.dsh-oauth-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-button[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.dsh-oauth-button:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.dsh-oauth-button:disabled{cursor:not-allowed;opacity:.4}
|
|
672
1152
|
.dsh-oauth-flow{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l3);font-size:13px;line-height:20px}.dsh-oauth-link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);text-decoration:none}.dsh-oauth-link:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-link-label{font-weight:500}.dsh-oauth-link-host{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsh-oauth-link-icon{color:var(--dsw-alias-brand-text)}.dsh-oauth-code{display:flex;align-items:center;gap:8px}.dsh-oauth-code code{padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-module-platform);font-size:14px;letter-spacing:.08em}
|
|
673
1153
|
.dsh-oauth-prompt{display:flex;flex-direction:column;gap:7px}.dsh-oauth-input,.dsh-oauth-select{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
|
|
1154
|
+
|
|
1155
|
+
.dsh-oauth-model-provider { border: 1px solid var(--border-color, currentColor); border-radius: 8px; padding: 16px; margin-block: 16px; }
|
|
1156
|
+
.dsh-oauth-model-provider button { margin: 4px; }
|
|
1157
|
+
.dsh-oauth-model-list { padding: 0; list-style: none; }
|
|
1158
|
+
.dsh-oauth-model-list li { padding-block: 8px; border-bottom: 1px solid var(--border-color, currentColor); overflow-wrap: anywhere; }
|
|
1159
|
+
.dsh-oauth-model-list code { margin-inline: 8px; }
|
|
1160
|
+
.dsh-oauth-model-form { display: grid; gap: 12px; margin-block: 12px; }
|
|
1161
|
+
.dsh-oauth-model-form label { display: grid; gap: 4px; }
|
|
1162
|
+
.dsh-oauth-model-provider nav { display: flex; align-items: center; gap: 8px; }
|
|
674
1163
|
`;
|
|
675
1164
|
//#endregion
|
|
676
1165
|
//#region src/client/index.ts
|
|
677
1166
|
const NS = "settings.oauth-accounts";
|
|
678
|
-
const MODEL_SETTINGS_VISIBILITY = "modelSettingsVisibility";
|
|
679
1167
|
/** Required browser services for settings placement and localized copy. */
|
|
680
1168
|
const inject = ["slots", "locale"];
|
|
681
1169
|
/** Register the OAuth account page and its app-lifetime connection controller. */
|
|
@@ -697,11 +1185,16 @@ window.__ModuleLoader__.load({
|
|
|
697
1185
|
ctx.effect(() => () => {
|
|
698
1186
|
controller.dispose();
|
|
699
1187
|
}, "dsh-oauth-adapter: controller");
|
|
700
|
-
ctx.inject([MODEL_SETTINGS_VISIBILITY], (scope) => {
|
|
701
|
-
const visibility = scope.get(MODEL_SETTINGS_VISIBILITY);
|
|
702
|
-
scope.effect(() => visibility.registerHiddenProviders(PROVIDERS.map((provider) => provider.id)), "dsh-oauth-adapter: hide OAuth providers from Models settings");
|
|
703
|
-
});
|
|
704
1188
|
const t = ctx.locale.bind(NS);
|
|
1189
|
+
ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
|
|
1190
|
+
name: "settings.models.footer",
|
|
1191
|
+
id: "oauth-models",
|
|
1192
|
+
order: 10,
|
|
1193
|
+
inject: () => ({
|
|
1194
|
+
controller,
|
|
1195
|
+
t
|
|
1196
|
+
})
|
|
1197
|
+
}, OAuthModelsSection));
|
|
705
1198
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
706
1199
|
name: "settings.section",
|
|
707
1200
|
id: "oauth-accounts",
|
package/lib/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* `dsh-llm-pi-ai` plugin.
|
|
4
4
|
* @module @edge-sky/dsh-oauth-adapter
|
|
5
5
|
*/
|
|
6
|
+
import { type ModelContext } from './model-service.js';
|
|
6
7
|
import type { IncomingMessage } from 'node:http';
|
|
7
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
8
8
|
import type { AuthorizationService } from '@deepseek-ai/dsh-authorization';
|
|
9
9
|
import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
|
|
10
10
|
import type { WebServer } from '@deepseek-ai/dsh-host-webserver';
|
|
@@ -14,6 +14,10 @@ import z from '@deepseek-ai/schemastery';
|
|
|
14
14
|
export interface Config {
|
|
15
15
|
/** Emit secret-free transport lifecycle diagnostics. */
|
|
16
16
|
debug?: boolean;
|
|
17
|
+
modelSyncTimeoutMs?: number;
|
|
18
|
+
modelSyncMaxResponseBytes?: number;
|
|
19
|
+
modelSyncMaxPages?: number;
|
|
20
|
+
codexClientVersion?: string;
|
|
17
21
|
}
|
|
18
22
|
/** Runtime-validated plugin configuration. */
|
|
19
23
|
export declare const Config: z<Config>;
|
|
@@ -21,7 +25,7 @@ export declare const Config: z<Config>;
|
|
|
21
25
|
export declare const name = "dsh-oauth-adapter";
|
|
22
26
|
/** Official services required by the OAuth account surface. */
|
|
23
27
|
export declare const inject: string[];
|
|
24
|
-
type OAuthContext =
|
|
28
|
+
type OAuthContext = ModelContext & {
|
|
25
29
|
authorization: AuthorizationService;
|
|
26
30
|
credentials: CredentialProvider;
|
|
27
31
|
settings: SettingsProvider;
|
|
@@ -35,7 +39,7 @@ export declare function safeNoticeUrl(value: string | undefined): string | undef
|
|
|
35
39
|
export declare function acceptsBrowserUpgrade(request: IncomingMessage): boolean;
|
|
36
40
|
/** Format one Host failure for browser diagnostics without forwarding credentials or unbounded data. */
|
|
37
41
|
export declare function formatErrorDetail(error: unknown): string;
|
|
38
|
-
/** Mount the loopback-only OAuth WebSocket over official
|
|
42
|
+
/** Mount the loopback-only OAuth WebSocket over official rc services. */
|
|
39
43
|
export declare function apply(ctx: OAuthContext, config: Config): () => Promise<void>;
|
|
40
44
|
export {};
|
|
41
45
|
//# sourceMappingURL=index.d.ts.map
|