@serviceme/devtools-shared 0.4.5

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.
@@ -0,0 +1,1394 @@
1
+ /**
2
+ * BYO Model Provider types — Phase 2 of the multi-vendor model
3
+ * configuration system.
4
+ *
5
+ * Source plan: docs/architecture/phase-2-byo-model-providers.md
6
+ *
7
+ * Phase 1 only exposed models that VSCode's language-model API knew
8
+ * about (effectively Copilot). Phase 2 lets the user register their
9
+ * own API providers (MiniMax / DeepSeek / OpenAI / Anthropic / any
10
+ * OpenAI-compatible service) and have @serviceme chat route directly
11
+ * to that provider's HTTP API, bypassing `copilot prompt --model`.
12
+ *
13
+ * The shape is intentionally small:
14
+ * - {@link ProviderType}: picks an HTTP adapter
15
+ * - {@link ProviderModel}: one entry per model the provider exposes
16
+ * - {@link ProviderConfig}: persisted form (includes `apiKeyRef`,
17
+ * a SecretStorage key reference — NEVER the actual key)
18
+ * - {@link PublicProvider}: webview-facing projection (no secret
19
+ * reference; only a `hasApiKey: boolean` flag)
20
+ * - {@link ProviderTestResult}: result of a connectivity probe
21
+ *
22
+ * The secret/reference split is the invariant that lets us move
23
+ * ProviderConfig across the postMessage boundary safely (we send
24
+ * PublicProvider) while still being able to fetch the real key from
25
+ * SecretStorage on the extension side at chat time.
26
+ */
27
+ /**
28
+ * Adapter family. Each type maps to one `IProviderAdapter`
29
+ * implementation in `apps/extension/src/services/providers/adapters/`.
30
+ *
31
+ * `vscode-builtin` is a read-only pseudo-provider that delegates to
32
+ * the Phase 1 `vscode.lm.selectChatModels` path — preserves existing
33
+ * Copilot-by-VSCode-LM behavior so users who don't BYO still see
34
+ * whatever VSCode already exposes.
35
+ *
36
+ * The named vendor types (`minimax`, `deepseek`) are thin aliases
37
+ * that pick a protocol family + sensible defaults (see
38
+ * {@link BUILTIN_PROVIDER_PRESETS}). The wire protocol is identical
39
+ * to the `-compatible` family; the named type exists so the UI can
40
+ * pre-fill baseUrl / displayName / default model list without
41
+ * requiring the user to look up endpoint docs.
42
+ */
43
+ type ProviderType = "openai-compatible" | "anthropic-compatible" | "minimax" | "deepseek" | "kimi" | "zhipu" | "stepfun" | "siliconflow" | "openrouter" | "novita" | "agnes" | "vscode-builtin";
44
+ /**
45
+ * Curated metadata for named, well-known models. Used as a
46
+ * **fallback** when a `ProviderModel` entry omits one of the
47
+ * extended fields (`detail` / `capabilities` / `pricing` /
48
+ * `priceCategory` / `thinkingSchema`). The lookup is keyed on
49
+ * the un-qualified model id (the `id` field, not the
50
+ * `providerId::modelId` qualified form) so it stays stable
51
+ * across providers.
52
+ *
53
+ * The UI pre-fills this metadata into `BUILTIN_PROVIDER_PRESETS`
54
+ * at extension load, so the user does not have to type it in for
55
+ * the named vendors (minimax / deepseek). For user-added
56
+ * `-compatible` providers, the metadata stays empty and the
57
+ * picker just shows the model without the cost column / thinking
58
+ * dropdown.
59
+ *
60
+ * Pricing numbers are stored once in USD; the CNY values live on
61
+ * the same entry so a single `lookupModelMetadata(id)` call
62
+ * gives the registrar everything it needs to format the picker's
63
+ * cost column under either currency (resolved from `baseUrl`).
64
+ *
65
+ * Source numbers:
66
+ * - minimax M3: https://platform.minimaxi.com/docs/llms/overview
67
+ * (USD $0.3 / $1.2 / $0.06; CNY ¥2.1 / ¥8.4 / ¥0.42)
68
+ * - minimax M2.7: same family, half the speed tier
69
+ * - minimax M2.7-highspeed: 2x the M2.7 price
70
+ * - deepseek v4-flash: USD $0.14 / $0.28 / $0.0028
71
+ * - deepseek v4-pro: USD $0.435 / $0.87 / $0.003625
72
+ * (CNY values from the per-region pricing pages on
73
+ * https://api-docs.deepseek.com/quick_start/pricing)
74
+ */
75
+ interface CuratedModelMetadata {
76
+ /** Short description for the picker's `detail` column. */
77
+ detail: ModelDetail;
78
+ /** Whether the model advertises vision. */
79
+ imageInput: boolean;
80
+ /** Whether the model supports tool calling. */
81
+ toolCalling: boolean;
82
+ /** USD pricing (per million tokens). */
83
+ pricingUSD: ModelPricing;
84
+ /** CNY pricing (per million tokens). */
85
+ pricingCNY: ModelPricing;
86
+ /** Tier tag for the picker's price column. */
87
+ priceCategory: ModelPriceCategory;
88
+ /** Thinking-dropdown schema (omit = no thinking dropdown). */
89
+ thinkingSchema?: Exclude<ModelThinkingSchema, "none">;
90
+ /**
91
+ * Context-window token caps. Centralized here (rather than as
92
+ * magic numbers scattered across `BUILTIN_PROVIDER_PRESETS` call
93
+ * sites) so every consumer — the named-vendor Add-form presets,
94
+ * "Fetch from API" enrichment (`ProviderRegistry.fetchRemoteModels`),
95
+ * and the chat-registration fallback
96
+ * (`LmChatProviderRegistrar.resolveModelMetadata`) — agrees on the
97
+ * same numbers for a given model id. Optional: a curated entry can
98
+ * exist purely for capability/pricing metadata without token caps
99
+ * (uncommon, but keeps the field honest for cases where only the
100
+ * cost/capability data is verified).
101
+ */
102
+ maxInputTokens?: number;
103
+ maxOutputTokens?: number;
104
+ }
105
+ /**
106
+ * Short one-line description rendered in the VSCode chat model
107
+ * picker's `detail` column. The full pricing block + baseUrl is
108
+ * surfaced separately as the picker's `tooltip` (multi-line). Kept
109
+ * short (one sentence) so it fits on a single line in the picker.
110
+ */
111
+ type ModelDetail = string;
112
+ /**
113
+ * Pricing column metadata. Both the chat model picker and the
114
+ * ProvidersTab form render this. `cacheRead` is the cache-hit cost
115
+ * per million tokens (USD or CNY depending on the resolved
116
+ * `baseUrl` — see `currencyForBaseUrl` in
117
+ * `apps/extension/src/services/providers/`). `null` means the
118
+ * provider has not published a cache-hit price; the picker shows
119
+ * "(not published)" rather than a misleading zero.
120
+ */
121
+ interface ModelPricing {
122
+ /** Input cost per million tokens (USD or CNY per `currencyForBaseUrl`). */
123
+ input: number;
124
+ /** Output cost per million tokens. */
125
+ output: number;
126
+ /** Cache-hit cost per million tokens. `null` = provider has not published one. */
127
+ cacheRead: number | null;
128
+ }
129
+ /**
130
+ * Tier tag for the picker's price column. Aligned with the
131
+ * upstream Copilot Chat price categories so the visual bucket
132
+ * matches what GitHub Copilot uses for its own models.
133
+ */
134
+ type ModelPriceCategory = "low" | "medium" | "high" | "very_high";
135
+ /**
136
+ * Thinking dropdown schema the picker renders beneath the model
137
+ * name. The user's selection is delivered to
138
+ * `provideLanguageModelChatResponse` via
139
+ * `options.modelConfiguration[schemaKey]`. The request layer reads
140
+ * that value and translates it into the vendor's native
141
+ * `thinking` block.
142
+ * - `thinkingEnabled` — minimax M3 binary on/off
143
+ * (Anthropic `thinking.type = "adaptive" | "disabled"`)
144
+ * - `reasoningEffort` — deepseek v4 three-level
145
+ * `none | high | max` scale
146
+ * - `none` — picker hides the thinking dropdown
147
+ * entirely (model has no configurable reasoning depth)
148
+ */
149
+ type ModelThinkingSchema = "thinkingEnabled" | "reasoningEffort" | "none";
150
+ /**
151
+ * One model that a provider exposes. Stable id within the provider.
152
+ *
153
+ * Optional fields beyond `id` + `maxInputTokens` + `maxOutputTokens`
154
+ * surface in the VSCode chat model picker (Copilot Chat) as the
155
+ * `detail` / `tooltip` / `statusIcon` / `capabilities` / `inputCost` /
156
+ * `outputCost` / `cacheCost` / `priceCategory` / `isBYOK` /
157
+ * `configurationSchema` fields on `LanguageModelChatInformation`.
158
+ * Missing fields fall back to the curated metadata table in
159
+ * `MODEL_METADATA` (see `lookupModelMetadata` in
160
+ * `apps/extension/src/services/providers/LmChatProviderRegistrar.ts`),
161
+ * so user-added models that omit these fields still work — they
162
+ * just show up in the picker without the cost column or thinking
163
+ * dropdown.
164
+ */
165
+ interface ProviderModel {
166
+ /** Stable id within the provider (e.g. "deepseek-v4-flash", "MiniMax-M2.7") */
167
+ id: string;
168
+ /** Human-readable display name (defaults to id if omitted) */
169
+ displayName?: string;
170
+ /** Max input tokens (best-effort, may be approximate) */
171
+ maxInputTokens?: number;
172
+ /** Max output tokens */
173
+ maxOutputTokens?: number;
174
+ /** Short one-line description for the picker's `detail` column. */
175
+ detail?: ModelDetail;
176
+ /** Capability flags (advisory — adapter honors only what it supports) */
177
+ capabilities?: {
178
+ supportsImageToText?: boolean;
179
+ supportsToolCalling?: boolean;
180
+ };
181
+ /**
182
+ * Pricing block. Renders in the picker's `Input` / `Output` /
183
+ * `Cache Read` columns when this model is selected. Currency is
184
+ * resolved from `baseUrl` (minimaxi.com / deepseek.com → CNY,
185
+ * minimax.io / others → USD); the same numeric value is shown
186
+ * with the resolved currency symbol.
187
+ */
188
+ pricing?: ModelPricing;
189
+ /** Tier tag for the picker's price column (default bucket per upstream). */
190
+ priceCategory?: ModelPriceCategory;
191
+ /**
192
+ * Thinking-dropdown schema the picker renders for this model.
193
+ * Omit (or set to `none`) to hide the dropdown entirely.
194
+ */
195
+ thinkingSchema?: ModelThinkingSchema;
196
+ }
197
+ /**
198
+ * Persisted provider configuration. `apiKeyRef` is the SecretStorage
199
+ * key; the actual key never appears in this object after creation
200
+ * (it's stored under `ms-devtools.providers.<id>.apiKey` and resolved
201
+ * by `ProviderRegistry.getApiKey(id)`).
202
+ */
203
+ interface ProviderConfig {
204
+ /** Stable identifier (UUID v4) — used as SecretStorage key suffix */
205
+ id: string;
206
+ /** UI-facing name, e.g. "MiniMax (prod)" */
207
+ displayName: string;
208
+ /** Provider type — picks the adapter */
209
+ type: ProviderType;
210
+ /** HTTP base URL (no trailing slash) */
211
+ baseUrl: string;
212
+ /** Reference to SecretStorage key. Format: "ms-devtools.providers.<id>.apiKey" */
213
+ apiKeyRef: string;
214
+ /** Models this provider exposes */
215
+ models: ProviderModel[];
216
+ /** Whether this provider is the user's default. At most one at a time. */
217
+ isDefault?: boolean;
218
+ /**
219
+ * Whether this provider is active. `undefined` is treated as
220
+ * `true` (pre-existing providers persisted before this field
221
+ * existed default to enabled). Set to `false` to stop the
222
+ * provider's models from being registered with `vscode.lm`
223
+ * (see `LmChatProviderRegistrar`) without deleting the provider
224
+ * config or its SecretStorage-held API key — the user can
225
+ * re-enable it later without re-entering credentials.
226
+ */
227
+ enabled?: boolean;
228
+ /**
229
+ * MiniMax billing type for this provider's API key.
230
+ * `token_plan` — key is on a coding plan; usage can be fetched via the coding_plan API.
231
+ * `pay_as_you_go` — key is pure pay-as-you-go; no usage API available.
232
+ * Ignored for non-minimax providers.
233
+ */
234
+ minimaxBillingType?: "token_plan" | "pay_as_you_go";
235
+ /** Created / updated timestamps (informational) */
236
+ createdAt: string;
237
+ updatedAt: string;
238
+ /**
239
+ * Display order hint. Lower values sort first. User-added providers
240
+ * that never had their order explicitly set default to `0`. Builtin
241
+ * (`vscode-builtin`) is always injected at position `0` regardless
242
+ * of this field — its sort order is fixed.
243
+ */
244
+ sortOrder?: number;
245
+ }
246
+ /**
247
+ * Public-facing projection of {@link ProviderConfig}. Strips
248
+ * `apiKeyRef` and timestamps before crossing the postMessage boundary.
249
+ *
250
+ * The Webview never sees the secret reference path; the extension
251
+ * resolves it at chat time. `hasApiKey` is a boolean only — never
252
+ * the actual value.
253
+ */
254
+ type PublicProvider = Omit<ProviderConfig, "apiKeyRef"> & {
255
+ /** Whether this provider has a key configured (boolean only, never the value) */
256
+ hasApiKey: boolean;
257
+ };
258
+ /**
259
+ * Result of a connectivity probe. `ok=true` means credentials +
260
+ * reachability check passed. `latencyMs` is wall-clock from request
261
+ * start to first byte (or full response for tiny `/models` calls).
262
+ * `sampleModelId` is the first model id the provider reported
263
+ * (helpful when validating "does this base URL serve the models I expect").
264
+ */
265
+ interface ProviderTestResult {
266
+ ok: boolean;
267
+ latencyMs?: number;
268
+ sampleModelId?: string;
269
+ error?: string;
270
+ }
271
+ /**
272
+ * Message types for the providers list. Used in `GetProviders` /
273
+ * `ProvidersResponse` envelopes.
274
+ */
275
+ interface ProvidersResponsePayload {
276
+ providers: PublicProvider[];
277
+ defaultProviderId: string | null;
278
+ }
279
+ /**
280
+ * Payload for `AddProvider` / `UpdateProvider`. Does NOT include
281
+ * `apiKeyRef` (the registry generates it) or `id` (registry assigns
282
+ * on add; update receives it as a separate `id` field).
283
+ *
284
+ * `apiKey` is the user's plaintext key — the registry writes it to
285
+ * SecretStorage and never echoes it back. Pass empty string to
286
+ * leave an existing key untouched on update.
287
+ */
288
+ interface ProviderMutationPayload {
289
+ id?: string;
290
+ displayName: string;
291
+ type: ProviderType;
292
+ baseUrl: string;
293
+ apiKey: string;
294
+ models: ProviderModel[];
295
+ isDefault?: boolean;
296
+ /** See {@link ProviderConfig.enabled}. Omit to leave unchanged on update / default to `true` on add. */
297
+ enabled?: boolean;
298
+ /** See {@link ProviderConfig.minimaxBillingType}. */
299
+ minimaxBillingType?: "token_plan" | "pay_as_you_go";
300
+ }
301
+ /** Which vendor API to call for usage data. */
302
+ type ProviderUsageKind = "minimax" | "deepseek" | "kimi" | "zhipu" | "stepfun" | "siliconflow" | "openrouter" | "novita";
303
+ /**
304
+ * One quota window (5h or weekly) from a coding-plan provider.
305
+ * `utilizationPercent` is precomputed as `100 - remaining_percent`.
306
+ */
307
+ interface UsageWindow {
308
+ /** 0..100 utilization */
309
+ utilizationPercent: number;
310
+ /** ISO 8601 reset time */
311
+ resetsAt: string;
312
+ /** Precomputed epoch ms (webview convenience) */
313
+ resetsAtMs: number;
314
+ }
315
+ /** MiniMax `/coding_plan/remains` response, normalised. */
316
+ interface MinimaxUsage {
317
+ kind: "minimax";
318
+ /** 5-hour billing window (always present, derived from model_name === "general") */
319
+ interval: UsageWindow;
320
+ /** Weekly quota — only present when the account has a weekly cap. */
321
+ weekly?: UsageWindow;
322
+ }
323
+ /**
324
+ * Shared coding-plan usage for Kimi / Zhipu (same five_hour + optional weekly structure).
325
+ * Parsed from their respective API responses into this normalised shape.
326
+ */
327
+ interface CodingPlanUsage {
328
+ kind: "kimi" | "zhipu";
329
+ /** Five-hour billing window */
330
+ interval: UsageWindow;
331
+ /** Weekly quota — only present when the account has a weekly cap. */
332
+ weekly?: UsageWindow;
333
+ }
334
+ /** One currency row in a balance response. */
335
+ interface BalanceEntry {
336
+ currency: string;
337
+ totalBalance: number;
338
+ }
339
+ /** DeepSeek-specific balance entry (includes granted + topped-up breakdown). */
340
+ interface DeepseekBalanceEntry {
341
+ currency: string;
342
+ totalBalance: number;
343
+ grantedBalance?: number;
344
+ toppedUpBalance?: number;
345
+ }
346
+ /** DeepSeek `/user/balance` response, normalised. */
347
+ interface DeepseekUsage {
348
+ kind: "deepseek";
349
+ isAvailable: boolean;
350
+ balances: DeepseekBalanceEntry[];
351
+ }
352
+ /**
353
+ * Generic balance for StepFun / SiliconFlow / OpenRouter / Novita.
354
+ * Each provider returns a different response shape; the parse function normalises
355
+ * all four into this shared interface.
356
+ */
357
+ interface GenericBalanceUsage {
358
+ kind: "stepfun" | "siliconflow" | "openrouter" | "novita";
359
+ balances: BalanceEntry[];
360
+ }
361
+ /** Union of all supported vendor usage shapes. */
362
+ type ProviderUsageData = MinimaxUsage | DeepseekUsage | CodingPlanUsage | GenericBalanceUsage;
363
+ /**
364
+ * Result of a usage probe. `transient` governs the webview's keep-last-good
365
+ * strategy: true = network/timeout (retry silently), false = deterministic
366
+ * (auth/parse/4xx — drop last good, show hard error).
367
+ */
368
+ type ProviderUsageResult = {
369
+ ok: true;
370
+ data: ProviderUsageData;
371
+ fetchedAtMs: number;
372
+ } | {
373
+ ok: false;
374
+ transient: boolean;
375
+ code: string;
376
+ message: string;
377
+ };
378
+ /**
379
+ * Cache_control capability declaration, keyed by provider type.
380
+ *
381
+ * `supportsCacheControl` — does that protocol/vendor implementation
382
+ * claim a stable prompt-prefix cache?
383
+ * - Anthropic path: writes body `cache_control: {type:"ephemeral"}` —
384
+ * independent of this flag (P0.1 4-breakpoint logic).
385
+ * - OpenAI path: writes `prompt_cache_key` HEADER only when this
386
+ * flag is `true`. Metadata absence == opt-out (safer than 400).
387
+ * - minimax (Anthropic-compat host) → supportsCacheControl:true
388
+ * means the upstream honours ephemeral cache.
389
+ * - deepseek v4 / agnes (OpenAI-compat) → supportsCacheControl:true
390
+ * means the upstream `prompt_cache_key` header is honoured.
391
+ * - generic `-compatible` providers (no curated entry) → flag absent
392
+ * == entire cache path is SKIPPED. Avoids 400 risk on unknown
393
+ * upstreams.
394
+ */
395
+ interface ProviderCacheControlMetadata {
396
+ /** Stable prompt-prefix cache declared by the upstream? (OpenAI path reads this) */
397
+ supportsCacheControl?: boolean;
398
+ }
399
+ /**
400
+ * A single known-good baseUrl candidate for a provider type, offered
401
+ * as a dropdown suggestion in the ProvidersTab form. The baseUrl
402
+ * `<input>` stays freely editable — these are suggestions, not an
403
+ * enum; the user can always type a custom endpoint.
404
+ */
405
+ interface ProviderBaseUrlPreset {
406
+ /** Short human label distinguishing candidates (e.g. "国内" / "全球"). */
407
+ label: string;
408
+ baseUrl: string;
409
+ }
410
+
411
+ /**
412
+ * Provider-type → known baseUrl candidates (e.g. mainland-China vs.
413
+ * global endpoints for the same vendor, like Agnes/MiniMax/DeepSeek).
414
+ * Purely a UI convenience for the Add/Edit form's baseUrl dropdown —
415
+ * no runtime auto-switching reads this (that mechanism was removed;
416
+ * see git history for the retired `autoSwitch` feature).
417
+ */
418
+ declare const PROVIDER_BASE_URL_PRESETS: Readonly<Record<ProviderType, readonly ProviderBaseUrlPreset[]>>;
419
+ /**
420
+ * Resolve the dropdown candidate list for a provider type. Returns
421
+ * an empty array (NEVER throws) for `-compatible` types or provider
422
+ * types the table doesn't cover.
423
+ */
424
+ declare function getProviderBaseUrlPresets(type: ProviderType): readonly ProviderBaseUrlPreset[];
425
+
426
+ /**
427
+ * Provider-type → cache_control capability lookup. Read by the
428
+ * OpenAI adapter's `prompt_cache_key` decision (T-04) via
429
+ * {@link isProviderCacheControlAware}.
430
+ */
431
+ declare const PROVIDER_CACHE_CONTROL_METADATA: Readonly<Record<ProviderType, ProviderCacheControlMetadata>>;
432
+ /**
433
+ * Does this provider type declare "stable prompt cache" support?
434
+ * The OpenAI path reads this flag to decide whether to write
435
+ * `prompt_cache_key`; the Anthropic path uses its own 4-breakpoint
436
+ * logic and ignores this flag.
437
+ */
438
+ declare function isProviderCacheControlAware(type: ProviderType): boolean;
439
+
440
+ declare const MODEL_METADATA: Readonly<Record<string, CuratedModelMetadata>>;
441
+ /**
442
+ * Look up curated metadata for a model by its un-qualified id
443
+ * (the part after the last `::` in a qualified id, or the raw id
444
+ * for a non-namespaced provider). Returns `undefined` for
445
+ * user-added / `-compatible` models that have no curated entry;
446
+ * callers should then fall back to whatever the user typed into
447
+ * the ProvidersTab.
448
+ */
449
+ declare function lookupModelMetadata(modelId: string): CuratedModelMetadata | undefined;
450
+ /**
451
+ * Resolve the currency for a baseUrl. Strict hostname match —
452
+ * `api.minimaxi.com` and `api.minimaxi.cn` map to CNY (the China
453
+ * platform), `api.minimax.io` maps to USD (the global platform),
454
+ * `api.deepseek.com` maps to CNY (DeepSeek's regional pricing is
455
+ * published in CNY on the public docs even though the API is
456
+ * global), everything else falls back to USD. The match is
457
+ * exact-host so a typo in the hostname never silently flips
458
+ * currency.
459
+ */
460
+ declare function currencyForBaseUrl(baseUrl: string): "USD" | "CNY";
461
+
462
+ declare const BUILTIN_PROVIDER_PRESETS: Readonly<Record<"minimax" | "deepseek" | "kimi" | "zhipu" | "stepfun" | "siliconflow" | "openrouter" | "novita" | "agnes", {
463
+ displayName: string;
464
+ baseUrl: string;
465
+ models: ProviderModel[];
466
+ }>>;
467
+ /**
468
+ * Look up the default config (displayName / baseUrl / models) for a
469
+ * named vendor type. Returns `null` for the `-compatible` family —
470
+ * those have no canned defaults; the user enters them by hand.
471
+ */
472
+ declare function getBuiltinProviderPreset(type: ProviderType): {
473
+ displayName: string;
474
+ baseUrl: string;
475
+ models: ProviderModel[];
476
+ } | null;
477
+
478
+ /**
479
+ * Shared AI model types.
480
+ *
481
+ * Lifted from `apps/extension/src/services/ai/types.ts` so the Webview
482
+ * (which imports from `@serviceme/devtools-shared`) can use the same contract as
483
+ * the extension host. Phase 1 only needs these two types; Phase 2
484
+ * (provider registration) will extend `AIModelInfo` with
485
+ * provider-specific fields.
486
+ *
487
+ * Source plan: docs/architecture/extension-llm-providers.md (v3)
488
+ */
489
+ /**
490
+ * Persisted "current model" configuration. Stored in
491
+ * `context.globalState` and used to select a model from
492
+ * `vscode.lm.selectChatModels({ vendor, family })`.
493
+ */
494
+ interface AIModelConfig {
495
+ vendor: string;
496
+ family?: string;
497
+ version?: string;
498
+ }
499
+ /**
500
+ * Webview-friendly projection of `vscode.LanguageModelChat`.
501
+ *
502
+ * `vscode.LanguageModelChat.capabilities` is a lazy method bag; once
503
+ * we cross the `postMessage` boundary, those function references are
504
+ * lost. This type freezes the boolean fields we care about so the
505
+ * Webview can render capability badges (tool calling, image input)
506
+ * without a second round-trip.
507
+ */
508
+ interface AIModelInfo {
509
+ id: string;
510
+ name: string;
511
+ vendor: string;
512
+ family?: string;
513
+ version?: string;
514
+ maxInputTokens?: number;
515
+ maxOutputTokens?: number;
516
+ capabilities?: {
517
+ supportsImageToText?: boolean;
518
+ supportsToolCalling?: boolean;
519
+ };
520
+ }
521
+
522
+ /**
523
+ * Certificate bundle download — wire types shared between the certificate
524
+ * Webview (apps/webview-ui/src/certificate) and the extension panel service
525
+ * (apps/extension/src/services/view/CertificatePanelService). The actual
526
+ * conversion work is delegated to the `sslkit` npm package on the extension
527
+ * side; these types just describe the IPC payload for choosing a target
528
+ * format and receiving the resulting zip.
529
+ */
530
+ /** Target formats that the extension can produce via `sslkit`. */
531
+ type CertificateBundleFormat = "pem" | "pfx" | "crt" | "jks";
532
+ /** Static metadata describing each supported bundle format — used by the UI
533
+ * to render the format picker without hard-coding labels in two places. */
534
+ interface CertificateBundleFormatDescriptor {
535
+ readonly format: CertificateBundleFormat;
536
+ /** Short label shown in the picker (e.g. "Nginx PEM"). */
537
+ readonly label: string;
538
+ /** One-line description of what the format is for. */
539
+ readonly description: string;
540
+ /** Icon name (lucide) for the picker button. */
541
+ readonly icon: string;
542
+ /** Whether this format requires a password (PFX / JKS). */
543
+ readonly requiresPassword: boolean;
544
+ /** Conventional file extension(s) for the primary artifact(s) the user
545
+ * is downloading, shown in the picker subtitle. */
546
+ readonly artifactExtension: string;
547
+ }
548
+ declare const CERTIFICATE_BUNDLE_FORMATS: readonly CertificateBundleFormatDescriptor[];
549
+ /** Request sent from the Webview when the user picks a format. */
550
+ interface DownloadCertificateBundleRequest {
551
+ id: string;
552
+ format: CertificateBundleFormat;
553
+ /** Override for the export password (PFX / JKS only). When omitted, the
554
+ * extension falls back to the `ms-devtools.certificate.exportPassword`
555
+ * setting (default "123456", matching `sslkit`). */
556
+ password?: string;
557
+ }
558
+ /** Response sent back to the Webview — same shape as the existing PEM bundle
559
+ * download (filename + base64 zip), so the Webview side can reuse its
560
+ * existing download helper without branching on format. */
561
+ interface DownloadCertificateBundleResponse {
562
+ success: boolean;
563
+ filename?: string;
564
+ mime?: string;
565
+ base64?: string;
566
+ error?: string;
567
+ }
568
+ /** Feature availability reported by the extension to the Webview so the UI
569
+ * can grey out formats whose prerequisites aren't met on this machine. */
570
+ interface CertificateBundleEnvironmentSupport {
571
+ /** `true` once `sslkit` was resolved via `node_modules/.bin` or PATH. */
572
+ sslkitAvailable: boolean;
573
+ /** `true` once `keytool` was found on PATH (required only for JKS). */
574
+ jdkAvailable: boolean;
575
+ /** Mirror of `msDevTools.certificate.exportPassword` — surfaces the
576
+ * effective default so the UI doesn't have to subscribe to config
577
+ * change events. */
578
+ defaultPassword: string;
579
+ }
580
+
581
+ /**
582
+ * Git URL Utilities
583
+ *
584
+ * Pure functions for parsing and validating Git remote URLs into canonical slugs.
585
+ * No platform-specific logic — suitable for both Node.js and browser environments.
586
+ */
587
+ /**
588
+ * Built-in hostname aliases for Git remotes.
589
+ *
590
+ * Some users/teams configure `~/.ssh/config` `Host` aliases (e.g. to pick a
591
+ * specific SSH identity for a work GitHub account) so their remotes read
592
+ * `git@github-msc:owner/repo.git` instead of `git@github.com:owner/repo.git`.
593
+ * Resolving these here — rather than only on the client — keeps the server's
594
+ * independently-recomputed canonical slug (see `ensureRemotesMatchCanonicalSlug`
595
+ * in `apps/server/src/app/api/v1/projects/_validators.ts`) consistent with
596
+ * whatever the client already resolved and sent as `canonical_slug`.
597
+ */
598
+ declare const GIT_REMOTE_HOST_ALIASES: Readonly<Record<string, string>>;
599
+ /**
600
+ * Apply {@link GIT_REMOTE_HOST_ALIASES} to an already-canonical `host/owner/repo`
601
+ * slug string (as opposed to a raw Git URL — see `normalizeGitUrl` for that).
602
+ */
603
+ declare function normalizeCanonicalSlug(slug: string): string;
604
+ /**
605
+ * Normalize a Git remote URL to a canonical slug: host/owner/repo
606
+ * - Supports HTTPS, SSH, and SCP-like syntax
607
+ * - Strips credentials, ports, and .git suffix
608
+ * - Lowercases host and every path segment
609
+ * - Resolves known host aliases (see {@link GIT_REMOTE_HOST_ALIASES})
610
+ *
611
+ * @throws Error if the input cannot be parsed into a valid slug
612
+ */
613
+ declare function normalizeGitUrl(input: string): string;
614
+ /**
615
+ * Validate whether a string matches the canonical slug format: host/owner/repo
616
+ * Requires at least three segments (host + two path parts).
617
+ * Allows percent-encoded characters (e.g. %20) for hosts like Azure DevOps
618
+ * that permit spaces in project/repo names.
619
+ */
620
+ declare function isValidCanonicalSlug(slug: string): boolean;
621
+
622
+ interface GitHubUser {
623
+ id: number;
624
+ login: string;
625
+ email: string | null;
626
+ name: string | null;
627
+ avatar_url: string | null;
628
+ plan?: {
629
+ name: string;
630
+ space: number;
631
+ collaborators: number;
632
+ private_repos: number;
633
+ };
634
+ [key: string]: unknown;
635
+ }
636
+ interface GitHubEmailRecord {
637
+ email: string;
638
+ primary: boolean;
639
+ verified: boolean;
640
+ visibility: "public" | "private" | null;
641
+ }
642
+ type GitHubOrgMembershipStatus = "active" | "pending" | "not_member" | "forbidden" | "unauthorized";
643
+ interface GitHubOrgMembershipCheckResult {
644
+ status: GitHubOrgMembershipStatus;
645
+ httpStatus: number;
646
+ organization: string;
647
+ role: string | null;
648
+ directMembership: boolean | null;
649
+ }
650
+ /**
651
+ * Fetch user info from GitHub API
652
+ * @param token GitHub Personal Access Token or OAuth Access Token
653
+ * @returns GitHubUser object
654
+ * @throws Error if request fails or token is invalid
655
+ */
656
+ declare function fetchGitHubUser(token: string): Promise<GitHubUser>;
657
+ declare function pickPreferredGitHubEmail(emails: GitHubEmailRecord[]): string | null;
658
+ declare function sanitizeEmail(email: string | null | undefined): string | null;
659
+ declare const __internal: {
660
+ pickPreferredGitHubEmail: typeof pickPreferredGitHubEmail;
661
+ sanitizeEmail: typeof sanitizeEmail;
662
+ };
663
+ /**
664
+ * Fetch the authenticated user's membership details for a GitHub organization.
665
+ * Uses the memberships list endpoint so callers can distinguish active, pending,
666
+ * and indeterminate states instead of collapsing everything into a boolean.
667
+ */
668
+ declare function getGitHubOrgMembership(token: string, org: string): Promise<GitHubOrgMembershipCheckResult>;
669
+ /**
670
+ * Check if the token owner is authorized for a given GitHub organization.
671
+ * Uses the authenticated user's own token — works for both public and private membership.
672
+ * Returns true if the user is active or has a pending invitation, false otherwise.
673
+ * @param token GitHub Personal Access Token or OAuth Access Token
674
+ * @param org GitHub organization name
675
+ */
676
+ declare function checkGitHubOrgMembership(token: string, org: string): Promise<boolean>;
677
+
678
+ declare function isGitHubLocalEmail(email: string | null | undefined): boolean;
679
+ declare function buildGitHubLocalEmail(login: string): string;
680
+ declare function resolvePrimaryEmail(login: string, email: string | null | undefined): string;
681
+
682
+ /**
683
+ * Unified, environment-agnostic logger contract for the SERVICEME monorepo.
684
+ *
685
+ * This module intentionally has NO dependency on `vscode`, the extension
686
+ * runtime, or `@serviceme/devtools-core` so it can be consumed by every
687
+ * package (shared → protocol → core → cli → webview → extension → server)
688
+ * without creating cycles or pulling in heavyweight environment-specific
689
+ * APIs.
690
+ *
691
+ * Consumers route their telemetry through an {@link ILogger}:
692
+ * - the extension uses the OutputChannel + file-backed `Logger` (see
693
+ * `apps/extension/src/core/logger/Logger.ts`);
694
+ * - server / cli / webview use the console / postMessage-backed
695
+ * implementations provided by their own package, or the
696
+ * {@link createConsoleLogger} fallback defined here.
697
+ */
698
+ /** Severity levels, ordered low → high. */
699
+ declare enum LogLevel {
700
+ DEBUG = 0,
701
+ INFO = 1,
702
+ WARN = 2,
703
+ ERROR = 3
704
+ }
705
+ /**
706
+ * The logger contract every package implements / consumes.
707
+ *
708
+ * Method signatures are intentionally `(message: string, ...args: unknown[])`
709
+ * so callers can attach an error object or structured context without
710
+ * pre-formatting. `error` takes an optional leading `error` argument so the
711
+ * implementation can normalize / redact it; implementations SHOULD run
712
+ * {@link normalizeErrorForLog} on that value before emitting.
713
+ */
714
+ interface ILogger {
715
+ debug(message: string, ...args: unknown[]): void;
716
+ info(message: string, ...args: unknown[]): void;
717
+ warn(message: string, ...args: unknown[]): void;
718
+ error(message: string, error?: unknown, ...args: unknown[]): void;
719
+ setLevel(level: LogLevel): void;
720
+ /** Optional — file-backed loggers return their directory. */
721
+ getLogDir?(): string | undefined;
722
+ /** Optional — UI-backed loggers reveal their sink (e.g. an OutputChannel). */
723
+ show?(): void;
724
+ }
725
+ /**
726
+ * Normalize any thrown value into a serializable record for logging.
727
+ *
728
+ * Handles `Error` (canonical fields + protocol-level extras), strings,
729
+ * primitives, and plain objects, so log sinks can `JSON.stringify` the
730
+ * result without throwing on circular refs or dropping context. This is a
731
+ * pure function with no environment dependencies and was promoted from
732
+ * `apps/extension/src/core/logger/Logger.ts` so every package shares one
733
+ * normalization path.
734
+ */
735
+ declare function normalizeErrorForLog(error: unknown): Record<string, unknown>;
736
+ /**
737
+ * Create a console-backed {@link ILogger}.
738
+ *
739
+ * - `debug` is gated to `NODE_ENV !== "production"` (verbose traces only in
740
+ * dev / test), mirroring the server's legacy `logDebug` behavior.
741
+ * - `info` / `warn` / `error` always emit to the matching `console` method.
742
+ * - `error`'s leading `error` argument is run through
743
+ * {@link normalizeErrorForLog} so serialized errors stay structured and
744
+ * circular-ref safe.
745
+ *
746
+ * This is the canonical fallback for packages without a richer sink (cli,
747
+ * webview bootstrap, extension logger self-diagnostics).
748
+ */
749
+ declare function createConsoleLogger(name: string): ILogger;
750
+
751
+ /**
752
+ * Protocol Contract Types — single source of truth.
753
+ *
754
+ * This module is the canonical home for the protocol contract types that are
755
+ * shared by the devtools protocol package (CLI / core / extension / server),
756
+ * the webview (`apps/webview-ui`), and `@serviceme/devtools-shared` itself.
757
+ *
758
+ * Why this file exists (code-cleanliness Phase 3, T04):
759
+ *
760
+ * Previously these types lived in the devtools protocol package and were
761
+ * re-exported from `@serviceme/devtools-shared`, which forced `@serviceme/devtools-shared` to
762
+ * depend on the protocol package — an inverted layering (the leaf
763
+ * shared package should not depend on the richer protocol package). Sinking
764
+ * the type definitions here makes `@serviceme/devtools-shared` the single source, lets
765
+ * `@serviceme/devtools-shared` drop its dependency on the protocol package, and lets
766
+ * the protocol package re-exports these same types (API-stable) from
767
+ * `@serviceme/devtools-shared`.
768
+ *
769
+ * Rules for this file:
770
+ *
771
+ * - Self-contained: it must NOT import from the protocol package or
772
+ * any runtime package. Only the TypeScript standard lib is allowed.
773
+ * - Type-only: no runtime values (no enums with runtime behavior, no functions).
774
+ * The protocol package keeps the structural validators (`isXxx`) next to
775
+ * where they are used.
776
+ * - Transitive bases are included: a sunk type whose shape references another
777
+ * type that is not itself part of the public 19 must also be defined here so
778
+ * the module stays self-contained (e.g. `ScheduledTaskV1`, `TaskWorkspaceRef`,
779
+ * `TaskRunStatus`, `BridgeLinkMode`).
780
+ */
781
+ type ScheduledTaskType = "command" | "shell" | "http_request" | "github_copilot_cli";
782
+ interface CommandPayload {
783
+ command: string;
784
+ args?: unknown[];
785
+ }
786
+ interface ShellPayload {
787
+ script: string;
788
+ cwd?: string;
789
+ /** Timeout in seconds. Omit for default 60s; use 0 for no limit. */
790
+ timeout?: number;
791
+ }
792
+ interface HttpRequestPayload {
793
+ url: string;
794
+ method: string;
795
+ headers?: Record<string, string>;
796
+ body?: string;
797
+ /** Timeout in seconds. Omit for default 30s; use 0 for no limit. */
798
+ timeout?: number;
799
+ }
800
+ interface GithubCopilotCliPayload {
801
+ prompt: string;
802
+ workspace?: string;
803
+ autopilot?: boolean;
804
+ allowTools?: string[];
805
+ /** Timeout in seconds. Omit for default 300s; use 0 for no limit. */
806
+ timeout?: number;
807
+ model?: string;
808
+ agent?: string;
809
+ }
810
+ type TaskPayload = CommandPayload | ShellPayload | HttpRequestPayload | GithubCopilotCliPayload;
811
+ /** Reference to the workspace a scheduled task is bound to. Required by v2. */
812
+ interface TaskWorkspaceRef {
813
+ /** Absolute path to the workspace root. Required and unique. */
814
+ path: string;
815
+ /** Display name. Defaults to `path` basename; users may override. */
816
+ name: string;
817
+ /** `git@github.com:owner/repo.git` — auto-detected at task creation. */
818
+ gitRemote?: string;
819
+ /** Current branch — auto-detected at task creation; verified at execute. */
820
+ gitBranch?: string;
821
+ /** Last time the extension saw this workspace alive. */
822
+ lastSeenAt?: string;
823
+ }
824
+ /**
825
+ * v1 task shape — kept for reading legacy `<workspace>/.serviceme/scheduled-tasks.json`
826
+ * during migration. New code should always use {@link ScheduledTask} (v2).
827
+ */
828
+ interface ScheduledTaskV1 {
829
+ id: string;
830
+ name: string;
831
+ description?: string;
832
+ enabled: boolean;
833
+ scheduleType: "cron" | "interval";
834
+ schedule: string;
835
+ taskType: ScheduledTaskType;
836
+ payload: TaskPayload;
837
+ createdAt: string;
838
+ updatedAt: string;
839
+ }
840
+ /** Runtime status of the most recent execution for a task. */
841
+ type TaskRunStatus = "ok" | "error" | "skipped";
842
+ /**
843
+ * v2 scheduled task — adds required `workspace` reference and runtime metadata
844
+ * fields. See `docs/architecture/skill-agent-v2-repo.md` §14.2.
845
+ */
846
+ interface ScheduledTask extends ScheduledTaskV1 {
847
+ /** Required in v2. The workspace this task executes in. */
848
+ workspace: TaskWorkspaceRef;
849
+ /** ISO timestamp of the most recent execution. */
850
+ lastRunAt?: string;
851
+ /** Status of the most recent execution. */
852
+ lastRunStatus?: TaskRunStatus;
853
+ /** Error message from the most recent failed execution. */
854
+ lastRunError?: string;
855
+ /** Duration of the most recent execution, in milliseconds. */
856
+ lastRunDurationMs?: number;
857
+ /** Next scheduled run (computed by scheduler). */
858
+ nextRunAt?: string;
859
+ /** Total successful executions. */
860
+ runCount?: number;
861
+ }
862
+ /** v2 config — single source of truth for the global scheduler. */
863
+ interface ScheduledTasksConfig {
864
+ version: 2;
865
+ tasks: ScheduledTask[];
866
+ }
867
+ type TaskExecutionStatus = "running" | "success" | "failure" | "timeout" | "cancelled";
868
+ interface TaskExecutionLog {
869
+ id: string;
870
+ taskId: string;
871
+ taskName: string;
872
+ startedAt: string;
873
+ finishedAt: string;
874
+ status: Exclude<TaskExecutionStatus, "running">;
875
+ output?: string;
876
+ error?: string;
877
+ }
878
+ type AgentToolRiskLevel = "high" | "medium" | "low";
879
+ interface AgentToolPermission {
880
+ tool: string;
881
+ riskLevel: AgentToolRiskLevel;
882
+ }
883
+ interface AgentPermissionSummary {
884
+ agentId: string;
885
+ agentName: string;
886
+ tools: AgentToolPermission[];
887
+ highRiskCount: number;
888
+ mediumRiskCount: number;
889
+ lowRiskCount: number;
890
+ }
891
+ /** Kinds of artifacts in the catalog. */
892
+ type BridgeSkillKind = "skill" | "agent";
893
+ /** Which side of the link is reported. Matches SkillLinker's `LinkMode`. */
894
+ type BridgeLinkMode = "symlink" | "junction" | "copy";
895
+ /** A single skill/agent entry inside a repo (SkillStore.SkillEntry). */
896
+ interface BridgeSkillRepoEntry {
897
+ repoId: string;
898
+ name: string;
899
+ kind: BridgeSkillKind;
900
+ /** Absolute path to the manifest file. */
901
+ manifestPath: string;
902
+ /** Absolute path to the entry's directory. */
903
+ dir: string;
904
+ /** Best-effort parsed frontmatter. */
905
+ frontmatter: Record<string, unknown>;
906
+ /** ISO timestamp of the manifest's mtime. */
907
+ modifiedAt: string;
908
+ }
909
+ /** A single file inside an entry (SkillStore.SkillFile). */
910
+ interface BridgeSkillRepoFile {
911
+ /** Path relative to the entry's directory. */
912
+ path: string;
913
+ /** UTF-8 content. */
914
+ content: string;
915
+ }
916
+ /** A symlink/junction currently present in a workspace or user (global) scope. */
917
+ interface BridgeLinkedSkill {
918
+ repoId: string;
919
+ name: string;
920
+ linkPath: string;
921
+ targetPath: string;
922
+ mode: BridgeLinkMode;
923
+ /** Which scope this link lives in — "workspace" or "user" (global, `~/.agents/...`). */
924
+ scope: "workspace" | "user";
925
+ }
926
+ /** A single entry from `repos.json` (subset of RepoConfig). */
927
+ interface BridgeRepoEntry {
928
+ id: string;
929
+ name: string;
930
+ url: string;
931
+ branch: string;
932
+ enabled: boolean;
933
+ useProxy?: boolean;
934
+ writeEnabled: boolean;
935
+ source: "default" | "user";
936
+ description?: string;
937
+ addedAt: string;
938
+ lastSyncAt?: string;
939
+ lastSyncCommitSha?: string;
940
+ lastSyncStatus?: "ok" | "error";
941
+ lastSyncError?: string;
942
+ }
943
+ /** A single line of a sync run. */
944
+ interface BridgeRepoSyncPull {
945
+ repoId: string;
946
+ status: "ok" | "error";
947
+ commitSha?: string;
948
+ error?: string;
949
+ }
950
+
951
+ declare enum WebviewMessageType {
952
+ WebviewReady = "webviewReady",
953
+ Ready = "ready",
954
+ Log = "log",
955
+ ExecuteCommand = "executeCommand",
956
+ OpenUrl = "openUrl",
957
+ UsePrompt = "usePrompt",
958
+ UpdateAzureProfiles = "updateAzureProfiles",
959
+ UpdateAzureProfile = "updateAzureProfile",
960
+ GetApiConfig = "getApiConfig",
961
+ UpdateApiConfig = "updateApiConfig",
962
+ SaveApiConfig = "saveApiConfig",
963
+ ApiConfigSaveFailed = "apiConfigSaveFailed",
964
+ ExportApiConfig = "exportApiConfig",
965
+ ImportApiConfig = "importApiConfig",
966
+ ApiConfigImported = "apiConfigImported",
967
+ AddExternalTool = "addExternalTool",
968
+ UpdateExternalTool = "updateExternalTool",
969
+ UpdateExternalTools = "updateExternalTools",
970
+ DeleteExternalTool = "deleteExternalTool",
971
+ ReorderExternalTools = "reorderExternalTools",
972
+ UpdateNgrokStatus = "updateNgrokStatus",
973
+ UpdateServerStatus = "updateServerStatus",
974
+ GetAuthState = "getAuthState",
975
+ UpdateAuthState = "updateAuthState",
976
+ Login = "login",
977
+ Logout = "logout",
978
+ GetAccounts = "getAccounts",
979
+ UpdateAccounts = "updateAccounts",
980
+ SwitchAccount = "switchAccount",
981
+ GetUserProfile = "getUserProfile",
982
+ UpdateUserProfile = "updateUserProfile",
983
+ UserProfileUpdated = "userProfileUpdated",
984
+ AddExtraEmail = "addExtraEmail",
985
+ DeleteExtraEmail = "deleteExtraEmail",
986
+ GetCalendarHolidays = "getCalendarHolidays",
987
+ UpdateCalendarHolidays = "updateCalendarHolidays",
988
+ GetCalendarLeaves = "getCalendarLeaves",
989
+ UpdateCalendarLeaves = "updateCalendarLeaves",
990
+ CreateCalendarLeave = "createCalendarLeave",
991
+ DeleteCalendarLeave = "deleteCalendarLeave",
992
+ GetCalendarNotes = "getCalendarNotes",
993
+ UpdateCalendarNotes = "updateCalendarNotes",
994
+ SaveCalendarNote = "saveCalendarNote",
995
+ GetCalendarMonthSchedule = "getCalendarMonthSchedule",
996
+ GetScheduledTasks = "getScheduledTasks",
997
+ UpdateScheduledTasks = "updateScheduledTasks",
998
+ CreateScheduledTask = "createScheduledTask",
999
+ EditScheduledTask = "editScheduledTask",
1000
+ DeleteScheduledTask = "deleteScheduledTask",
1001
+ ToggleScheduledTask = "toggleScheduledTask",
1002
+ TriggerScheduledTask = "triggerScheduledTask",
1003
+ CancelTaskExecution = "cancelTaskExecution",
1004
+ GetTaskExecutionLogs = "getTaskExecutionLogs",
1005
+ ClearTaskExecutionLogs = "clearTaskExecutionLogs",
1006
+ UpdateTaskExecutionLogs = "updateTaskExecutionLogs",
1007
+ UpdateTaskExecutionState = "updateTaskExecutionState",
1008
+ GetCurrentWorkspace = "getCurrentWorkspace",
1009
+ UpdateCurrentWorkspace = "updateCurrentWorkspace",
1010
+ GetProviders = "getProviders",
1011
+ ProvidersResponse = "providersResponse",
1012
+ AddProvider = "addProvider",
1013
+ UpdateProvider = "updateProvider",
1014
+ RemoveProvider = "removeProvider",
1015
+ SetDefaultProvider = "setDefaultProvider",
1016
+ SetProviderEnabled = "setProviderEnabled",
1017
+ SetProviderOrder = "setProviderOrder",
1018
+ TestProvider = "testProvider",
1019
+ ProviderTestResultMessage = "providerTestResult",
1020
+ DefaultProviderChanged = "defaultProviderChanged",
1021
+ FetchProviderModels = "fetchProviderModels",
1022
+ FetchProviderModelsResult = "fetchProviderModelsResult",
1023
+ GetProviderUsage = "getProviderUsage",
1024
+ ProviderUsageResponse = "providerUsageResponse",
1025
+ SetCacheControlEnabled = "setCacheControlEnabled",
1026
+ GetByomSettings = "getByomSettings",
1027
+ ByomSettingsResponse = "byomSettingsResponse",
1028
+ ListSkillRepoEntries = "listSkillRepoEntries",
1029
+ GetSkillRepoEntry = "getSkillRepoEntry",
1030
+ InstallSkillRepoEntry = "installSkillRepoEntry",
1031
+ ConvertSkillRepoEntryToSymlink = "convertSkillRepoEntryToSymlink",
1032
+ UninstallSkillRepoEntry = "uninstallSkillRepoEntry",
1033
+ ListLinkedSkills = "listLinkedSkills",
1034
+ UpdateSkillRepoCatalog = "updateSkillRepoCatalog",
1035
+ UpdateSkillRepoInstall = "updateSkillRepoInstall",
1036
+ UpdateLinkedSkills = "updateLinkedSkills",
1037
+ CreateSkillRepoDraft = "createSkillRepoDraft",
1038
+ CommitSkillRepoDraft = "commitSkillRepoDraft",
1039
+ ListSkillRepoDrafts = "listSkillRepoDrafts",
1040
+ DeleteSkillRepoDraft = "deleteSkillRepoDraft",
1041
+ UpdateSkillRepoDraft = "updateSkillRepoDraft",
1042
+ ListRepositories = "listRepositories",
1043
+ AddRepository = "addRepository",
1044
+ UpdateRepository = "updateRepository",
1045
+ RemoveRepository = "removeRepository",
1046
+ EnableRepository = "enableRepository",
1047
+ DisableRepository = "disableRepository",
1048
+ SyncRepository = "syncRepository",
1049
+ SyncAllRepositories = "syncAllRepositories",
1050
+ UpdateRepositoryList = "updateRepositoryList",
1051
+ UpdateRepositoryAdd = "updateRepositoryAdd",
1052
+ UpdateRepositoryUpdate = "updateRepositoryUpdate",
1053
+ UpdateRepositoryRemove = "updateRepositoryRemove",
1054
+ UpdateRepositoryToggle = "updateRepositoryToggle",
1055
+ UpdateRepositorySyncResult = "updateRepositorySyncResult",
1056
+ GetUtilityModels = "getUtilityModels",
1057
+ UtilityModelsResponse = "utilityModelsResponse",
1058
+ UpdateUtilityModels = "updateUtilityModels",
1059
+ GetServerProxyState = "getServerProxyState",
1060
+ ServerProxyStateResponse = "serverProxyStateResponse",
1061
+ SetServerProxyEnabled = "setServerProxyEnabled",
1062
+ SetServerProxyAllowOverride = "setServerProxyAllowOverride",
1063
+ GetCachedServerUrl = "getCachedServerUrl",
1064
+ CachedServerUrlResponse = "cachedServerUrlResponse"
1065
+ }
1066
+ /**
1067
+ * Top-level const aliases for the BYO Utility Models message-type
1068
+ * members above. Re-exported as `export const` (rather than just enum
1069
+ * members) because `@serviceme/devtools-shared` ships as CommonJS — bare
1070
+ * `import { GetUtilityModels } from "@serviceme/devtools-shared"` from an ESM
1071
+ * module resolves to `undefined` unless the binding is also exported
1072
+ * as a top-level const. The webview's vitest tests compare against
1073
+ * these by reference; without the const aliases, every
1074
+ * `c[0] === UpdateUtilityModels` check matches the mount-time
1075
+ * `vscode.post(GetUtilityModels)` call (because `undefined ===
1076
+ * undefined` is true). Keep both the enum members AND the consts in
1077
+ * sync; Task 5 (extension handler) uses the enum members, the webview
1078
+ * component + tests use the consts.
1079
+ */
1080
+ declare const GetUtilityModels: "getUtilityModels";
1081
+ declare const UtilityModelsResponse: "utilityModelsResponse";
1082
+ declare const UpdateUtilityModels: "updateUtilityModels";
1083
+ declare const SetCacheControlEnabled: "setCacheControlEnabled";
1084
+ declare const GetByomSettings: "getByomSettings";
1085
+ declare const ByomSettingsResponse: "byomSettingsResponse";
1086
+ declare const GetServerProxyState: "getServerProxyState";
1087
+ declare const ServerProxyStateResponse: "serverProxyStateResponse";
1088
+ declare const SetServerProxyEnabled: "setServerProxyEnabled";
1089
+ declare const SetServerProxyAllowOverride: "setServerProxyAllowOverride";
1090
+ declare const GetCachedServerUrl: "getCachedServerUrl";
1091
+ declare const CachedServerUrlResponse: "cachedServerUrlResponse";
1092
+ /**
1093
+ * Snapshot of the r7 server-proxy toggle (HTTP CONNECT through intranet
1094
+ * Server). Mirrors `IProxyConfigService`'s `ProxyState` shape so the
1095
+ * webview can render the same fields without a reshape.
1096
+ *
1097
+ * Returned by `GetServerProxyState` and broadcast as
1098
+ * `ServerProxyStateResponse` so the ProvidersTab's ServerProxyToggle
1099
+ * component can paint at the persisted state on mount.
1100
+ *
1101
+ * See `apps/extension/src/services/proxy/ProxyConfigService.ts` for
1102
+ * the authoritative source of truth.
1103
+ */
1104
+ type ServerProxySupportMode = "off" | "on" | "fallback" | "override";
1105
+ interface ServerProxySnapshot {
1106
+ httpProxy: string | undefined;
1107
+ httpProxyStrictSSL: boolean;
1108
+ httpProxySupport: ServerProxySupportMode;
1109
+ /** Self-flag at serverProxy.enabled; computed by the extension handler. */
1110
+ enabled: boolean;
1111
+ /**
1112
+ * rev.21 — opt-in to bypass the `proxySupport === "override"`
1113
+ * guard. Power users (e.g. corp environments that WANT all
1114
+ * extensions including GitHub Copilot Chat to route through the
1115
+ * corp tunnel) tick the "Allow under proxySupport=override"
1116
+ * checkbox in the Webview; this flag is persisted to
1117
+ * `~/.serviceme/server-proxy.json` alongside `enabled`.
1118
+ */
1119
+ allowOverride: boolean;
1120
+ /** Last server URL we toggled ON with. Used for re-toggling and UI hint. */
1121
+ lastServerUrl?: string;
1122
+ }
1123
+ /**
1124
+ * Payload for `SetServerProxyEnabled`. The extension handler is the
1125
+ * sole owner of `http.proxy` writes — the webview only sends the
1126
+ * desired state + (optional) target scope. Server URL is normally read
1127
+ * from the snapshot the webview already received via
1128
+ * `ServerProxyStateResponse`; if missing, the handler rejects with
1129
+ * `serverProxy.error.missingServerUrl`.
1130
+ *
1131
+ * `serverUrl` is an OVERRIDE: when supplied, the handler uses it
1132
+ * instead of the snapshot's `http.proxy` for the `enable` write
1133
+ * path. The Webview's "Use Cached Base URL" affordance relies on this
1134
+ * so the cached `ServerConnectionService.getBaseUrl()` value gets
1135
+ * applied as `http.proxy` even when `http.proxy` is currently empty.
1136
+ * Still subject to the `proxySupport !== "override"` server-side gate.
1137
+ */
1138
+ interface ServerProxyTogglePayload {
1139
+ enabled: boolean;
1140
+ /** Optional — defaults to Workspace. */
1141
+ target?: "workspace" | "global";
1142
+ /**
1143
+ * Optional — override the snapshot's `http.proxy` for the write.
1144
+ * Only honoured when `enabled === true`; ignored on disable so the
1145
+ * existing "only clear our own write" semantics stay intact.
1146
+ */
1147
+ serverUrl?: string;
1148
+ }
1149
+ /**
1150
+ * rev.21 — payload for `SetServerProxyAllowOverride`. Toggles the
1151
+ * `serverProxy.allowOverride` self-flag in `~/.serviceme/server-proxy.json`.
1152
+ * Persisted across VS Code restarts; defaults to `false` so users
1153
+ * must explicitly opt-in to the `override` mode bypass.
1154
+ */
1155
+ interface ServerProxyAllowOverridePayload {
1156
+ allowOverride: boolean;
1157
+ }
1158
+ /**
1159
+ * Reply to `GetCachedServerUrl`. Carries the cached base URL resolved
1160
+ * by `ServerConnectionService` (probe + cache of `SERVER_URL_CANDIDATES`)
1161
+ * and the same `proxySupport` snapshot for the UI to gate the
1162
+ * "Apply" button.
1163
+ *
1164
+ * `baseUrl: null` means the candidate probe has not yet resolved a
1165
+ * reachable Server — the Webview should hide the "Apply cached base
1166
+ * URL" affordance rather than offer a no-op button.
1167
+ */
1168
+ interface CachedServerUrlSnapshot {
1169
+ baseUrl: string | null;
1170
+ httpProxySupport: ServerProxySupportMode;
1171
+ }
1172
+ /**
1173
+ * Where the three `msDevTools.utilityModels.*` settings (and their
1174
+ * derived `chat.*` mirrors) are persisted.
1175
+ *
1176
+ * - `"user"` — VS Code `ConfigurationTarget.Global`. Persists across
1177
+ * workspaces; the user's "default utility model setup".
1178
+ * - `"workspace"` — VS Code `ConfigurationTarget.Workspace`. Persists
1179
+ * to `<workspace>/.vscode/settings.json` only; overrides the user
1180
+ * scope when the workspace file explicitly sets a key.
1181
+ *
1182
+ * VS Code itself implements multi-scope fallback (a workspace value
1183
+ * wins over a user value when both are present), so we only need to
1184
+ * write to ONE target — the other scope just naturally inherits.
1185
+ */
1186
+ type UtilityModelScope = "user" | "workspace";
1187
+ /**
1188
+ * Per-field source for UI "this value comes from user / workspace /
1189
+ * default" indicators. Mirrors the `ConfigurationInspectionScope`
1190
+ * categories of `vscode.WorkspaceConfiguration.inspect(key)`.
1191
+ */
1192
+ interface UtilityModelsSource {
1193
+ small: "user" | "workspace" | "default";
1194
+ medium: "user" | "workspace" | "default";
1195
+ fallbackToCopilot: "user" | "workspace" | "default";
1196
+ }
1197
+ /**
1198
+ * The value Copilot is ACTUALLY using right now — VS Code's real
1199
+ * merged resolution (workspace always wins over user when both are
1200
+ * set), independent of which scope tab the UI currently has selected
1201
+ * for editing. Computed via `configSection.get(key, default)`, the
1202
+ * same call VS Code itself uses to resolve a setting.
1203
+ */
1204
+ interface UtilityModelsEffective {
1205
+ small: string;
1206
+ medium: string;
1207
+ fallbackToCopilot: boolean;
1208
+ }
1209
+ /**
1210
+ * Snapshot of the three msDevTools.utilityModels.* settings plus the
1211
+ * currently-enabled BYO model list (qualified `${providerId}::${modelId}`
1212
+ * ids, sorted, deduplicated). Returned by GetUtilityModels and by
1213
+ * UpdateUtilityModels after a successful write.
1214
+ *
1215
+ * - `scope` is the SCOPE THE WEBVIEW LAST WROTE / the scope the handler
1216
+ * will use for the next write. Computed from
1217
+ * `configSection.inspect(...)` per field and reduced to a single
1218
+ * scope: if ANY field is overridden at the workspace level, the
1219
+ * UI surfaces "workspace" (the user is actively overriding at
1220
+ * workspace scope). Otherwise "user".
1221
+ * - `source` lets the UI render per-field origin badges without
1222
+ * re-running `inspect()` client-side.
1223
+ * - `small` / `medium` / `fallbackToCopilot` are the values stored AT
1224
+ * `scope` specifically — used to populate the editing dropdowns for
1225
+ * whichever scope tab is selected. `effective` is the SEPARATE,
1226
+ * real merged value Copilot uses; the collapsed-section summary
1227
+ * badges must read from `effective`, not from these fields, so the
1228
+ * title always shows what's actually in effect regardless of which
1229
+ * scope tab happens to be open.
1230
+ */
1231
+ interface UtilityModelsSnapshot {
1232
+ small: string;
1233
+ medium: string;
1234
+ fallbackToCopilot: boolean;
1235
+ enabledModelIds: string[];
1236
+ /** Effective scope for the next write. Defaults to `"user"`. */
1237
+ scope: UtilityModelScope;
1238
+ /** Per-field source (which scope actually defined the value). */
1239
+ source?: UtilityModelsSource;
1240
+ /** The real merged value Copilot uses right now. See docstring above. */
1241
+ effective: UtilityModelsEffective;
1242
+ }
1243
+ /**
1244
+ * Partial-update payload for UpdateUtilityModels. Omit a field to
1245
+ * leave it untouched; supply `""` to write the empty string (the user
1246
+ * wants the slot cleared).
1247
+ *
1248
+ * `scope` is the persistence target for the write. Omit it to keep
1249
+ * the previous scope; the first call (with no prior scope) defaults
1250
+ * to `"user"`.
1251
+ */
1252
+ interface UpdateUtilityModelsPayload {
1253
+ small?: string;
1254
+ medium?: string;
1255
+ fallbackToCopilot?: boolean;
1256
+ scope?: UtilityModelScope;
1257
+ }
1258
+ /**
1259
+ * Per-provider BYOM toggle payload. Used by `SetCacheControlEnabled` —
1260
+ * the extension handler writes the `cacheEnabled` key for the given
1261
+ * provider id.
1262
+ *
1263
+ * The handler writes through `byomSettings.ts` so the
1264
+ * `cacheEnabled` flag lands at the same
1265
+ * `msDevTools.byom.<providerId>.<key>` key that the
1266
+ * `LmChatProviderRegistrar` already reads on every chat invocation.
1267
+ */
1268
+ interface ByomTogglePayload {
1269
+ id: string;
1270
+ enabled: boolean;
1271
+ }
1272
+ /**
1273
+ * Snapshot of every BYOM provider's runtime toggles, keyed by
1274
+ * providerId. Returned by `GetByomSettings` and broadcast as
1275
+ * `ByomSettingsResponse` so the Webview can hydrate the per-provider
1276
+ * cache_control switch at mount time without a per-provider
1277
+ * round-trip.
1278
+ *
1279
+ * Providers that have never been touched fall through to the
1280
+ * `byomSettings.ts` default (`cacheEnabled: true`) — the Webview
1281
+ * does NOT need a separate "default" branch.
1282
+ */
1283
+ type ByomSettingsSnapshot = Record<string, ByomProviderToggles>;
1284
+ /**
1285
+ * Per-provider BYOM toggles. Mirrors the runtime contract in
1286
+ * `apps/extension/src/services/providers/byomSettings.ts`
1287
+ * (`ByomProviderToggles`) — keep the two in lock-step so a
1288
+ * `GetByomSettings` payload round-trips without reshape code.
1289
+ */
1290
+ interface ByomProviderToggles {
1291
+ /** P0.1 + P0.2 cache toggle — default TRUE (main-line decision Q1). */
1292
+ cacheEnabled: boolean;
1293
+ }
1294
+ /**
1295
+ * Strict-typed shape for the inbound messages currently handled by
1296
+ * `useAppMessages`. Stage 1 deliberately covers ONLY the variants whose
1297
+ * payload types already live in `@serviceme/devtools-shared`
1298
+ * (re-exported from the package index via `types/protocol-contracts` as
1299
+ * `BridgeRepoEntry` / `BridgeSkillRepoEntry`) or here as named types
1300
+ * (`LinkedSkillPayloadEntry`). The variants whose payload types live in
1301
+ * `apps/webview-ui/src/types` (ExternalTool, AzureProfile, ApiConfig) stay
1302
+ * on the enum path for now — moving those types into `@serviceme/devtools-shared` is
1303
+ * its own refactor and not in scope.
1304
+ *
1305
+ * Each variant lists ONLY the fields the hook actually reads; optional
1306
+ * fields are explicit (`workspaceOpen` on `UpdateSkillRepoCatalog`).
1307
+ */
1308
+ type WebviewInboundMessage = {
1309
+ type: typeof WebviewMessageType.UpdateRepositoryList;
1310
+ repos: BridgeRepoEntry[];
1311
+ } | {
1312
+ type: typeof WebviewMessageType.UpdateRepositoryUpdate;
1313
+ repos: BridgeRepoEntry[];
1314
+ } | {
1315
+ type: typeof WebviewMessageType.UpdateSkillRepoCatalog;
1316
+ entries?: BridgeSkillRepoEntry[];
1317
+ workspaceOpen?: boolean;
1318
+ } | {
1319
+ type: typeof WebviewMessageType.UpdateLinkedSkills;
1320
+ links: LinkedSkillPayloadEntry[];
1321
+ kind: "skill" | "agent";
1322
+ };
1323
+ /**
1324
+ * One entry inside the `UpdateLinkedSkills.links` array. Mirrors the
1325
+ * inline cast in `useAppMessages.ts:102-106`. Keeping it named (rather
1326
+ * than re-declared at the call site) lets the discriminated union's
1327
+ * `links` field carry a real type instead of `unknown[]`.
1328
+ */
1329
+ interface LinkedSkillPayloadEntry {
1330
+ repoId: string;
1331
+ name: string;
1332
+ scope?: string;
1333
+ }
1334
+
1335
+ /** Live execution state pushed to webview during task execution */
1336
+ interface TaskExecutionState {
1337
+ executionId: string;
1338
+ taskId: string;
1339
+ taskName: string;
1340
+ status: "running" | "success" | "failure" | "timeout" | "cancelled";
1341
+ startedAt: string;
1342
+ finishedAt?: string;
1343
+ output: string;
1344
+ error?: string;
1345
+ }
1346
+ /** Log file schema for .serviceme/scheduled-tasks-log.json */
1347
+ interface ScheduledTasksLogFile {
1348
+ version: 1;
1349
+ logs: TaskExecutionLog[];
1350
+ }
1351
+
1352
+ /**
1353
+ * Environment-agnostic cast helpers.
1354
+ *
1355
+ * These centralize the handful of `as unknown as` / `JSON.parse` fallbacks
1356
+ * that used to be scattered across the codebase. They are intentionally
1357
+ * thin wrappers that preserve the exact runtime behavior of the original
1358
+ * inline casts — they exist for consistency and discoverability, not to
1359
+ * change semantics. No `protocol`-level imports are used here so the helpers
1360
+ * stay usable from any package (extension, server, webview) without pulling
1361
+ * in transport types.
1362
+ */
1363
+ /**
1364
+ * Narrow an unknown payload into a typed shape.
1365
+ *
1366
+ * Equivalent to `raw as T`. Retains the original "blind cast" semantics used
1367
+ * for scheduled-task payloads: callers own the contract and we do not validate
1368
+ * the runtime shape here. Keeping the cast in one place makes the intent
1369
+ * (and the assumption) explicit and grep-able.
1370
+ */
1371
+ declare function parsePayload<T>(raw: unknown): T;
1372
+ /**
1373
+ * Best-effort extraction of an `AbortSignal` from an inbound request object.
1374
+ *
1375
+ * The original code read `req.signal` via `req as unknown as { signal?: AbortSignal }`,
1376
+ * which would return whatever sat on `.signal` — including a non-`AbortSignal`
1377
+ * value. To avoid leaking an invalid signal into downstream `fetch`/`undici`
1378
+ * calls (where a non-`AbortSignal` signal throws), we only return the value when
1379
+ * it is a genuine `AbortSignal` instance; otherwise we return `undefined`, which
1380
+ * is the same as "no signal". In practice the request signal is always a real
1381
+ * `AbortSignal`, so behavior is unchanged for every production path.
1382
+ */
1383
+ declare function asAbortSignal(input: unknown): AbortSignal | undefined;
1384
+ /**
1385
+ * Parse a JSON string, returning `fallback` when parsing fails.
1386
+ *
1387
+ * Equivalent to wrapping `JSON.parse(text)` in a try/catch. Used to replace
1388
+ * the previous `response.json().catch(() => ({}))` patterns (callers pair this
1389
+ * with their own `.catch` so that a body-read failure still yields the same
1390
+ * fallback as a malformed-body failure).
1391
+ */
1392
+ declare function safeJson<T>(text: string, fallback: T): T;
1393
+
1394
+ export { type AIModelConfig, type AIModelInfo, type AgentPermissionSummary, type AgentToolPermission, type AgentToolRiskLevel, BUILTIN_PROVIDER_PRESETS, type BalanceEntry, type BridgeLinkMode, type BridgeLinkedSkill, type BridgeRepoEntry, type BridgeRepoSyncPull, type BridgeSkillKind, type BridgeSkillRepoEntry, type BridgeSkillRepoFile, type ByomProviderToggles, ByomSettingsResponse, type ByomSettingsSnapshot, type ByomTogglePayload, CERTIFICATE_BUNDLE_FORMATS, CachedServerUrlResponse, type CachedServerUrlSnapshot, type CertificateBundleEnvironmentSupport, type CertificateBundleFormat, type CertificateBundleFormatDescriptor, type CodingPlanUsage, type CommandPayload, type CuratedModelMetadata, type DeepseekBalanceEntry, type DeepseekUsage, type DownloadCertificateBundleRequest, type DownloadCertificateBundleResponse, GIT_REMOTE_HOST_ALIASES, type GenericBalanceUsage, GetByomSettings, GetCachedServerUrl, GetServerProxyState, GetUtilityModels, type GitHubOrgMembershipCheckResult, type GitHubOrgMembershipStatus, type GitHubUser, type GithubCopilotCliPayload, type HttpRequestPayload, type ILogger, type LinkedSkillPayloadEntry, LogLevel, MODEL_METADATA, type MinimaxUsage, type ModelDetail, type ModelPriceCategory, type ModelPricing, type ModelThinkingSchema, PROVIDER_BASE_URL_PRESETS, PROVIDER_CACHE_CONTROL_METADATA, type ProviderBaseUrlPreset, type ProviderCacheControlMetadata, type ProviderConfig, type ProviderModel, type ProviderMutationPayload, type ProviderTestResult, type ProviderType, type ProviderUsageData, type ProviderUsageKind, type ProviderUsageResult, type ProvidersResponsePayload, type PublicProvider, type ScheduledTask, type ScheduledTaskType, type ScheduledTaskV1, type ScheduledTasksConfig, type ScheduledTasksLogFile, type ServerProxyAllowOverridePayload, type ServerProxySnapshot, ServerProxyStateResponse, type ServerProxySupportMode, type ServerProxyTogglePayload, SetCacheControlEnabled, SetServerProxyAllowOverride, SetServerProxyEnabled, type ShellPayload, type TaskExecutionLog, type TaskExecutionState, type TaskExecutionStatus, type TaskPayload, type TaskRunStatus, type TaskWorkspaceRef, UpdateUtilityModels, type UpdateUtilityModelsPayload, type UsageWindow, type UtilityModelScope, type UtilityModelsEffective, UtilityModelsResponse, type UtilityModelsSnapshot, type UtilityModelsSource, type WebviewInboundMessage, WebviewMessageType, __internal, asAbortSignal, buildGitHubLocalEmail, checkGitHubOrgMembership, createConsoleLogger, currencyForBaseUrl, fetchGitHubUser, getBuiltinProviderPreset, getGitHubOrgMembership, getProviderBaseUrlPresets, isGitHubLocalEmail, isProviderCacheControlAware, isValidCanonicalSlug, lookupModelMetadata, normalizeCanonicalSlug, normalizeErrorForLog, normalizeGitUrl, parsePayload, resolvePrimaryEmail, safeJson };