paseo-plugin-zcode-provider 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +29 -0
- package/README.md +112 -2
- package/client/account.tsx +827 -0
- package/client/diagnostics.tsx +379 -0
- package/client/runtime.tsx +296 -0
- package/client/settings.tsx +84 -0
- package/client/value.tsx +20 -0
- package/icon.svg +1 -0
- package/index.client.tsx +22 -0
- package/index.server.ts +39 -0
- package/package.json +77 -4
- package/paseo-plugin.json +6 -0
- package/server/account.ts +781 -0
- package/server/attachments.ts +150 -0
- package/server/build-info.ts +2 -0
- package/server/conversation.ts +319 -0
- package/server/diagnostics.ts +252 -0
- package/server/discovery/discover.ts +225 -0
- package/server/discovery/manifest.ts +36 -0
- package/server/discovery/types.ts +30 -0
- package/server/errors.ts +36 -0
- package/server/host/bridge.ts +490 -0
- package/server/host/schemas.ts +119 -0
- package/server/logger.ts +19 -0
- package/server/mapping.ts +270 -0
- package/server/models.ts +68 -0
- package/server/persistence.ts +146 -0
- package/server/presentation.ts +470 -0
- package/server/provider-status.ts +81 -0
- package/server/provider.ts +838 -0
- package/server/runtime/managed.ts +501 -0
- package/server/runtime/pins.ts +72 -0
- package/server/runtime-setup.ts +77 -0
- package/server/session-types.ts +49 -0
- package/server/session.ts +866 -0
- package/server/status.ts +89 -0
- package/server/timeline.ts +18 -0
- package/server/usage.ts +362 -0
- package/server/vendor/zcode/LICENSE +202 -0
- package/server/vendor/zcode/NOTICE.md +70 -0
- package/server/vendor/zcode/THIRD-PARTY-NOTICES.md +37 -0
- package/server/vendor/zcode/packages/model-option-map/src/compiler.ts +92 -0
- package/server/vendor/zcode/packages/model-option-map/src/evaluator.ts +214 -0
- package/server/vendor/zcode/packages/model-option-map/src/index.ts +5 -0
- package/server/vendor/zcode/packages/model-option-map/src/merge-patch.ts +97 -0
- package/server/vendor/zcode/packages/model-option-map/src/option-maps.ts +42 -0
- package/server/vendor/zcode/packages/model-option-map/src/parser.ts +240 -0
- package/server/vendor/zcode/packages/model-option-map/src/tokenizer.ts +141 -0
- package/server/vendor/zcode/packages/model-option-map/src/types.ts +36 -0
- package/server/vendor/zcode/packages/rpc/src/buffer.ts +88 -0
- package/server/vendor/zcode/packages/rpc/src/channelClient.ts +275 -0
- package/server/vendor/zcode/packages/rpc/src/channelServer.ts +283 -0
- package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +66 -0
- package/server/vendor/zcode/packages/rpc/src/channels.ts +4 -0
- package/server/vendor/zcode/packages/rpc/src/delayedChannel.ts +17 -0
- package/server/vendor/zcode/packages/rpc/src/foundation.ts +260 -0
- package/server/vendor/zcode/packages/rpc/src/protocol.ts +397 -0
- package/server/vendor/zcode/packages/rpc/src/proxy-channel.ts +213 -0
- package/server/vendor/zcode/packages/rpc/src/serialization.ts +291 -0
- package/server/vendor/zcode/packages/shared/src/account-provider-state.ts +13 -0
- package/server/vendor/zcode/packages/shared/src/background-bash-output.ts +24 -0
- package/server/vendor/zcode/packages/shared/src/background-task-controls.ts +404 -0
- package/server/vendor/zcode/packages/shared/src/bash-output-display.ts +11 -0
- package/server/vendor/zcode/packages/shared/src/bots.ts +558 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/backend.ts +80 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/command-metadata.ts +170 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +588 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/result.ts +130 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/snapshot.ts +77 -0
- package/server/vendor/zcode/packages/shared/src/command-types.ts +96 -0
- package/server/vendor/zcode/packages/shared/src/config-schema.ts +8 -0
- package/server/vendor/zcode/packages/shared/src/cuaAccessibilitySettings.ts +41 -0
- package/server/vendor/zcode/packages/shared/src/database-startup.ts +178 -0
- package/server/vendor/zcode/packages/shared/src/env.ts +63 -0
- package/server/vendor/zcode/packages/shared/src/execution-output-preview.ts +14 -0
- package/server/vendor/zcode/packages/shared/src/localTtft.ts +192 -0
- package/server/vendor/zcode/packages/shared/src/model-config.ts +123 -0
- package/server/vendor/zcode/packages/shared/src/model-execution.ts +23 -0
- package/server/vendor/zcode/packages/shared/src/model-provider-family.ts +171 -0
- package/server/vendor/zcode/packages/shared/src/model-provider-types.ts +81 -0
- package/server/vendor/zcode/packages/shared/src/model-selection.ts +60 -0
- package/server/vendor/zcode/packages/shared/src/oauth.ts +203 -0
- package/server/vendor/zcode/packages/shared/src/official-mcp-auth.ts +272 -0
- package/server/vendor/zcode/packages/shared/src/official-mcp-tool-error.ts +68 -0
- package/server/vendor/zcode/packages/shared/src/process-diagnostic.ts +42 -0
- package/server/vendor/zcode/packages/shared/src/protocol.ts +369 -0
- package/server/vendor/zcode/packages/shared/src/provider-family-connection-selection.ts +32 -0
- package/server/vendor/zcode/packages/shared/src/remoteAssetInstallMode.ts +11 -0
- package/server/vendor/zcode/packages/shared/src/remoteResourcePackages.ts +91 -0
- package/server/vendor/zcode/packages/shared/src/runtimeEnv.ts +315 -0
- package/server/vendor/zcode/packages/shared/src/settings-source.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/usage-quota.ts +74 -0
- package/server/vendor/zcode/packages/shared/src/usage-stats.ts +462 -0
- package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +562 -0
- package/server/vendor/zcode/packages/shared/src/workspacePurpose.ts +2 -0
- package/server/vendor/zcode/packages/shared/src/wslUserValidation.ts +33 -0
- package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +25 -0
- package/server/vendor/zcode/packages/shared/src/zcode-media-policy.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +3720 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +529 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +218 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +14 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +482 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/core.ts +100 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +156 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/cuaPermission.ts +41 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +147 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +69 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +432 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/session-config.ts +43 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +195 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +85 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-import.ts +38 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-ref.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +509 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +5 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +264 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +1152 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +574 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-binary.ts +65 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-codec.ts +240 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +39 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire.ts +159 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +291 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-observation-display.ts +281 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +163 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +62 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +517 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +512 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-config.ts +81 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-hook-review.ts +210 -0
- package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +6 -0
- package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +1187 -0
- package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +296 -0
- package/server/vendor/zcode/provenance.json +405 -0
- package/shared/account.ts +201 -0
- package/shared/diagnostics.ts +52 -0
- package/shared/runtime-setup.ts +73 -0
- package/vendor/acp-registry/LICENSE +176 -0
- package/vendor/paseo/LICENSE +211 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
accountChangeSchema,
|
|
4
|
+
apiFormatSchema,
|
|
5
|
+
planModeSchema,
|
|
6
|
+
type AccountChange,
|
|
7
|
+
type AccountFamily,
|
|
8
|
+
type AccountView,
|
|
9
|
+
type ReadyAccountView,
|
|
10
|
+
} from "../shared/account.js";
|
|
11
|
+
import { AdapterError } from "./errors.js";
|
|
12
|
+
import type { SettingsChannel } from "./host/bridge.js";
|
|
13
|
+
import { logger } from "./logger.js";
|
|
14
|
+
|
|
15
|
+
/** The official services this screen uses, on a ZCode Server it owns. */
|
|
16
|
+
export interface SettingsHost {
|
|
17
|
+
invoke<Schema extends z.ZodType>(
|
|
18
|
+
channel: SettingsChannel,
|
|
19
|
+
method: string,
|
|
20
|
+
args: readonly unknown[],
|
|
21
|
+
resultSchema: Schema,
|
|
22
|
+
timeoutMs?: number,
|
|
23
|
+
): Promise<z.output<Schema>>;
|
|
24
|
+
onFailure(listener: (error: AdapterError) => void): () => void;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AccountServiceOptions {
|
|
29
|
+
/** Closes the Server after this long without requests or a pending sign-in. */
|
|
30
|
+
readonly idleMs?: number;
|
|
31
|
+
readonly pollMs?: number;
|
|
32
|
+
readonly now?: () => number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ZCode keeps one pending flow per Server process for at most five minutes.
|
|
36
|
+
const SIGN_IN_LIFETIME_MS = 5 * 60_000;
|
|
37
|
+
const record = z.record(z.string(), z.unknown());
|
|
38
|
+
const toApiFormat = (value: unknown) => {
|
|
39
|
+
const parsed = apiFormatSchema.safeParse(value);
|
|
40
|
+
return parsed.success ? parsed.data : undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const nativeConfigSchema = z.object({
|
|
44
|
+
group: z.string().nullish(),
|
|
45
|
+
visibility: z.string().nullish(),
|
|
46
|
+
access: z
|
|
47
|
+
.object({
|
|
48
|
+
type: z.string(),
|
|
49
|
+
apiKey: z.string().nullish(),
|
|
50
|
+
apiKeyManagementUrl: z.string().nullish(),
|
|
51
|
+
mode: z.string().nullish(),
|
|
52
|
+
accountType: z.string().nullish(),
|
|
53
|
+
entitled: z.boolean().nullish(),
|
|
54
|
+
})
|
|
55
|
+
.nullish(),
|
|
56
|
+
api: z
|
|
57
|
+
.object({ type: z.string().nullish(), baseUrl: z.string().nullish() })
|
|
58
|
+
.nullish(),
|
|
59
|
+
builtinModelIds: z.array(z.string()).nullish(),
|
|
60
|
+
});
|
|
61
|
+
const nativeProviderSchema = z.object({
|
|
62
|
+
providerId: z.string(),
|
|
63
|
+
providerName: z.string().nullish(),
|
|
64
|
+
templateId: z.string().nullish(),
|
|
65
|
+
enabled: z.boolean(),
|
|
66
|
+
executable: z.boolean(),
|
|
67
|
+
accountState: z
|
|
68
|
+
.object({
|
|
69
|
+
availability: z.string(),
|
|
70
|
+
entitled: z.boolean(),
|
|
71
|
+
unavailableReason: z.string().nullish(),
|
|
72
|
+
})
|
|
73
|
+
.optional(),
|
|
74
|
+
// Kept on the daemon only: it may contain the API key.
|
|
75
|
+
personalConfig: record.optional(),
|
|
76
|
+
effectiveConfig: nativeConfigSchema,
|
|
77
|
+
issues: z.array(
|
|
78
|
+
z.object({ code: z.string(), path: z.array(z.unknown()).optional() }),
|
|
79
|
+
),
|
|
80
|
+
models: z.array(
|
|
81
|
+
z.object({
|
|
82
|
+
modelId: z.string(),
|
|
83
|
+
enabled: z.boolean(),
|
|
84
|
+
selectable: z.boolean(),
|
|
85
|
+
builtin: z.boolean(),
|
|
86
|
+
effectiveConfig: z
|
|
87
|
+
.object({
|
|
88
|
+
properties: z
|
|
89
|
+
.object({
|
|
90
|
+
contextWindow: z.number().optional(),
|
|
91
|
+
inputFormat: z
|
|
92
|
+
.object({ supportsImage: z.boolean().optional() })
|
|
93
|
+
.optional(),
|
|
94
|
+
})
|
|
95
|
+
.optional(),
|
|
96
|
+
})
|
|
97
|
+
.optional(),
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
});
|
|
101
|
+
export const nativeViewSchema = z.object({
|
|
102
|
+
providerTemplates: z.array(
|
|
103
|
+
z.object({
|
|
104
|
+
templateId: z.string(),
|
|
105
|
+
templateNameMap: z.record(z.string(), z.string()),
|
|
106
|
+
config: nativeConfigSchema,
|
|
107
|
+
}),
|
|
108
|
+
),
|
|
109
|
+
providers: z.array(nativeProviderSchema),
|
|
110
|
+
});
|
|
111
|
+
export type NativeView = z.output<typeof nativeViewSchema>;
|
|
112
|
+
export type NativeProvider = z.output<typeof nativeProviderSchema>;
|
|
113
|
+
|
|
114
|
+
export const sessionStateSchema = z.discriminatedUnion("status", [
|
|
115
|
+
z.object({
|
|
116
|
+
status: z.literal("authenticated"),
|
|
117
|
+
userInfo: z.object({
|
|
118
|
+
/** The account's backend user ID ("unknown" when ZCode has none). */
|
|
119
|
+
id: z.string().optional(),
|
|
120
|
+
username: z.string().optional(),
|
|
121
|
+
displayName: z.string().optional(),
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
z.object({ status: z.literal("signed-out") }),
|
|
125
|
+
z.object({ status: z.literal("reauthentication-required") }),
|
|
126
|
+
]);
|
|
127
|
+
export const familySchema = z.enum(["zai", "bigmodel"]).nullable();
|
|
128
|
+
export const accountSettingsSchema = z.object({
|
|
129
|
+
providerFamilyDomain: z.string().optional(),
|
|
130
|
+
providerFamilyConnectionSelections: record.optional(),
|
|
131
|
+
});
|
|
132
|
+
const startedSchema = z.object({
|
|
133
|
+
authorizeUrl: z
|
|
134
|
+
.string()
|
|
135
|
+
.url()
|
|
136
|
+
.refine((value) => new URL(value).protocol === "https:"),
|
|
137
|
+
});
|
|
138
|
+
const pollSchema = z
|
|
139
|
+
.object({ kind: z.string(), provider: z.string().optional() })
|
|
140
|
+
.nullable();
|
|
141
|
+
|
|
142
|
+
type SignIn = ReadyAccountView["signIn"];
|
|
143
|
+
|
|
144
|
+
function issueText(issue: NativeProvider["issues"][number]): string {
|
|
145
|
+
const field = issue.path?.at(-1);
|
|
146
|
+
if (issue.code === "required-field-missing") {
|
|
147
|
+
if (field === "apiKey") return "API key is missing";
|
|
148
|
+
if (field === "baseUrl" || field === "api") return "Base URL is missing";
|
|
149
|
+
}
|
|
150
|
+
return `ZCode reported ${issue.code}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const isCustom = (provider: NativeProvider) =>
|
|
154
|
+
provider.effectiveConfig.group === "standard-personal";
|
|
155
|
+
|
|
156
|
+
function project(
|
|
157
|
+
view: NativeView,
|
|
158
|
+
account: ReadyAccountView["account"],
|
|
159
|
+
family: AccountFamily | undefined,
|
|
160
|
+
signIn: SignIn,
|
|
161
|
+
): ReadyAccountView {
|
|
162
|
+
const visible = view.providers.filter(
|
|
163
|
+
(provider) => provider.effectiveConfig.visibility !== "hidden",
|
|
164
|
+
);
|
|
165
|
+
return {
|
|
166
|
+
status: "ready",
|
|
167
|
+
account,
|
|
168
|
+
signIn,
|
|
169
|
+
plans:
|
|
170
|
+
account.state === "signed-out" || family === undefined
|
|
171
|
+
? []
|
|
172
|
+
: visible
|
|
173
|
+
.filter(
|
|
174
|
+
(provider) =>
|
|
175
|
+
provider.effectiveConfig.group === `${family}-family` &&
|
|
176
|
+
provider.effectiveConfig.access?.type === "zhipu-account",
|
|
177
|
+
)
|
|
178
|
+
.flatMap((provider) => {
|
|
179
|
+
const mode = planModeSchema.safeParse(
|
|
180
|
+
provider.effectiveConfig.access?.mode,
|
|
181
|
+
);
|
|
182
|
+
if (!mode.success) return [];
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
providerId: provider.providerId,
|
|
186
|
+
name: provider.providerName ?? provider.providerId,
|
|
187
|
+
mode: mode.data,
|
|
188
|
+
availability:
|
|
189
|
+
provider.accountState?.availability ?? "unknown",
|
|
190
|
+
entitled: provider.accountState?.entitled ?? false,
|
|
191
|
+
...(provider.accountState?.unavailableReason
|
|
192
|
+
? {
|
|
193
|
+
unavailableReason:
|
|
194
|
+
provider.accountState.unavailableReason,
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
197
|
+
models: provider.models
|
|
198
|
+
.filter((model) => model.selectable)
|
|
199
|
+
.map((model) => model.modelId),
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
}),
|
|
203
|
+
providers: visible.filter(isCustom).map((provider) => {
|
|
204
|
+
const { access, api } = provider.effectiveConfig;
|
|
205
|
+
const format = toApiFormat(api?.type);
|
|
206
|
+
return {
|
|
207
|
+
providerId: provider.providerId,
|
|
208
|
+
name: provider.providerName ?? provider.providerId,
|
|
209
|
+
...(provider.templateId ? { templateId: provider.templateId } : {}),
|
|
210
|
+
enabled: provider.enabled,
|
|
211
|
+
executable: provider.executable,
|
|
212
|
+
...(format ? { apiFormat: format } : {}),
|
|
213
|
+
...(api?.baseUrl ? { baseUrl: api.baseUrl } : {}),
|
|
214
|
+
apiKeySet: Boolean(access?.apiKey?.trim()),
|
|
215
|
+
...(access?.apiKeyManagementUrl
|
|
216
|
+
? { apiKeyManagementUrl: access.apiKeyManagementUrl }
|
|
217
|
+
: {}),
|
|
218
|
+
issues: provider.issues.map(issueText),
|
|
219
|
+
models: provider.models.map((model) => {
|
|
220
|
+
const properties = model.effectiveConfig?.properties;
|
|
221
|
+
return {
|
|
222
|
+
modelId: model.modelId,
|
|
223
|
+
enabled: model.enabled,
|
|
224
|
+
selectable: model.selectable,
|
|
225
|
+
builtin: model.builtin,
|
|
226
|
+
...(properties?.contextWindow === undefined
|
|
227
|
+
? {}
|
|
228
|
+
: { contextWindow: properties.contextWindow }),
|
|
229
|
+
vision: properties?.inputFormat?.supportsImage === true,
|
|
230
|
+
};
|
|
231
|
+
}),
|
|
232
|
+
};
|
|
233
|
+
}),
|
|
234
|
+
templates: view.providerTemplates.map((template) => {
|
|
235
|
+
const { access, api } = template.config;
|
|
236
|
+
const format = toApiFormat(api?.type);
|
|
237
|
+
return {
|
|
238
|
+
templateId: template.templateId,
|
|
239
|
+
name:
|
|
240
|
+
template.templateNameMap["en-US"] ??
|
|
241
|
+
template.templateNameMap["zh-CN"] ??
|
|
242
|
+
template.templateId,
|
|
243
|
+
...(format ? { apiFormat: format } : {}),
|
|
244
|
+
...(api?.baseUrl ? { baseUrl: api.baseUrl } : {}),
|
|
245
|
+
...(access?.apiKeyManagementUrl
|
|
246
|
+
? { apiKeyManagementUrl: access.apiKeyManagementUrl }
|
|
247
|
+
: {}),
|
|
248
|
+
};
|
|
249
|
+
}),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Account sign-in and model providers through the official ZCode services.
|
|
255
|
+
* Desktop uses the same calls, so ZCode validates and stores everything: the
|
|
256
|
+
* plugin keeps no credentials and never returns API keys to clients.
|
|
257
|
+
*/
|
|
258
|
+
export class AccountService {
|
|
259
|
+
private host?: Promise<SettingsHost>;
|
|
260
|
+
private active = 0;
|
|
261
|
+
private idleTimer?: ReturnType<typeof setTimeout>;
|
|
262
|
+
private pollTimer?: ReturnType<typeof setTimeout>;
|
|
263
|
+
private signIn: SignIn = { state: "idle" };
|
|
264
|
+
private queue: Promise<unknown> = Promise.resolve();
|
|
265
|
+
private closed = false;
|
|
266
|
+
private readonly listeners = new Set<() => void>();
|
|
267
|
+
private readonly idleMs: number;
|
|
268
|
+
private readonly pollMs: number;
|
|
269
|
+
private readonly now: () => number;
|
|
270
|
+
|
|
271
|
+
constructor(
|
|
272
|
+
private readonly startHost: () => Promise<SettingsHost>,
|
|
273
|
+
options: AccountServiceOptions = {},
|
|
274
|
+
) {
|
|
275
|
+
this.idleMs = options.idleMs ?? 60_000;
|
|
276
|
+
this.pollMs = options.pollMs ?? 1_000;
|
|
277
|
+
this.now = options.now ?? Date.now;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
view(): Promise<AccountView> {
|
|
281
|
+
return this.run((host) => this.read(host));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async change(input: AccountChange): Promise<AccountView> {
|
|
285
|
+
const change = accountChangeSchema.parse(input);
|
|
286
|
+
return this.run(async (host) => {
|
|
287
|
+
try {
|
|
288
|
+
await this.apply(host, change);
|
|
289
|
+
} finally {
|
|
290
|
+
this.changed();
|
|
291
|
+
}
|
|
292
|
+
return this.read(host);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Runs a read on the shared Server, after any pending account request.
|
|
298
|
+
* Rejects when the Server cannot start.
|
|
299
|
+
*/
|
|
300
|
+
use<T>(operation: (host: SettingsHost) => Promise<T>): Promise<T> {
|
|
301
|
+
return this.enqueue(async () => operation(await this.acquire()));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Called after sign-in, sign-out and provider edits. */
|
|
305
|
+
onChange(listener: () => void): () => void {
|
|
306
|
+
this.listeners.add(listener);
|
|
307
|
+
return () => this.listeners.delete(listener);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private changed(): void {
|
|
311
|
+
for (const listener of this.listeners) listener();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async close(): Promise<void> {
|
|
315
|
+
this.closed = true;
|
|
316
|
+
clearTimeout(this.idleTimer);
|
|
317
|
+
clearTimeout(this.pollTimer);
|
|
318
|
+
const host = this.host;
|
|
319
|
+
this.host = undefined;
|
|
320
|
+
await host?.then((value) => value.close()).catch(() => {});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Requests run one at a time so sign-in completion, sign-out and provider
|
|
324
|
+
// edits cannot interleave on the shared Server.
|
|
325
|
+
private run(
|
|
326
|
+
operation: (host: SettingsHost) => Promise<AccountView>,
|
|
327
|
+
): Promise<AccountView> {
|
|
328
|
+
return this.enqueue(async (): Promise<AccountView> => {
|
|
329
|
+
let host: SettingsHost;
|
|
330
|
+
try {
|
|
331
|
+
host = await this.acquire();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
logger.error("zcode.account.host.failed", error);
|
|
334
|
+
return {
|
|
335
|
+
status: "unavailable",
|
|
336
|
+
code: error instanceof AdapterError ? error.code : "HOST_FAILED",
|
|
337
|
+
message:
|
|
338
|
+
error instanceof AdapterError
|
|
339
|
+
? error.message
|
|
340
|
+
: "ZCode Server could not be started",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return operation(host);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private enqueue<T>(operation: () => Promise<T>): Promise<T> {
|
|
348
|
+
const result = this.queue.then(async (): Promise<T> => {
|
|
349
|
+
if (this.closed)
|
|
350
|
+
throw new AdapterError("NATIVE_EXITED", "ZCode settings are closed");
|
|
351
|
+
this.active += 1;
|
|
352
|
+
clearTimeout(this.idleTimer);
|
|
353
|
+
try {
|
|
354
|
+
return await operation();
|
|
355
|
+
} finally {
|
|
356
|
+
this.active -= 1;
|
|
357
|
+
this.scheduleIdle();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
this.queue = result.catch(() => {});
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private acquire(): Promise<SettingsHost> {
|
|
365
|
+
if (this.host) return this.host;
|
|
366
|
+
const started = this.startHost().then((host) => {
|
|
367
|
+
host.onFailure(() => this.drop(started));
|
|
368
|
+
return host;
|
|
369
|
+
});
|
|
370
|
+
this.host = started;
|
|
371
|
+
started.catch(() => {
|
|
372
|
+
if (this.host === started) this.host = undefined;
|
|
373
|
+
});
|
|
374
|
+
return started;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private drop(host: Promise<SettingsHost>): void {
|
|
378
|
+
if (this.host !== host) return;
|
|
379
|
+
this.host = undefined;
|
|
380
|
+
if (this.signIn.state === "pending") {
|
|
381
|
+
clearTimeout(this.pollTimer);
|
|
382
|
+
this.signIn = {
|
|
383
|
+
state: "failed",
|
|
384
|
+
message: "ZCode Server stopped during sign-in. Try again.",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private scheduleIdle(): void {
|
|
390
|
+
clearTimeout(this.idleTimer);
|
|
391
|
+
if (this.closed || this.active > 0 || this.signIn.state === "pending")
|
|
392
|
+
return;
|
|
393
|
+
this.idleTimer = setTimeout(() => {
|
|
394
|
+
if (this.active > 0 || this.signIn.state === "pending") return;
|
|
395
|
+
const host = this.host;
|
|
396
|
+
this.host = undefined;
|
|
397
|
+
void host?.then((value) => value.close()).catch(() => {});
|
|
398
|
+
}, this.idleMs);
|
|
399
|
+
this.idleTimer.unref?.();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private async read(host: SettingsHost): Promise<AccountView> {
|
|
403
|
+
const [session, active, settings, view] = await Promise.all([
|
|
404
|
+
host.invoke("oauth", "restoreCachedSessionState", [], sessionStateSchema),
|
|
405
|
+
host.invoke("oauth", "getActiveProvider", [], familySchema),
|
|
406
|
+
host.invoke("setting", "get", [], accountSettingsSchema),
|
|
407
|
+
host.invoke("provider-settings", "getView", [], nativeViewSchema),
|
|
408
|
+
]);
|
|
409
|
+
const domain = familySchema.safeParse(
|
|
410
|
+
settings.providerFamilyDomain || null,
|
|
411
|
+
);
|
|
412
|
+
const family = (domain.success ? domain.data : null) ?? active ?? undefined;
|
|
413
|
+
const account: ReadyAccountView["account"] =
|
|
414
|
+
session.status === "signed-out"
|
|
415
|
+
? { state: "signed-out" }
|
|
416
|
+
: {
|
|
417
|
+
state:
|
|
418
|
+
session.status === "authenticated"
|
|
419
|
+
? "signed-in"
|
|
420
|
+
: "reauthentication-required",
|
|
421
|
+
...(family ? { family } : {}),
|
|
422
|
+
...(session.status === "authenticated" &&
|
|
423
|
+
session.userInfo.displayName
|
|
424
|
+
? { displayName: session.userInfo.displayName }
|
|
425
|
+
: {}),
|
|
426
|
+
...(session.status === "authenticated" && session.userInfo.username
|
|
427
|
+
? { username: session.userInfo.username }
|
|
428
|
+
: {}),
|
|
429
|
+
};
|
|
430
|
+
return project(view, account, family, this.signIn);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private async apply(
|
|
434
|
+
host: SettingsHost,
|
|
435
|
+
change: z.output<typeof accountChangeSchema>,
|
|
436
|
+
): Promise<void> {
|
|
437
|
+
switch (change.action) {
|
|
438
|
+
case "signIn": {
|
|
439
|
+
const started = await host.invoke(
|
|
440
|
+
"oauth",
|
|
441
|
+
"startOAuthWithPolling",
|
|
442
|
+
[change.family],
|
|
443
|
+
startedSchema,
|
|
444
|
+
);
|
|
445
|
+
this.signIn = {
|
|
446
|
+
state: "pending",
|
|
447
|
+
family: change.family,
|
|
448
|
+
authorizeUrl: started.authorizeUrl,
|
|
449
|
+
expiresAt: this.now() + SIGN_IN_LIFETIME_MS,
|
|
450
|
+
};
|
|
451
|
+
this.schedulePoll();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
case "cancelSignIn":
|
|
455
|
+
clearTimeout(this.pollTimer);
|
|
456
|
+
this.signIn = { state: "idle" };
|
|
457
|
+
await host.invoke("oauth", "cancelPending", [], z.unknown());
|
|
458
|
+
return;
|
|
459
|
+
case "signOut":
|
|
460
|
+
clearTimeout(this.pollTimer);
|
|
461
|
+
this.signIn = { state: "idle" };
|
|
462
|
+
// Desktop's sign-out: the OAuth logout also removes derived plan keys.
|
|
463
|
+
await host.invoke("oauth", "logout", [], z.unknown());
|
|
464
|
+
await host.invoke(
|
|
465
|
+
"setting",
|
|
466
|
+
"update",
|
|
467
|
+
[
|
|
468
|
+
{
|
|
469
|
+
providerFamilyDomain: "",
|
|
470
|
+
providerFamilyDomainUpdatedAt: this.now(),
|
|
471
|
+
providerFamilyDomainMigrated: true,
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
z.unknown(),
|
|
475
|
+
);
|
|
476
|
+
await this.refresh(host, "oauth-logout");
|
|
477
|
+
return;
|
|
478
|
+
case "createProvider":
|
|
479
|
+
return this.createProvider(host, change);
|
|
480
|
+
case "updateProvider": {
|
|
481
|
+
const provider = await this.customProvider(host, change.providerId);
|
|
482
|
+
await this.mutate(
|
|
483
|
+
host,
|
|
484
|
+
"savePersonalProviderOverlay",
|
|
485
|
+
[
|
|
486
|
+
provider.providerId,
|
|
487
|
+
this.personalConfig(provider, change),
|
|
488
|
+
{
|
|
489
|
+
...(change.name === undefined
|
|
490
|
+
? {}
|
|
491
|
+
: { providerName: change.name }),
|
|
492
|
+
...(change.enabled === undefined
|
|
493
|
+
? {}
|
|
494
|
+
: { enabled: change.enabled }),
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
z.unknown(),
|
|
498
|
+
);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
case "deleteProvider":
|
|
502
|
+
await this.customProvider(host, change.providerId);
|
|
503
|
+
await this.mutate(
|
|
504
|
+
host,
|
|
505
|
+
"deletePersonalProvider",
|
|
506
|
+
[change.providerId],
|
|
507
|
+
z.unknown(),
|
|
508
|
+
);
|
|
509
|
+
return;
|
|
510
|
+
case "addModel":
|
|
511
|
+
await this.customProvider(host, change.providerId);
|
|
512
|
+
await this.mutate(
|
|
513
|
+
host,
|
|
514
|
+
"addPersonalModel",
|
|
515
|
+
[change.providerId, change.modelId, {}],
|
|
516
|
+
z.unknown(),
|
|
517
|
+
);
|
|
518
|
+
return;
|
|
519
|
+
case "deleteModel":
|
|
520
|
+
await this.customProvider(host, change.providerId);
|
|
521
|
+
await this.mutate(
|
|
522
|
+
host,
|
|
523
|
+
"deletePersonalModel",
|
|
524
|
+
[change.providerId, change.modelId],
|
|
525
|
+
z.unknown(),
|
|
526
|
+
);
|
|
527
|
+
return;
|
|
528
|
+
case "setModelEnabled":
|
|
529
|
+
await this.customProvider(host, change.providerId);
|
|
530
|
+
await this.mutate(
|
|
531
|
+
host,
|
|
532
|
+
"setPersonalModelEnabled",
|
|
533
|
+
[change.providerId, change.modelId, change.enabled],
|
|
534
|
+
z.unknown(),
|
|
535
|
+
);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private async createProvider(
|
|
541
|
+
host: SettingsHost,
|
|
542
|
+
change: Extract<
|
|
543
|
+
z.output<typeof accountChangeSchema>,
|
|
544
|
+
{ action: "createProvider" }
|
|
545
|
+
>,
|
|
546
|
+
): Promise<void> {
|
|
547
|
+
if (change.templateId !== undefined) {
|
|
548
|
+
const view = await host.invoke(
|
|
549
|
+
"provider-settings",
|
|
550
|
+
"getView",
|
|
551
|
+
[],
|
|
552
|
+
nativeViewSchema,
|
|
553
|
+
);
|
|
554
|
+
if (
|
|
555
|
+
!view.providerTemplates.some(
|
|
556
|
+
(template) => template.templateId === change.templateId,
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
throw new AdapterError(
|
|
560
|
+
"INVALID_CONFIGURATION",
|
|
561
|
+
"Unknown provider template",
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
const created = await this.mutate(
|
|
565
|
+
host,
|
|
566
|
+
"createPersonalProvider",
|
|
567
|
+
[
|
|
568
|
+
{
|
|
569
|
+
...(change.templateId ? { templateId: change.templateId } : {}),
|
|
570
|
+
providerName: change.name,
|
|
571
|
+
locale: "en-US",
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
z.object({ providerId: z.string(), view: nativeViewSchema }),
|
|
575
|
+
);
|
|
576
|
+
const provider = created.view.providers.find(
|
|
577
|
+
(entry) => entry.providerId === created.providerId,
|
|
578
|
+
);
|
|
579
|
+
try {
|
|
580
|
+
if (!provider)
|
|
581
|
+
throw new AdapterError(
|
|
582
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
583
|
+
"ZCode did not return the new provider",
|
|
584
|
+
);
|
|
585
|
+
await this.mutate(
|
|
586
|
+
host,
|
|
587
|
+
"savePersonalProviderOverlay",
|
|
588
|
+
[
|
|
589
|
+
created.providerId,
|
|
590
|
+
this.personalConfig(provider, change),
|
|
591
|
+
{ providerName: change.name },
|
|
592
|
+
],
|
|
593
|
+
z.unknown(),
|
|
594
|
+
);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
// Do not leave an unusable entry behind when ZCode rejects the details.
|
|
597
|
+
await host
|
|
598
|
+
.invoke(
|
|
599
|
+
"provider-settings",
|
|
600
|
+
"deletePersonalProvider",
|
|
601
|
+
[created.providerId],
|
|
602
|
+
z.unknown(),
|
|
603
|
+
)
|
|
604
|
+
.catch(() => {});
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Applies only the edited leaves to the stored personal rule, as Desktop
|
|
610
|
+
// does, so hidden headers and inherited template values are preserved.
|
|
611
|
+
private personalConfig(
|
|
612
|
+
provider: NativeProvider,
|
|
613
|
+
change: {
|
|
614
|
+
apiFormat?: string;
|
|
615
|
+
baseUrl?: string;
|
|
616
|
+
apiKey?: string;
|
|
617
|
+
},
|
|
618
|
+
): Record<string, unknown> {
|
|
619
|
+
const personal = { ...provider.personalConfig };
|
|
620
|
+
const effective = provider.effectiveConfig;
|
|
621
|
+
if (change.apiKey !== undefined) {
|
|
622
|
+
personal.access = {
|
|
623
|
+
...record.catch({}).parse(personal.access ?? {}),
|
|
624
|
+
type: effective.access?.type ?? "api-key",
|
|
625
|
+
apiKey: change.apiKey,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const typeChanged =
|
|
629
|
+
change.apiFormat !== undefined &&
|
|
630
|
+
change.apiFormat !== (effective.api?.type ?? undefined);
|
|
631
|
+
const urlChanged =
|
|
632
|
+
change.baseUrl !== undefined &&
|
|
633
|
+
change.baseUrl !== (effective.api?.baseUrl ?? undefined);
|
|
634
|
+
if (typeChanged || urlChanged) {
|
|
635
|
+
personal.api = {
|
|
636
|
+
...record.catch({}).parse(personal.api ?? {}),
|
|
637
|
+
...(typeChanged || !effective.api?.type
|
|
638
|
+
? { type: change.apiFormat }
|
|
639
|
+
: {}),
|
|
640
|
+
...(urlChanged ? { baseUrl: change.baseUrl } : {}),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
return personal;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private async customProvider(
|
|
647
|
+
host: SettingsHost,
|
|
648
|
+
providerId: string,
|
|
649
|
+
): Promise<NativeProvider> {
|
|
650
|
+
const view = await host.invoke(
|
|
651
|
+
"provider-settings",
|
|
652
|
+
"getView",
|
|
653
|
+
[],
|
|
654
|
+
nativeViewSchema,
|
|
655
|
+
);
|
|
656
|
+
const provider = view.providers.find(
|
|
657
|
+
(entry) => entry.providerId === providerId,
|
|
658
|
+
);
|
|
659
|
+
// Account plans are managed by sign-in; their access cannot be edited.
|
|
660
|
+
if (!provider || !isCustom(provider))
|
|
661
|
+
throw new AdapterError("INVALID_CONFIGURATION", "Unknown model provider");
|
|
662
|
+
return provider;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private async mutate<Schema extends z.ZodType>(
|
|
666
|
+
host: SettingsHost,
|
|
667
|
+
method: string,
|
|
668
|
+
args: readonly unknown[],
|
|
669
|
+
schema: Schema,
|
|
670
|
+
): Promise<z.output<Schema>> {
|
|
671
|
+
try {
|
|
672
|
+
return await host.invoke("provider-settings", method, args, schema);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
logger.error("zcode.account.provider.failed", error);
|
|
675
|
+
// ZCode's validation messages may echo the submitted values.
|
|
676
|
+
throw new AdapterError(
|
|
677
|
+
"INVALID_CONFIGURATION",
|
|
678
|
+
"ZCode rejected the provider settings. Check the base URL, API format and API key.",
|
|
679
|
+
{},
|
|
680
|
+
{ cause: error },
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
private async refresh(host: SettingsHost, reason: string): Promise<void> {
|
|
686
|
+
await host.invoke(
|
|
687
|
+
"provider-settings",
|
|
688
|
+
"refresh",
|
|
689
|
+
[reason],
|
|
690
|
+
z.unknown(),
|
|
691
|
+
60_000,
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private schedulePoll(): void {
|
|
696
|
+
clearTimeout(this.pollTimer);
|
|
697
|
+
this.pollTimer = setTimeout(() => {
|
|
698
|
+
const pending = this.signIn;
|
|
699
|
+
if (pending.state !== "pending" || this.closed) return;
|
|
700
|
+
const step = this.queue.then(() => this.poll(pending));
|
|
701
|
+
this.queue = step.catch(() => {});
|
|
702
|
+
}, this.pollMs);
|
|
703
|
+
this.pollTimer.unref?.();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// The daemon, not the browser, receives the result: the sign-in page can be
|
|
707
|
+
// opened on any device, including one other than the daemon machine.
|
|
708
|
+
private async poll(pending: Extract<SignIn, { state: "pending" }>) {
|
|
709
|
+
if (this.signIn !== pending || this.closed) return;
|
|
710
|
+
const fail = (message: string) => {
|
|
711
|
+
if (this.signIn === pending) this.signIn = { state: "failed", message };
|
|
712
|
+
this.scheduleIdle();
|
|
713
|
+
};
|
|
714
|
+
let host: SettingsHost;
|
|
715
|
+
try {
|
|
716
|
+
if (!this.host) throw new Error("ZCode Server stopped");
|
|
717
|
+
host = await this.host;
|
|
718
|
+
} catch {
|
|
719
|
+
return fail("ZCode Server stopped during sign-in. Try again.");
|
|
720
|
+
}
|
|
721
|
+
let result: z.output<typeof pollSchema>;
|
|
722
|
+
try {
|
|
723
|
+
result = await host.invoke("oauth", "pollPendingOAuth", [], pollSchema);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
logger.error("zcode.account.signin.failed", error);
|
|
726
|
+
return fail("Sign-in was not completed. Try again.");
|
|
727
|
+
}
|
|
728
|
+
if (result?.kind === "session" || result?.kind === "duplicate") {
|
|
729
|
+
try {
|
|
730
|
+
await this.completeSignIn(host, pending.family);
|
|
731
|
+
this.changed();
|
|
732
|
+
} catch (error) {
|
|
733
|
+
logger.error("zcode.account.signin.settings.failed", error);
|
|
734
|
+
return fail("Signed in, but ZCode could not switch to the account.");
|
|
735
|
+
}
|
|
736
|
+
if (this.signIn === pending) this.signIn = { state: "idle" };
|
|
737
|
+
this.scheduleIdle();
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (this.now() >= pending.expiresAt) {
|
|
741
|
+
await host
|
|
742
|
+
.invoke("oauth", "cancelPending", [], z.unknown())
|
|
743
|
+
.catch(() => {});
|
|
744
|
+
return fail("Sign-in timed out. Try again.");
|
|
745
|
+
}
|
|
746
|
+
if (this.signIn === pending) this.schedulePoll();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Desktop's completion: select the signed-in family and its individual
|
|
750
|
+
// Coding Plan unless a plan is already chosen, then refresh entitlements.
|
|
751
|
+
// Without the family the Server does not offer the account's models.
|
|
752
|
+
private async completeSignIn(
|
|
753
|
+
host: SettingsHost,
|
|
754
|
+
family: AccountFamily,
|
|
755
|
+
): Promise<void> {
|
|
756
|
+
const settings = await host.invoke(
|
|
757
|
+
"setting",
|
|
758
|
+
"get",
|
|
759
|
+
[],
|
|
760
|
+
accountSettingsSchema,
|
|
761
|
+
);
|
|
762
|
+
const selections = settings.providerFamilyConnectionSelections ?? {};
|
|
763
|
+
await host.invoke(
|
|
764
|
+
"setting",
|
|
765
|
+
"update",
|
|
766
|
+
[
|
|
767
|
+
{
|
|
768
|
+
providerFamilyDomain: family,
|
|
769
|
+
providerFamilyDomainUpdatedAt: this.now(),
|
|
770
|
+
providerFamilyDomainMigrated: true,
|
|
771
|
+
providerFamilyConnectionSelections: {
|
|
772
|
+
...selections,
|
|
773
|
+
[family]: selections[family] ?? { kind: "individual-coding-plan" },
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
z.unknown(),
|
|
778
|
+
);
|
|
779
|
+
await this.refresh(host, "oauth-login-entitlement");
|
|
780
|
+
}
|
|
781
|
+
}
|