@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.
- package/LICENSE.md +46 -0
- package/README.md +21 -0
- package/dist/index.d.mts +1394 -0
- package/dist/index.d.ts +1394 -0
- package/dist/index.js +1283 -0
- package/dist/index.mjs +1217 -0
- package/package.json +51 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|