Atlas › Test

activeSessionsChanged with correct count|occurrence=1

Exact test identity: mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentHostStateManager.test|title=AgentHostStateManager turnStarted dispatches root/activeSessionsChanged with correct count|occurrence=1

Package
mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentHostStateManager.test|title=AgentHostStateManager turnStarted dispatches root
Suite / test hierarchy
activeSessionsChanged with correct count|occurrence=1
Test
activeSessionsChanged with correct count|occurrence=1
Introduced at
activeSessionsChanged with correct count|occurrence=1 Frontier kind: Test frontier
Covered ranges
2101
Covered lines
20921
Covered files
96

Covered source

Expand a file to inspect source; the > gutter marks covered lines.

src/vs/platform/agentHost/common/state/protocol/channels-chat/state.ts 1557 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { ModelSelection } from '../channels-root/state.js';
10 > import type { AgentSelection, McpAuthRequirement, SessionStatus } from '../channels-session/state.js';
11 > import type { ContentRef, ErrorInfo, FileEdit, StringOrMarkdown, TextRange, TextSelection, URI, UsageInfo } from '../common/state.js';
12 >
13 > // ─── Chat State ──────────────────────────────────────────────────────────────
14 >
15 > /**
16 > * Full state for a single chat, loaded when a client subscribes to the chat's
17 > * URI.
18 > *
19 > * The lightweight catalog representation of a chat is {@link ChatSummary},
20 > * carried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`
21 > * **denormalizes** every {@link ChatSummary} field directly onto itself so
22 > * subscribers receive one flat object instead of having to merge a nested
23 > * `summary` sub-object. Producers MUST keep the two representations
24 > * consistent: any change to the inlined fields below SHOULD also be
25 > * announced on the parent session via the matching
26 > * {@link SessionChatUpdatedAction | `session/chatUpdated`} action.
27 > *
28 > * @category Chat State
29 > */
30 > export interface ChatState {
31 > // ── Summary fields (denormalized from ChatSummary) ─────────────────
32 > /** Chat URI */
33 > resource: URI;
34 > /** Chat title */
35 > title: string;
36 > /** Current chat status (reuses SessionStatus shape) */
37 > status: SessionStatus;
38 > /** Human-readable description of what the chat is currently doing */
39 > activity?: string;
40 > /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */
41 > modifiedAt: string;
42 > /** How this chat came into existence */
43 > origin?: ChatOrigin;
44 > /**
45 > * How the user can interact with this chat. See {@link ChatInteractivity}.
46 > *
47 > * Supports agent-team patterns where worker chats are read-only or hidden.
48 > * Absence defaults to {@link ChatInteractivity.Full} for backward
49 > * compatibility.
50 > */
51 > interactivity?: ChatInteractivity;
52 > /**
53 > * The subset of the session's
54 > * {@link SessionState.workingDirectories | `workingDirectories`} that this
55 > * chat's agent has tool access to. Every entry MUST be present in the owning
56 > * session's `workingDirectories`; servers MUST reject a
57 > * `chat/workingDirectorySet` action that violates this constraint.
58 > *
59 > * When absent, the chat inherits the full session set. When present but empty
60 > * (not recommended), the chat has no working-directory tool access at all.
61 > *
62 > * Dispatch `chat/workingDirectorySet` / `chat/workingDirectoryRemoved` to
63 > * update the subset on a running chat.
64 > */
65 > workingDirectories?: URI[];
66 > /**
67 > * The chat's primary working directory — the distinguished root this chat is
68 > * centered on (e.g. the agent's process root for this chat, the default
69 > * location for relative paths). MUST be one of this chat's effective working
70 > * directories ({@link workingDirectories}, or the session's set when that is
71 > * absent). Present when the agent advertises
72 > * {@link MultipleWorkingDirectoriesCapability.requiresPrimary}.
73 > *
74 > * **Read-only and fixed at creation.** It is set from
75 > * {@link CreateChatParams.primaryWorkingDirectory} (or, for the session's
76 > * default chat, {@link CreateSessionParams.primaryWorkingDirectory}) and does
77 > * not change over the chat's lifetime — there is no action to mutate it, and
78 > * it does not participate in `session/chatUpdated`.
79 > */
80 > primaryWorkingDirectory?: URI;
81 >
82 > // ── Conversation contents ──────────────────────────────────────────
83 > /** Completed turns */
84 > turns: Turn[];
85 > /**
86 > * Cursor for loading older completed turns into this chat state.
87 > *
88 > * Presence means `turns` is a tail window and more historical turns are
89 > * available. Pass this opaque cursor to `fetchTurns`; the host MUST insert
90 > * the loaded turns into state and update or clear this cursor before
91 > * responding. Absence means the state contains all retained turns.
92 > */
93 > turnsNextCursor?: string;
94 > /** Currently in-progress turn */
95 > activeTurn?: ActiveTurn;
96 > /** Message to inject into the current turn at a convenient point */
97 > steeringMessage?: PendingMessage;
98 > /** Messages to send automatically as new turns after the current turn finishes */
99 > queuedMessages?: PendingMessage[];
100 > /**
101 > * The user's in-progress draft input for this chat — the message they are
102 > * composing but have not sent yet, including its
103 > * {@link Message.model | model} / {@link Message.agent | agent} selection
104 > * and attachments.
105 > *
106 > * Clients MAY periodically sync their local input state into this field so
107 > * a draft survives reloads and is visible to other clients viewing the same
108 > * chat. Eager syncing is **not** required — clients SHOULD debounce and MAY
109 > * sync only at convenient points. When presenting input UI for an existing
110 > * chat, clients SHOULD use any `draft` to initialize their input state.
111 > * Cleared (set to `undefined`) once the message is sent.
112 > */
113 > draft?: Message;
114 > /**
115 > * Additional provider-specific metadata for this chat.
116 > */
117 > _meta?: Record<string, unknown>;
118 > }
119 >
120 > /**
121 > * Lightweight catalog entry for a chat, carried in
122 > * {@link SessionState.chats | `SessionState.chats`}. The full conversation
123 > * lives in {@link ChatState}, which inlines (denormalizes) every field below.
124 > *
125 > * @category Chat State
126 > */
127 > export interface ChatSummary {
128 > /** Chat URI */
129 > resource: URI;
130 > /** Chat title */
131 > title: string;
132 > /** Current chat status (reuses SessionStatus shape) */
133 > status: SessionStatus;
134 > /** Human-readable description of what the chat is currently doing */
135 > activity?: string;
136 > /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */
137 > modifiedAt: string;
138 > /** How this chat came into existence */
139 > origin?: ChatOrigin;
140 > /**
141 > * How the user can interact with this chat. See {@link ChatInteractivity}.
142 > *
143 > * Supports agent-team patterns where worker chats are read-only or hidden.
144 > * Absence defaults to {@link ChatInteractivity.Full} for backward
145 > * compatibility.
146 > */
147 > interactivity?: ChatInteractivity;
148 > /**
149 > * The subset of the session's working directories this chat uses.
150 > * See {@link ChatState.workingDirectories} for the full semantics.
151 > */
152 > workingDirectories?: URI[];
153 > /**
154 > * The chat's primary working directory.
155 > * See {@link ChatState.primaryWorkingDirectory} for the full semantics.
156 > */
157 > primaryWorkingDirectory?: URI;
158 > }
159 >
160 > /**
161 > * Discriminant for {@link ChatOrigin} — how a chat came into existence.
162 > *
163 > * @category Chat State
164 > */
165 > export const enum ChatOriginKind {
166 > /** User created the chat explicitly (e.g. via the host UI). */
167 > User = 'user',
168 > /** Forked from an existing chat at a specific turn. */
169 > Fork = 'fork',
170 > /** Created as an independent side conversation from a specific turn. */
171 > SideChat = 'sideChat',
172 > /** Spawned by a tool call running in another chat (e.g. a sub-agent delegation). */
173 > Tool = 'tool',
174 > }
175 >
176 > /**
177 > * Immutable selected-text snapshot captured when a side chat is created.
178 > *
179 > * The host records this exact text when it accepts `createChat`; later changes
180 > * to the source chat do not alter it.
181 > *
182 > * @category Chat State
183 > */
184 > export interface SideChatSelection {
185 > /**
186 > * Exact selected-text snapshot captured at `createChat` acceptance.
187 > *
188 > * MUST be non-empty.
189 > */
190 > text: string;
191 > /**
192 > * Optional provenance for the response part that contained {@link text} when
193 > * the host took the snapshot.
194 > *
195 > * Advisory only: this is not a live range or offset and MUST NOT be used to
196 > * recompute `text`.
197 > */
198 > responsePartId?: string;
199 > }
200 >
201 > /**
202 > * How a chat came into existence. Clients MAY use it to render
203 > * contextual UI (parent indicators, fork markers, "spawned by tool" badges).
204 > *
205 > * Fork and side-chat origins both carry a stable top-level `turnId` alongside
206 > * their discriminated `kind` value instead of snapshotting whether that turn
207 > * was active or historical at creation time. Consumers resolve the identifier
208 > * against the
209 > * source chat's current `activeTurn` or retained `turns` as needed.
210 > *
211 > * When a host accepts side-chat creation from the source chat's current active
212 > * turn, it snapshots the retained history plus that turn's current user
213 > * message and any partial assistant response already available. Later
214 > * source-turn deltas do not retroactively change the created side chat's
215 > * starting context, and once the source turn completes it is still referenced
216 > * by the same `turnId`. Side-chat origins MAY also retain an immutable
217 > * {@link SideChatSelection | selected-text snapshot} captured at acceptance
218 > * time; any `responsePartId` there is provenance only, not a range.
219 > *
220 > * The `tool` variant records a tool-spawned worker from the worker's side: its
221 > * `chat`/`toolCallId` identify the spawning tool call in the parent chat. This
222 > * is the canonical record of the spawn relationship. The same edge is surfaced
223 > * from the parent's side by {@link ToolResultSubagentContent}, whose `resource`
224 > * is this chat's URI; hosts MUST keep the two consistent.
225 > *
226 > * @category Chat State
227 > */
228 > export type ChatOrigin =
229 > | { kind: ChatOriginKind.User }
230 > | { kind: ChatOriginKind.Fork; chat: URI; turnId: string }
231 > | { kind: ChatOriginKind.SideChat; chat: URI; turnId: string; selection?: SideChatSelection }
232 > | { kind: ChatOriginKind.Tool; chat: URI; toolCallId: string };
233 >
234 > /**
235 > * How a user can interact with a chat.
236 > *
237 > * - `Full` — user can send messages and watch (default when absent)
238 > * - `ReadOnly` — user can watch but not send messages (e.g. agent team workers)
239 > * - `Hidden` — internal worker not shown in UI at all
240 > *
241 > * Supports the agent-team pattern where a lead chat is fully interactive and
242 > * worker chats are read-only (visible for observability) or hidden (internal
243 > * implementation detail). The harness sets this based on the chat's role;
244 > * the UI uses it to show appropriate controls.
245 > *
246 > * @category Chat State
247 > */
248 > export const enum ChatInteractivity {
249 > /** User can send messages and watch (default when absent) */
250 > Full = 'full',
251 > /** User can watch but not send messages */
252 > ReadOnly = 'read-only',
253 > /** Internal worker not shown in UI at all */
254 > Hidden = 'hidden',
255 > }
256 >
257 > // ─── Pending Message Types ───────────────────────────────────────────────────
258 >
259 > /**
260 > * Discriminant for pending message kinds.
261 > *
262 > * @category Pending Message Types
263 > */
264 > export const enum PendingMessageKind {
265 > /** Injected into the current turn at a convenient point */
266 > Steering = 'steering',
267 > /** Sent automatically as a new turn after the current turn finishes */
268 > Queued = 'queued',
269 > }
270 >
271 > /**
272 > * A message queued for future delivery to the agent.
273 > *
274 > * Steering messages are injected into the current turn mid-flight.
275 > * Queued messages are automatically started as new turns after the
276 > * current turn naturally finishes.
277 > *
278 > * @category Pending Message Types
279 > */
280 > export interface PendingMessage {
281 > /** Unique identifier for this pending message */
282 > id: string;
283 > /** The message that will start the next turn */
284 > message: Message;
285 > }
286 >
287 >
288 > // ─── Chat Input Types ────────────────────────────────────────────────────
289 >
290 > /**
291 > * How a client completed an input request.
292 > *
293 > * @category Chat Input Types
294 > */
295 > export const enum ChatInputResponseKind {
296 > Accept = 'accept',
297 > Decline = 'decline',
298 > Cancel = 'cancel',
299 > }
300 >
301 > /**
302 > * Question/input control kind.
303 > *
304 > * @category Chat Input Types
305 > */
306 > export const enum ChatInputQuestionKind {
307 > Text = 'text',
308 > Number = 'number',
309 > Integer = 'integer',
310 > Boolean = 'boolean',
311 > SingleSelect = 'single-select',
312 > MultiSelect = 'multi-select',
313 > }
314 >
315 > /**
316 > * A choice in a select-style question.
317 > *
318 > * @category Chat Input Types
319 > */
320 > export interface ChatInputOption {
321 > /** Stable option identifier; for MCP enum values this is the enum string */
322 > id: string;
323 > /** Display label */
324 > label: string;
325 > /** Optional secondary text */
326 > description?: string;
327 > /** Whether this option is the recommended/default choice */
328 > recommended?: boolean;
329 > }
330 >
331 > interface ChatInputQuestionBase {
332 > /** Stable question identifier used as the key in `answers` */
333 > id: string;
334 > /** Short display title */
335 > title?: string;
336 > /** Prompt shown to the user */
337 > message: string;
338 > /** Whether the user must answer this question to accept the request */
339 > required?: boolean;
340 > }
341 >
342 > /** Text question within a chat input request. */
343 > export interface ChatInputTextQuestion extends ChatInputQuestionBase {
344 > kind: ChatInputQuestionKind.Text;
345 > /** Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` */
346 > format?: string;
347 > /** Minimum string length */
348 > min?: number;
349 > /** Maximum string length */
350 > max?: number;
351 > /** Default text */
352 > defaultValue?: string;
353 > }
354 >
355 > /** Numeric question within a chat input request. */
356 > export interface ChatInputNumberQuestion extends ChatInputQuestionBase {
357 > kind: ChatInputQuestionKind.Number | ChatInputQuestionKind.Integer;
358 > /**
359 > * Minimum value
360 > * @format float
361 > */
362 > min?: number;
363 > /**
364 > * Maximum value
365 > * @format float
366 > */
367 > max?: number;
368 > /**
369 > * Default numeric value
370 > * @format float
371 > */
372 > defaultValue?: number;
373 > }
374 >
375 > /** Boolean question within a chat input request. */
376 > export interface ChatInputBooleanQuestion extends ChatInputQuestionBase {
377 > kind: ChatInputQuestionKind.Boolean;
378 > /** Default boolean value */
379 > defaultValue?: boolean;
380 > }
381 >
382 > /** Single-select question within a chat input request. */
383 > export interface ChatInputSingleSelectQuestion extends ChatInputQuestionBase {
384 > kind: ChatInputQuestionKind.SingleSelect;
385 > /** Options the user may select from */
386 > options: ChatInputOption[];
387 > /** Whether the user may enter text instead of selecting an option */
388 > allowFreeformInput?: boolean;
389 > }
390 >
391 > /** Multi-select question within a chat input request. */
392 > export interface ChatInputMultiSelectQuestion extends ChatInputQuestionBase {
393 > kind: ChatInputQuestionKind.MultiSelect;
394 > /** Options the user may select from */
395 > options: ChatInputOption[];
396 > /** Whether the user may enter text in addition to selecting options */
397 > allowFreeformInput?: boolean;
398 > /** Minimum selected item count */
399 > min?: number;
400 > /** Maximum selected item count */
401 > max?: number;
402 > }
403 >
404 > /**
405 > * One question within a chat input request.
406 > *
407 > * @category Chat Input Types
408 > */
409 > export type ChatInputQuestion = ChatInputTextQuestion
410 > | ChatInputNumberQuestion
411 > | ChatInputBooleanQuestion
412 > | ChatInputSingleSelectQuestion
413 > | ChatInputMultiSelectQuestion;
414 >
415 > /**
416 > * The request payload carried by an {@link InputRequestResponsePart}.
417 > *
418 > * The server creates or replaces the containing response part with
419 > * `chat/inputRequested`. Clients sync drafts with `chat/inputAnswerChanged`
420 > * and submit responses with `chat/inputCompleted`.
421 > *
422 > * @category Chat Input Types
423 > */
424 > export interface ChatInputRequest {
425 > /** Stable request identifier */
426 > id: string;
427 > /** Display message for the request as a whole */
428 > message?: string;
429 > /** URL the user should review or open, for URL-style elicitations */
430 > url?: URI;
431 > /** Ordered questions to ask the user */
432 > questions?: ChatInputQuestion[];
433 > /** Current draft or submitted answers, keyed by question ID */
434 > answers?: Record<string, ChatInputAnswer>;
435 > }
436 >
437 > /**
438 > * Answer value kind.
439 > *
440 > * @category Chat Input Types
441 > */
442 > export const enum ChatInputAnswerValueKind {
443 > Text = 'text',
444 > Number = 'number',
445 > Boolean = 'boolean',
446 > Selected = 'selected',
447 > SelectedMany = 'selected-many',
448 > }
449 >
450 > /**
451 > * Value captured for one answer.
452 > *
453 > * @category Chat Input Types
454 > */
455 > export interface ChatInputTextAnswerValue {
456 > kind: ChatInputAnswerValueKind.Text;
457 > value: string;
458 > }
459 >
460 > export interface ChatInputNumberAnswerValue {
461 > kind: ChatInputAnswerValueKind.Number;
462 > /** @format float */
463 > value: number;
464 > }
465 >
466 > export interface ChatInputBooleanAnswerValue {
467 > kind: ChatInputAnswerValueKind.Boolean;
468 > value: boolean;
469 > }
470 >
471 > export interface ChatInputSelectedAnswerValue {
472 > kind: ChatInputAnswerValueKind.Selected;
473 > value: string;
474 > /** Free-form text entered instead of selecting an option */
475 > freeformValues?: string[];
476 > }
477 >
478 > export interface ChatInputSelectedManyAnswerValue {
479 > kind: ChatInputAnswerValueKind.SelectedMany;
480 > value: string[];
481 > /** Free-form text entered in addition to selected options */
482 > freeformValues?: string[];
483 > }
484 >
485 > export type ChatInputAnswerValue = ChatInputTextAnswerValue
486 > | ChatInputNumberAnswerValue
487 > | ChatInputBooleanAnswerValue
488 > | ChatInputSelectedAnswerValue
489 > | ChatInputSelectedManyAnswerValue;
490 >
491 > export interface ChatInputAnswered {
492 > /** Answer state */
493 > state: ChatInputAnswerState.Draft | ChatInputAnswerState.Submitted;
494 > /** Answer value */
495 > value: ChatInputAnswerValue;
496 > }
497 >
498 > export interface ChatInputSkipped {
499 > /** Answer state */
500 > state: ChatInputAnswerState.Skipped;
501 > /** Free-form reason or value captured while skipping, if any */
502 > freeformValues?: string[];
503 > }
504 >
505 > /**
506 > * Answer lifecycle state.
507 > *
508 > * @category Chat Input Types
509 > */
510 > export const enum ChatInputAnswerState {
511 > Draft = 'draft',
512 > Submitted = 'submitted',
513 > Skipped = 'skipped',
514 > }
515 >
516 > /**
517 > * Draft, submitted, or skipped answer for one question.
518 > *
519 > * @category Chat Input Types
520 > */
521 > export type ChatInputAnswer = ChatInputAnswered | ChatInputSkipped;
522 >
523 >
524 > // ─── Turn Types ──────────────────────────────────────────────────────────────
525 >
526 > /**
527 > * How a turn ended.
528 > *
529 > * @category Turn Types
530 > */
531 > export const enum TurnState {
532 > Complete = 'complete',
533 > Cancelled = 'cancelled',
534 > Error = 'error',
535 > }
536 >
537 > /**
538 > * Discriminant for {@link MessageAttachment} variants.
539 > *
540 > * @category Turn Types
541 > */
542 > export const enum MessageAttachmentKind {
543 > /** A simple, opaque attachment whose representation is described by the producer. */
544 > Simple = 'simple',
545 > /** An attachment whose data is embedded inline as a base64 string. */
546 > EmbeddedResource = 'embeddedResource',
547 > /** An attachment that references a resource by URI. */
548 > Resource = 'resource',
549 > /** An attachment that references annotations on an annotations channel. */
550 > Annotations = 'annotations',
551 > /** An attachment that references a bounded transcript from another chat. */
552 > Chat = 'chat',
553 > }
554 >
555 > /**
556 > * A completed request/response cycle.
557 > *
558 > * @category Turn Types
559 > */
560 > export interface Turn {
561 > /** Turn identifier */
562 > id: string;
563 > /** ISO 8601 timestamp when this turn started. */
564 > startedAt?: string;
565 > /** Turn duration in milliseconds. */
566 > duration?: number;
567 > /** The message that initiated the turn */
568 > message: Message;
569 > /**
570 > * All response content in stream order: text, tool calls, reasoning, and content refs.
571 > *
572 > * Consumers should derive display text by concatenating markdown parts,
573 > * and find tool calls by filtering for `ToolCall` parts.
574 > */
575 > responseParts: ResponsePart[];
576 > /** Token usage info */
577 > usage: UsageInfo | undefined;
578 > /** How the turn ended */
579 > state: TurnState;
580 > /** Error details if state is `'error'` */
581 > error?: ErrorInfo;
582 > }
583 >
584 > /**
585 > * An in-progress turn — the assistant is actively streaming.
586 > *
587 > * @category Turn Types
588 > */
589 > export interface ActiveTurn {
590 > /** Turn identifier */
591 > id: string;
592 > /** ISO 8601 timestamp when this turn started. */
593 > startedAt: string;
594 > /** The message that initiated the turn */
595 > message: Message;
596 > /**
597 > * All response content in stream order: text, tool calls, reasoning, and content refs.
598 > *
599 > * Tool call parts include `pendingPermissions` when permissions are awaiting user approval.
600 > */
601 > responseParts: ResponsePart[];
602 > /** Token usage info */
603 > usage: UsageInfo | undefined;
604 > }
605 >
606 > /**
607 > * Discriminant for {@link MessageOrigin} — identifies who produced a message.
608 > *
609 > * @category Turn Types
610 > */
611 > export enum MessageKind {
612 > /** Sent directly by the user. */
613 > User = 'user',
614 > /**
615 > * Produced by the agent itself rather than the user — for example, an agent
616 > * that seeds the first message of a chat it spawned.
617 > */
618 > Agent = 'agent',
619 > /**
620 > * Produced by a tool rather than the user — for example, a tool that spawns a
621 > * worker chat whose first message carries a seed prompt.
622 > */
623 > Tool = 'tool',
624 > /** A system-generated notification rather than a direct user message. */
625 > SystemNotification = 'systemNotification',
626 > }
627 >
628 > /**
629 > * Identifies the origin of a {@link Message} — who produced it. For the message
630 > * that initiates a turn ({@link Turn.message}), this is also the origin of the
631 > * turn; for steering or queued messages it is just the origin of that message.
632 > *
633 > * @category Turn Types
634 > */
635 > export interface MessageOrigin {
636 > /** The kind of actor that produced the message. */
637 > kind: MessageKind;
638 > }
639 >
640 > /**
641 > * A message that initiates or steers a turn. Messages can originate from the
642 > * user, the agent, a tool, or be system-generated (see {@link MessageOrigin}).
643 > *
644 > * Attachments MAY be referenced inside {@link Message.text} via their
645 > * {@link MessageAttachmentBase.range} field. Attachments without a range are
646 > * still associated with the message but do not correspond to a specific span
647 > * in the text.
648 > *
649 > * @category Turn Types
650 > */
651 > export interface Message {
652 > /** Message text */
653 > text: string;
654 > /** The origin of the message */
655 > origin: MessageOrigin;
656 > /** File/selection attachments */
657 > attachments?: MessageAttachment[];
658 > /**
659 > * The model this message was, or will be, sent with.
660 > *
661 > * For historic user/agent messages this records the model actually used, so
662 > * a client editing or resending the message can retain that selection. For a
663 > * {@link ChatState.draft | draft} it carries the model the user picked for
664 > * the message they are composing. Absent means the agent host's default
665 > * model applies.
666 > */
667 > model?: ModelSelection;
668 > /**
669 > * The custom agent this message was, or will be, sent with.
670 > *
671 > * For historic messages this records the agent actually used; for a
672 > * {@link ChatState.draft | draft} it carries the agent the user picked.
673 > * Absent means no custom agent — the provider's default behavior applies.
674 > */
675 > agent?: AgentSelection;
676 > /**
677 > * Additional provider-specific metadata for this message.
678 > *
679 > * Clients MAY look for well-known keys here to provide enhanced UI, and
680 > * agent hosts MAY use it to carry context that does not fit any other
681 > * field. Mirrors the MCP `_meta` convention.
682 > */
683 > _meta?: Record<string, unknown>;
684 > }
685 >
686 > /**
687 > * Common fields shared by all {@link MessageAttachment} variants.
688 > *
689 > * @category Turn Types
690 > */
691 > export interface MessageAttachmentBase {
692 > /**
693 > * A human-readable label for the attachment (e.g. the filename of a file
694 > * attachment). Used for display in UI.
695 > */
696 > label: string;
697 >
698 > /**
699 > * If defined, the range in {@link Message.text} that references this
700 > * attachment. This is a text range, not a byte range.
701 > */
702 > range?: TextRange;
703 >
704 > /**
705 > * Advisory display hint for clients rendering this attachment. Recognized
706 > * values include:
707 > *
708 > * - `'image'`: the attachment is an image
709 > * - `'document'`: the attachment is a textual document
710 > * - `'symbol'`: the attachment is a code symbol (e.g. a function or class)
711 > * - `'directory'`: the attachment is a folder
712 > * - `'selection'`: the attachment is a selection within a document
713 > *
714 > * Implementations MAY provide additional values; clients SHOULD fall back
715 > * to a reasonable default when an unknown value is encountered.
716 > */
717 > displayKind?: string;
718 >
719 > /**
720 > * Additional implementation-defined metadata for the attachment.
721 > *
722 > * If the attachment was produced by the `completions` command, the client
723 > * MUST preserve every property of `_meta` originally returned by the agent
724 > * host when sending the user message containing the accepted completion.
725 > */
726 > _meta?: Record<string, unknown>;
727 > }
728 >
729 > /**
730 > * A simple, opaque attachment whose model representation is described by
731 > * the producer.
732 > *
733 > * @category Turn Types
734 > */
735 > export interface SimpleMessageAttachment extends MessageAttachmentBase {
736 > /** Discriminant */
737 > type: MessageAttachmentKind.Simple;
738 >
739 > /**
740 > * Representation of the attachment as it should be shown to the model.
741 > *
742 > * If the attachment was produced by the client, this property MUST be
743 > * defined so the agent host can correctly interpret the attachment. This
744 > * property MAY be omitted when the attachment originated from a
745 > * `completions` response.
746 > */
747 > modelRepresentation?: string;
748 > }
749 >
750 > /**
751 > * An attachment whose data is embedded inline as a base64 string.
752 > *
753 > * Use this for small binary payloads (e.g. a pasted image) that should be
754 > * delivered with the user message itself rather than fetched separately.
755 > *
756 > * @category Turn Types
757 > */
758 > export interface MessageEmbeddedResourceAttachment extends MessageAttachmentBase {
759 > /** Discriminant */
760 > type: MessageAttachmentKind.EmbeddedResource;
761 > /** Base64-encoded binary data */
762 > data: string;
763 > /** Content MIME type (e.g. `"image/png"`, `"application/pdf"`) */
764 > contentType: string;
765 > /**
766 > * Optional selection within the attached textual resource.
767 > *
768 > * Only meaningful for textual resources.
769 > */
770 > selection?: TextSelection;
771 > }
772 >
773 > /**
774 > * An attachment that references a resource by URI. The content is not
775 > * delivered inline; consumers can fetch it via `resourceRead` when needed.
776 > *
777 > * @category Turn Types
778 > */
779 > export interface MessageResourceAttachment extends MessageAttachmentBase, ContentRef {
780 > /** Discriminant */
781 > type: MessageAttachmentKind.Resource;
782 > /**
783 > * Optional selection within the referenced textual resource.
784 > *
785 > * Only meaningful for textual resources.
786 > */
787 > selection?: TextSelection;
788 > }
789 >
790 > /**
791 > * An attachment that references annotations on a session's annotations
792 > * channel (see {@link AnnotationsState}).
793 > *
794 > * When {@link annotationIds} is omitted the attachment references every
795 > * annotation on the channel; when present it references only the listed
796 > * {@link Annotation.id | annotation ids}.
797 > *
798 > * @category Turn Types
799 > */
800 > export interface MessageAnnotationsAttachment extends MessageAttachmentBase {
801 > /** Discriminant */
802 > type: MessageAttachmentKind.Annotations;
803 > /**
804 > * The annotations channel URI (typically `ahp-session:/<uuid>/annotations`).
805 > * Matches {@link AnnotationsSummary.resource}.
806 > */
807 > resource: URI;
808 > /**
809 > * Specific {@link Annotation.id | annotation ids} to reference. When
810 > * omitted, the attachment references all annotations on the channel.
811 > */
812 > annotationIds?: string[];
813 > }
814 >
815 > /**
816 > * An attachment that references a chat transcript through a fixed completed
817 > * turn.
818 > *
819 > * The referenced chat MUST belong to the same session as the message's chat.
820 > * The host resolves the transcript from its first retained turn through
821 > * `endTurn`, inclusive, when accepting the message. Later turns do not
822 > * change the context represented by an already-sent attachment.
823 > *
824 > * Hosts MUST NOT recursively expand chat attachments found inside the
825 > * referenced transcript. Clients SHOULD keep rendering `label` if the
826 > * referenced chat is later pruned, and treat opening `resource` as best-effort.
827 > *
828 > * @category Turn Types
829 > */
830 > export interface MessageChatAttachment extends MessageAttachmentBase {
831 > /** Discriminant */
832 > type: MessageAttachmentKind.Chat;
833 > /** URI of the referenced chat. */
834 > resource: URI;
835 > /** Last completed turn included in the referenced transcript. */
836 > endTurn: string;
837 > }
838 >
839 > /**
840 > * An attachment associated with a {@link Message}.
841 > *
842 > * @category Turn Types
843 > */
844 > export type MessageAttachment =
845 > | SimpleMessageAttachment
846 > | MessageEmbeddedResourceAttachment
847 > | MessageResourceAttachment
848 > | MessageAnnotationsAttachment
849 > | MessageChatAttachment;
850 >
851 > // ─── Response Parts ──────────────────────────────────────────────────────────
852 >
853 > /**
854 > * Discriminant for response part types.
855 > *
856 > * @category Response Parts
857 > */
858 > export const enum ResponsePartKind {
859 > Markdown = 'markdown',
860 > ContentRef = 'contentRef',
861 > ToolCall = 'toolCall',
862 > Reasoning = 'reasoning',
863 > SystemNotification = 'systemNotification',
864 > InputRequest = 'inputRequest',
865 > }
866 >
867 > /**
868 > * @category Response Parts
869 > */
870 > export interface MarkdownResponsePart {
871 > /** Discriminant */
872 > kind: ResponsePartKind.Markdown;
873 > /** Part identifier, used by `chat/delta` to target this part for content appends */
874 > id: string;
875 > /** Markdown content */
876 > content: string;
877 > }
878 >
879 > /**
880 > * A content part that's a reference to large content stored outside the state tree.
881 > *
882 > * @category Response Parts
883 > */
884 > export interface ResourceReponsePart extends ContentRef {
885 > /** Discriminant */
886 > kind: ResponsePartKind.ContentRef;
887 > }
888 >
889 > /**
890 > * A tool call represented as a response part.
891 > *
892 > * Tool calls are part of the response stream, interleaved with text and
893 > * reasoning. The `toolCall.toolCallId` serves as the part identifier for
894 > * actions that target this part.
895 > *
896 > * @category Response Parts
897 > */
898 > export interface ToolCallResponsePart {
899 > /** Discriminant */
900 > kind: ResponsePartKind.ToolCall;
901 > /** Full tool call lifecycle state */
902 > toolCall: ToolCallState;
903 > }
904 >
905 > /**
906 > * Reasoning/thinking content from the model.
907 > *
908 > * @category Response Parts
909 > */
910 > export interface ReasoningResponsePart {
911 > /** Discriminant */
912 > kind: ResponsePartKind.Reasoning;
913 > /** Part identifier, used by `chat/reasoning` to target this part for content appends */
914 > id: string;
915 > /** Accumulated reasoning text */
916 > content: string;
917 > }
918 >
919 > /**
920 > * @category Response Parts
921 > */
922 > export type ResponsePart =
923 > | MarkdownResponsePart
924 > | ResourceReponsePart
925 > | ToolCallResponsePart
926 > | ReasoningResponsePart
927 > | SystemNotificationResponsePart
928 > | InputRequestResponsePart;
929 >
930 > /**
931 > * A live or resolved input request (elicitation) in the turn response stream.
932 > *
933 > * The server inserts the part with `chat/inputRequested`. While
934 > * {@link response} is absent, clients can update answer drafts with
935 > * `chat/inputAnswerChanged` and submit a response with `chat/inputCompleted`.
936 > * Completion updates this part in place so its stream position is stable and
937 > * the full interaction remains durable and backfillable via `fetchTurns`.
938 > *
939 > * If the turn ends without a submitted response, the unresolved part remains
940 > * in the completed turn transcript with {@link response} absent.
941 > *
942 > * @category Response Parts
943 > */
944 > export interface InputRequestResponsePart {
945 > /** Discriminant */
946 > kind: ResponsePartKind.InputRequest;
947 > /**
948 > * The request, carrying its `id`, `message`, `url`, `questions`, and current
949 > * draft or submitted `answers`.
950 > */
951 > request: ChatInputRequest;
952 > /**
953 > * How the request was resolved. Absent until a client submits `accept`,
954 > * `decline`, or `cancel` with `chat/inputCompleted`.
955 > */
956 > response?: ChatInputResponseKind;
957 > }
958 >
959 > /**
960 > * A system notification surfaced as part of the response stream.
961 > *
962 > * System notifications are messages authored by the agent harness
963 > * that need to be visible to both the agent (for situational awareness) and
964 > * the user (for transcript continuity). Examples include "background subagent
965 > * X completed" or "task Y was cancelled".
966 > *
967 > * @category Response Parts
968 > */
969 > export interface SystemNotificationResponsePart {
970 > /** Discriminant */
971 > kind: ResponsePartKind.SystemNotification;
972 > /** The text of the system notification */
973 > content: StringOrMarkdown;
974 > /**
975 > * Additional provider-specific metadata for this notification.
976 > *
977 > * A host MAY attach a machine-readable descriptor of what triggered the
978 > * notification so clients can categorize, icon, group, filter, or localize
979 > * it without parsing `content`. Clients MAY look for well-known keys here to
980 > * provide enhanced UI, and MUST render coherently from `content` alone when
981 > * `_meta` is absent or unrecognized.
982 > */
983 > _meta?: Record<string, unknown>;
984 > }
985 >
986 >
987 > // ─── Tool Call Types ─────────────────────────────────────────────────────────
988 >
989 > /**
990 > * Status of a tool call in the lifecycle state machine.
991 > *
992 > * @category Tool Call Types
993 > */
994 > export const enum ToolCallStatus {
995 > Streaming = 'streaming',
996 > PendingConfirmation = 'pending-confirmation',
997 > Running = 'running',
998 > /**
999 > * Running paused because the MCP server backing this call needs
1000 > * authentication (typically step-up auth for insufficient scope,
1001 > * surfacing mid-execution). See {@link ToolCallAuthRequiredState}.
1002 > */
1003 > AuthRequired = 'auth-required',
1004 > PendingResultConfirmation = 'pending-result-confirmation',
1005 > Completed = 'completed',
1006 > Cancelled = 'cancelled',
1007 > }
1008 >
1009 > /**
1010 > * How a tool call was confirmed for execution.
1011 > *
1012 > * - `NotNeeded` — No confirmation required (auto-approved)
1013 > * - `UserAction` — User explicitly approved
1014 > * - `Setting` — Approved by a persistent user setting
1015 > *
1016 > * @category Tool Call Types
1017 > */
1018 > export const enum ToolCallConfirmationReason {
1019 > NotNeeded = 'not-needed',
1020 > UserAction = 'user-action',
1021 > Setting = 'setting',
1022 > }
1023 >
1024 > /**
1025 > * Identifies a model judge as the source of a confirmation requirement.
1026 > *
1027 > * @category Tool Call Types
1028 > */
1029 > export const enum ToolCallRiskAssessmentKind {
1030 > Judge = 'judge',
1031 > }
1032 >
1033 > /**
1034 > * Lifecycle status of an asynchronous model-judge confirmation decision.
1035 > *
1036 > * @category Tool Call Types
1037 > */
1038 > export const enum ToolCallRiskAssessmentStatus {
1039 > Loading = 'loading',
1040 > Complete = 'complete',
1041 > }
1042 >
1043 > interface ToolCallRiskAssessmentBase {
1044 > kind: ToolCallRiskAssessmentKind;
1045 > }
1046 >
1047 > /**
1048 > * The model judge is still evaluating the tool call.
1049 > *
1050 > * @category Tool Call Types
1051 > */
1052 > export interface ToolCallRiskAssessmentLoadingState extends ToolCallRiskAssessmentBase {
1053 > status: ToolCallRiskAssessmentStatus.Loading;
1054 > }
1055 >
1056 > /**
1057 > * The model judge has completed its evaluation.
1058 > *
1059 > * @category Tool Call Types
1060 > */
1061 > export interface ToolCallRiskAssessmentCompleteState extends ToolCallRiskAssessmentBase {
1062 > status: ToolCallRiskAssessmentStatus.Complete;
1063 > reason: StringOrMarkdown;
1064 > /**
1065 > * The judge's normalized safety score, where `0` is unsafe and `1` is safe.
1066 > * @format float
1067 > */
1068 > safety: number;
1069 > }
1070 >
1071 > export type ToolCallRiskAssessment =
1072 > | ToolCallRiskAssessmentLoadingState
1073 > | ToolCallRiskAssessmentCompleteState;
1074 >
1075 > /**
1076 > * Why a tool call was cancelled.
1077 > *
1078 > * @category Tool Call Types
1079 > */
1080 > export const enum ToolCallCancellationReason {
1081 > Denied = 'denied',
1082 > Skipped = 'skipped',
1083 > ResultDenied = 'result-denied',
1084 > }
1085 >
1086 > /**
1087 > * Whether a confirmation option represents an approval or denial action.
1088 > *
1089 > * @category Tool Call Types
1090 > */
1091 > export const enum ConfirmationOptionKind {
1092 > Approve = 'approve',
1093 > Deny = 'deny',
1094 > }
1095 >
1096 > /**
1097 > * A confirmation option that the server offers for a tool call awaiting
1098 > * approval. Allows richer choices beyond simple approve/deny — for example,
1099 > * "Approve in this Session" or "Deny with reason."
1100 > *
1101 > * @category Tool Call Types
1102 > */
1103 > export interface ConfirmationOption {
1104 > /** Unique identifier for the option, returned in the confirmed action */
1105 > id: string;
1106 > /** Human-readable label displayed to the user */
1107 > label: string;
1108 > /** Whether this option represents an approval or denial */
1109 > kind: ConfirmationOptionKind;
1110 > /**
1111 > * Logical group number for visual categorisation.
1112 > *
1113 > * Clients SHOULD display options in the order they are defined and MAY
1114 > * use differing group numbers to insert dividers between logical clusters
1115 > * of options.
1116 > */
1117 > group?: number;
1118 > }
1119 >
1120 > export const enum ToolCallContributorKind {
1121 > Client = 'client',
1122 > MCP = 'mcp',
1123 > }
1124 >
1125 > export interface ToolCallClientContributor {
1126 > kind: ToolCallContributorKind.Client;
1127 > /**
1128 > * If this tool is provided by a client, the `clientId` of the owning client.
1129 > * Absent for server-side tools.
1130 > *
1131 > * When set, the identified client is responsible for executing the tool and
1132 > * dispatching `chat/toolCallComplete` with the result.
1133 > */
1134 > clientId: string;
1135 > }
1136 >
1137 > export interface ToolCallMcpContributor {
1138 > kind: ToolCallContributorKind.MCP;
1139 > /**
1140 > * Customization ID of the corresponding MCP server in {@link SessionState.customizations}.
1141 > */
1142 > customizationId: string;
1143 > }
1144 >
1145 > export type ToolCallContributor = ToolCallClientContributor | ToolCallMcpContributor;
1146 >
1147 > /**
1148 > * Metadata common to all tool call states.
1149 > *
1150 > * @category Tool Call Types
1151 > * @remarks
1152 > * Fields like `toolName` carry agent-specific identifiers on the wire despite the
1153 > * agent-agnostic design principle. These exist for debugging and logging purposes.
1154 > * A future version may move these to a separate diagnostic channel or namespace them
1155 > * more clearly.
1156 > */
1157 > interface ToolCallBase {
1158 > /** Unique tool call identifier */
1159 > toolCallId: string;
1160 > /** Internal tool name (for debugging/logging) */
1161 > toolName: string;
1162 > /** Human-readable tool name */
1163 > displayName: string;
1164 > /** Human-readable description of what the tool invocation intends to do */
1165 > intention?: string;
1166 > /**
1167 > * Reference to the contributor of the tool being called.
1168 > */
1169 > contributor?: ToolCallContributor;
1170 > /**
1171 > * Additional provider-specific metadata for this tool call.
1172 > *
1173 > * This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)
1174 > * `McpUiToolMeta` found in MCP tool calls, which may be used in combination
1175 > * with the {@link contributor} to serve MCP Apps.
1176 > */
1177 > _meta?: Record<string, unknown>;
1178 > }
1179 >
1180 > /**
1181 > * Properties available once tool call parameters are fully received.
1182 > *
1183 > * @category Tool Call Types
1184 > */
1185 > interface ToolCallParameterFields {
1186 > /** Message describing what the tool will do */
1187 > invocationMessage: StringOrMarkdown;
1188 > /** Raw tool input */
1189 > toolInput?: string;
1190 > }
1191 >
1192 > /**
1193 > * Tool execution result details, available after execution completes.
1194 > *
1195 > * @category Tool Call Types
1196 > */
1197 > export interface ToolCallResult {
1198 > /** Whether the tool succeeded */
1199 > success: boolean;
1200 > /** Past-tense description of what the tool did */
1201 > pastTenseMessage: StringOrMarkdown;
1202 > /**
1203 > * Unstructured result content blocks.
1204 > *
1205 > * This mirrors the `content` field of MCP `CallToolResult`.
1206 > */
1207 > content?: ToolResultContent[];
1208 > /**
1209 > * Optional structured result object.
1210 > *
1211 > * This mirrors the `structuredContent` field of MCP `CallToolResult`.
1212 > */
1213 > structuredContent?: Record<string, unknown>;
1214 > /** Error details if the tool failed */
1215 > error?: { message: string; code?: string };
1216 > }
1217 >
1218 > /**
1219 > * LM is streaming the tool call parameters.
1220 > *
1221 > * @category Tool Call Types
1222 > */
1223 > export interface ToolCallStreamingState extends ToolCallBase {
1224 > status: ToolCallStatus.Streaming;
1225 > /** Partial parameters accumulated so far */
1226 > partialInput?: string;
1227 > /** Progress message shown while parameters are streaming */
1228 > invocationMessage?: StringOrMarkdown;
1229 > }
1230 >
1231 > /**
1232 > * Parameters are complete, or a running tool requires re-confirmation
1233 > * (e.g. a mid-execution permission check).
1234 > *
1235 > * @category Tool Call Types
1236 > */
1237 > export interface ToolCallPendingConfirmationState extends ToolCallBase, ToolCallParameterFields {
1238 > status: ToolCallStatus.PendingConfirmation;
1239 > /** Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) */
1240 > confirmationTitle?: StringOrMarkdown;
1241 > /** Risk assessment that informed the confirmation requirement. */
1242 > riskAssessment?: ToolCallRiskAssessment;
1243 > /** File edits that this tool call will perform, for preview before confirmation */
1244 > edits?: { items: FileEdit[] };
1245 > /** Whether the agent host allows the client to edit the tool's input parameters before confirming */
1246 > editable?: boolean;
1247 > /**
1248 > * Options the server offers for this confirmation. When present, the client
1249 > * SHOULD render these instead of a plain approve/deny UI. Each option
1250 > * belongs to a {@link ConfirmationOptionGroup} so the client can still
1251 > * categorise the choices.
1252 > */
1253 > options?: ConfirmationOption[];
1254 > }
1255 >
1256 > /**
1257 > * Fields present on every tool call state that exists **after** confirmation
1258 > * has been resolved: {@link ToolCallRunningState}, {@link ToolCallAuthRequiredState},
1259 > * {@link ToolCallPendingResultConfirmationState}, and {@link ToolCallCompletedState}.
1260 > * `ToolCallPendingConfirmationState` (not yet confirmed) and
1261 > * `ToolCallCancelledState` (the denial path — never ran) don't satisfy this
1262 > * invariant, so they keep their own `selectedOption` field independently
1263 > * rather than extending this one.
1264 > *
1265 > * @category Tool Call Types
1266 > */
1267 > interface ToolCallPostConfirmationFields {
1268 > /** How the tool was confirmed for execution */
1269 > confirmed: ToolCallConfirmationReason;
1270 > /** The confirmation option the user selected, if confirmation options were provided */
1271 > selectedOption?: ConfirmationOption;
1272 > }
1273 >
1274 > /**
1275 > * Tool is actively executing.
1276 > *
1277 > * @category Tool Call Types
1278 > */
1279 > export interface ToolCallRunningState extends ToolCallBase, ToolCallParameterFields, ToolCallPostConfirmationFields {
1280 > status: ToolCallStatus.Running;
1281 > /**
1282 > * Partial content produced while the tool is still executing.
1283 > *
1284 > * For example, a terminal content block lets clients subscribe to live
1285 > * output before the tool completes.
1286 > */
1287 > content?: ToolResultContent[];
1288 > }
1289 >
1290 > /**
1291 > * A running tool call is paused because the MCP server backing it needs
1292 > * authentication — most commonly {@link McpAuthRequirement.reason |
1293 > * `insufficientScope`} step-up auth triggered by the `tools/call` request
1294 > * itself. Only ever reached from {@link ToolCallRunningState}, and normally
1295 > * returns there once authenticated: `running` → `auth-required` → `running`
1296 > * → …. A client MAY instead cancel the invocation without authenticating by
1297 > * dispatching a `chat/toolCallComplete` with a **failed** result, always
1298 > * moving straight to {@link ToolCallCompletedState} —
1299 > * `requiresResultConfirmation` is ignored on this path, so it can never
1300 > * enter {@link ToolCallPendingResultConfirmationState}. A **successful**
1301 > * result dispatched from this state is invalid and MUST be rejected/ignored
1302 > * as a no-op by the reducer, since execution never resumed after the
1303 > * challenge.
1304 > *
1305 > * This is the tool-call-level counterpart to
1306 > * {@link McpServerAuthRequiredState} — that state means the MCP *server*
1307 > * cannot serve any request; this one means *this specific invocation* is
1308 > * waiting on the same kind of challenge. The two are dispatched
1309 > * independently and MAY be true at the same time, or not: an
1310 > * `insufficientScope` challenge triggered by a single tool call, for
1311 > * example, need not block the whole server.
1312 > *
1313 > * Because the challenge is always resolved by pushing a token via the
1314 > * existing `authenticate` command, this state can only originate from a
1315 > * tool call {@link ToolCallContributorKind.MCP | contributed by an MCP
1316 > * server} — `contributor` is narrowed accordingly (unlike the optional,
1317 > * multi-kind `contributor` on other tool call states).
1318 > *
1319 > * @category Tool Call Types
1320 > */
1321 > export interface ToolCallAuthRequiredState extends ToolCallBase, ToolCallParameterFields, ToolCallPostConfirmationFields {
1322 > status: ToolCallStatus.AuthRequired;
1323 > /** The MCP server that contributed this tool call — always MCP, never a client tool. */
1324 > contributor: ToolCallMcpContributor;
1325 > /** The authentication challenge blocking this invocation. */
1326 > auth: McpAuthRequirement;
1327 > /** Partial content produced before the call paused for authentication. */
1328 > content?: ToolResultContent[];
1329 > }
1330 >
1331 > /**
1332 > * Tool finished executing, waiting for client to approve the result.
1333 > *
1334 > * @category Tool Call Types
1335 > */
1336 > export interface ToolCallPendingResultConfirmationState extends ToolCallBase, ToolCallParameterFields, ToolCallResult, ToolCallPostConfirmationFields {
1337 > status: ToolCallStatus.PendingResultConfirmation;
1338 > }
1339 >
1340 > /**
1341 > * Tool completed successfully or with an error.
1342 > *
1343 > * @category Tool Call Types
1344 > */
1345 > export interface ToolCallCompletedState extends ToolCallBase, ToolCallParameterFields, ToolCallResult, ToolCallPostConfirmationFields {
1346 > status: ToolCallStatus.Completed;
1347 > }
1348 >
1349 > /**
1350 > * Tool call was cancelled before execution.
1351 > *
1352 > * @category Tool Call Types
1353 > */
1354 > export interface ToolCallCancelledState extends ToolCallBase, ToolCallParameterFields {
1355 > status: ToolCallStatus.Cancelled;
1356 > /** Why the tool was cancelled */
1357 > reason: ToolCallCancellationReason;
1358 > /** Optional message explaining the cancellation */
1359 > reasonMessage?: StringOrMarkdown;
1360 > /** What the user suggested doing instead */
1361 > userSuggestion?: Message;
1362 > /** The confirmation option the user selected, if confirmation options were provided */
1363 > selectedOption?: ConfirmationOption;
1364 > }
1365 >
1366 > /**
1367 > * Discriminated union of all tool call lifecycle states.
1368 > *
1369 > * See the [state model guide](/guide/state-model.html#tool-call-lifecycle)
1370 > * for the full state machine diagram.
1371 > *
1372 > * @category Tool Call Types
1373 > */
1374 > export type ToolCallState =
1375 > | ToolCallStreamingState
1376 > | ToolCallPendingConfirmationState
1377 > | ToolCallRunningState
1378 > | ToolCallAuthRequiredState
1379 > | ToolCallPendingResultConfirmationState
1380 > | ToolCallCompletedState
1381 > | ToolCallCancelledState;
1382 >
1383 > /**
1384 > * The two tool-call states that block on a client confirmation: parameter
1385 > * confirmation before execution ({@link ToolCallPendingConfirmationState}) and
1386 > * result confirmation after execution
1387 > * ({@link ToolCallPendingResultConfirmationState}).
1388 > *
1389 > * {@link ToolCallAuthRequiredState} is intentionally **not** part of this
1390 > * union: it doesn't block on a `chat/toolCallConfirmed`-style client
1391 > * decision, it blocks on the client completing an OAuth flow and calling
1392 > * `authenticate`. See {@link SessionToolAuthenticationRequest} for its
1393 > * session-level surfacing.
1394 > *
1395 > * Surfaced at the session level by {@link SessionToolConfirmationRequest}.
1396 > *
1397 > * @category Tool Call Types
1398 > */
1399 > export type ToolCallConfirmationState =
1400 > | ToolCallPendingConfirmationState
1401 > | ToolCallPendingResultConfirmationState;
1402 >
1403 >
1404 > // ─── Tool Result Content ─────────────────────────────────────────────────────
1405 >
1406 > /**
1407 > * Discriminant for tool result content types.
1408 > *
1409 > * @category Tool Result Content
1410 > */
1411 > export const enum ToolResultContentType {
1412 > Text = 'text',
1413 > EmbeddedResource = 'embeddedResource',
1414 > Resource = 'resource',
1415 > FileEdit = 'fileEdit',
1416 > Terminal = 'terminal',
1417 > Subagent = 'subagent',
1418 > }
1419 >
1420 > /**
1421 > * Text content in a tool result.
1422 > *
1423 > * Mirrors MCP `TextContent`.
1424 > *
1425 > * @category Tool Result Content
1426 > */
1427 > export interface ToolResultTextContent {
1428 > type: ToolResultContentType.Text;
1429 > /** The text content */
1430 > text: string;
1431 > }
1432 >
1433 > /**
1434 > * Base64-encoded binary content embedded in a tool result.
1435 > *
1436 > * Mirrors MCP `EmbeddedResource` for inline binary data.
1437 > *
1438 > * @category Tool Result Content
1439 > */
1440 > export interface ToolResultEmbeddedResourceContent {
1441 > type: ToolResultContentType.EmbeddedResource;
1442 > /** Base64-encoded data */
1443 > data: string;
1444 > /** Content type (e.g. `"image/png"`, `"application/pdf"`) */
1445 > contentType: string;
1446 > }
1447 >
1448 > /**
1449 > * A reference to a resource stored outside the tool result.
1450 > *
1451 > * Wraps {@link ContentRef} for lazy-loading large results.
1452 > *
1453 > * @category Tool Result Content
1454 > */
1455 > export interface ToolResultResourceContent extends ContentRef {
1456 > type: ToolResultContentType.Resource;
1457 > }
1458 >
1459 > /**
1460 > * Describes a file modification performed by a tool.
1461 > *
1462 > * @category Tool Result Content
1463 > */
1464 > export interface ToolResultFileEditContent extends FileEdit {
1465 > type: ToolResultContentType.FileEdit;
1466 > }
1467 >
1468 > /**
1469 > * A reference to a terminal whose output is relevant to this tool result.
1470 > *
1471 > * Clients can subscribe to the terminal's URI to stream its output in real
1472 > * time, providing live feedback while a tool is executing.
1473 > *
1474 > * When the command exits, {@link result} is filled in on the completed
1475 > * result, retaining the outcome for clients that did not subscribe. This
1476 > * records the command's exit, not the terminal's — the terminal may keep
1477 > * running afterwards.
1478 > *
1479 > * @category Tool Result Content
1480 > */
1481 > export interface ToolResultTerminalContent {
1482 > type: ToolResultContentType.Terminal;
1483 > /** Terminal URI (subscribable for full terminal state) */
1484 > resource: URI;
1485 > /** Display title for the terminal content */
1486 > title: string;
1487 > /**
1488 > * Whether this terminal-style resource is backed by a pseudoterminal.
1489 > * When `false`, output is plain text and clients do not need to parse
1490 > * VT sequences.
1491 > */
1492 > isPty?: boolean;
1493 > /** Outcome of the command, present once it has exited. */
1494 > result?: TerminalCommandResult;
1495 > }
1496 >
1497 > /**
1498 > * Outcome of a command run in a terminal-style tool, filled in on
1499 > * {@link ToolResultTerminalContent.result} once the command exits.
1500 > *
1501 > * @category Tool Result Content
1502 > */
1503 > export interface TerminalCommandResult {
1504 > /** Exit code from the completed command, if reported by the runtime */
1505 > exitCode?: number;
1506 > /**
1507 > * Preview of the command's output, for clients that are not subscribed
1508 > * to the terminal or that arrive after it is disposed. When `isPty` is
1509 > * `true` the preview may contain VT sequences; when `false` it is plain
1510 > * text.
1511 > */
1512 > preview?: string;
1513 > /** Whether `preview` is known to be incomplete or truncated */
1514 > truncated?: boolean;
1515 > }
1516 >
1517 > /**
1518 > * A reference, embedded in a tool result, to a worker chat spawned by the tool
1519 > * call (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).
1520 > *
1521 > * This is the spawning tool call's forward view of the worker. The worker chat
1522 > * records the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),
1523 > * whose `toolCallId` identifies the tool call that emitted this content.
1524 > *
1525 > * @category Tool Result Content
1526 > */
1527 > export interface ToolResultSubagentContent {
1528 > type: ToolResultContentType.Subagent;
1529 > /** Worker chat URI (subscribable for full chat state) */
1530 > resource: URI;
1531 > /** Display title for the subagent */
1532 > title: string;
1533 > /** Internal agent name */
1534 > agentName?: string;
1535 > /** Human-readable description of the subagent's task */
1536 > description?: string;
1537 > }
1538 >
1539 > /**
1540 > * Content block in a tool result.
1541 > *
1542 > * Mirrors the content blocks in MCP `CallToolResult.content`, plus
1543 > * `ToolResultResourceContent` for lazy-loading large results,
1544 > * `ToolResultFileEditContent` for file edit diffs,
1545 > * `ToolResultTerminalContent` for live terminal output and
1546 > * command completion metadata, and
1547 > * `ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions).
1548 > *
1549 > * @category Tool Result Content
1550 > */
1551 > export type ToolResultContent =
1552 > | ToolResultTextContent
1553 > | ToolResultEmbeddedResourceContent
1554 > | ToolResultResourceContent
1555 > | ToolResultFileEditContent
1556 > | ToolResultTerminalContent
1557 > | ToolResultSubagentContent;
src/vs/platform/agentHost/common/state/protocol/channels-session/state.ts 1351 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { Changeset } from '../channels-changeset/state.js';
10 > import type { AnnotationsSummary } from '../channels-annotations/state.js';
11 > import type { ChatSummary, ChatInputRequest, ToolCallConfirmationState, ToolCallState, ToolCallAuthRequiredState } from '../channels-chat/state.js';
12 > import type { ConfigPropertySchema, ErrorInfo, Icon, ProtectedResourceMetadata, TextRange, URI } from '../common/state.js';
13 >
14 > // ─── Session State ───────────────────────────────────────────────────────────
15 >
16 > /**
17 > * Session initialization state.
18 > *
19 > * @category Session State
20 > */
21 > export const enum SessionLifecycle {
22 > Creating = 'creating',
23 > Ready = 'ready',
24 > CreationFailed = 'creationFailed',
25 > }
26 >
27 > /**
28 > * Bitset of summary-level session status flags.
29 > *
30 > * Use bitwise checks instead of equality for non-terminal activity. For example,
31 > * `status & SessionStatus.InProgress` matches both ordinary in-progress turns
32 > * and turns that are paused waiting for input.
33 > *
34 > * @category Session State
35 > */
36 > export const enum SessionStatus {
37 > /** Session is idle — no turn is active. */
38 > Idle = 1,
39 > /** Session ended with an error. */
40 > Error = 1 << 1,
41 > /** A turn is actively streaming. */
42 > InProgress = 1 << 3,
43 > /** A turn is in progress but blocked waiting for user input or tool confirmation. */
44 > InputNeeded = (1 << 3) | (1 << 4),
45 > /** The client has viewed this session since its last modification. */
46 > IsRead = 1 << 5,
47 > /** The session has been archived by the client. */
48 > IsArchived = 1 << 6,
49 > }
50 >
51 > /**
52 > * Metadata shared between the full {@link SessionState} (delivered when a
53 > * client subscribes to a session's URI) and the lightweight
54 > * {@link SessionSummary} (carried in the root-channel session catalog).
55 > *
56 > * These fields describe the session at a glance and appear in both places.
57 > * `SessionState` owns the authoritative values for a subscribed session;
58 > * `SessionSummary` mirrors them into the catalog so clients that only render a
59 > * session list don't have to subscribe to every session URI. The host keeps
60 > * the catalog in sync via `root/sessionSummaryChanged`.
61 > *
62 > * @category Session State
63 > */
64 > export interface SessionMetadata {
65 > /** Agent provider ID */
66 > provider: string;
67 > /** Session title */
68 > title: string;
69 > /** Current session status */
70 > status: SessionStatus;
71 > /** Human-readable description of what the session is currently doing */
72 > activity?: string;
73 > /** Server-owned project for this session */
74 > project?: ProjectInfo;
75 > /**
76 > * The working directories the session's agent has tool access to, as
77 > * maintained by the `session/workingDirectorySet` /
78 > * `session/workingDirectoryRemoved` actions. Directories are **equal peers** —
79 > * the session has no primary. Individual chats MAY restrict to a subset via
80 > * {@link ChatSummary.workingDirectories | their own `workingDirectories`} and
81 > * designate one of their own directories as primary (see
82 > * {@link ChatState.primaryWorkingDirectory}); a chat that sets no subset
83 > * operates against this full set.
84 > */
85 > workingDirectories?: URI[];
86 > /**
87 > * Lightweight summary of this session's inline annotations channel
88 > * (`ahp-session:/<uuid>/annotations`). Surfaced so badge UI can render
89 > * annotation / entry counts without subscribing. Absent when the session
90 > * does not expose an annotations channel.
91 > */
92 > annotations?: AnnotationsSummary;
93 > }
94 >
95 > /**
96 > * Full state for a single session, loaded when a client subscribes to the session's URI.
97 > *
98 > * Inlines (denormalizes) every {@link SessionMetadata} field directly onto
99 > * itself so subscribers receive one flat object instead of a nested summary.
100 > * The lightweight catalog representation is {@link SessionSummary}, surfaced on
101 > * the root channel; the host keeps the two in sync via
102 > * `root/sessionSummaryChanged`.
103 > *
104 > * @category Session State
105 > */
106 > export interface SessionState extends SessionMetadata {
107 > /** Session initialization state */
108 > lifecycle: SessionLifecycle;
109 > /** Error details if creation failed */
110 > creationError?: ErrorInfo;
111 > /** Tools provided by the server (agent host) for this session */
112 > serverTools?: ToolDefinition[];
113 > /**
114 > * The clients currently providing tools and interactive capabilities to this
115 > * session. If multiple tools or customizations are provided by the same
116 > * active client, an agent host MAY deduplicate them when exposed to a model,
117 > * with a preference given to the client that started the turn.
118 > *
119 > * Membership is host-managed: clients add (or refresh) themselves with
120 > * `session/activeClientSet`, and the host removes them with
121 > * `session/activeClientRemoved` when they unsubscribe, disconnect without
122 > * reconnecting in time, or reconnect without resubscribing to the session.
123 > */
124 > activeClients: SessionActiveClient[];
125 > /** Catalog of chats in this session. */
126 > chats: ChatSummary[];
127 > /**
128 > * The chat that receives input when the user addresses the session without
129 > * selecting a specific chat. This is a UI routing hint, not a hierarchy
130 > * marker — chats remain equal peers at the protocol level. Hosts MAY change
131 > * this over the session's lifetime.
132 > */
133 > defaultChat?: URI;
134 > /** Session configuration schema and current values */
135 > config?: SessionConfigState;
136 > /**
137 > * Top-level customizations active in this session.
138 > *
139 > * Always one of the {@link Customization} variants:
140 > *
141 > * - Container customizations ({@link PluginCustomization},
142 > * {@link DirectoryCustomization}) whose children — agents, skills,
143 > * prompts, rules, hooks, MCP servers — live in each container's
144 > * {@link ContainerCustomizationBase.children | `children`} array.
145 > * - Top-level {@link McpServerCustomization} entries the host
146 > * surfaces directly (for example a globally-configured MCP server
147 > * that isn't bundled in a plugin or directory). MCP servers may
148 > * also appear as children of a container.
149 > *
150 > * Client-published plugins arrive via
151 > * {@link SessionActiveClient.customizations | `activeClients[].customizations`}
152 > * and the host propagates them into this list (typically with the
153 > * container's `clientId` set and `children` populated). Clients
154 > * publish in container shape only; bare MCP servers at the top level
155 > * are server-originated.
156 > */
157 > customizations?: Customization[];
158 > /**
159 > * Catalogue of changesets the server can produce for this session. Each
160 > * entry advertises a subscribable view of file changes (uncommitted,
161 > * session-wide, per-turn, etc.) and the URI template the client expands
162 > * before subscribing. See {@link Changeset} for the full shape and
163 > * {@link /guide/changesets | Changesets} for an overview of the model.
164 > */
165 > changesets?: Changeset[];
166 > /**
167 > * Outstanding input the session is blocked on, aggregated across every chat
168 > * so a client can discover and answer it from the session channel alone,
169 > * without subscribing to individual chats.
170 > *
171 > * Each entry is self-sufficient: it carries the owning chat's URI plus every
172 > * identifier the client needs to respond. A client answers by dispatching the
173 > * ordinary `chat/*` action to that chat's channel — see
174 > * {@link SessionInputRequest} for the per-variant response path. A present,
175 > * non-empty list implies {@link SessionStatus.InputNeeded} on
176 > * {@link SessionSummary.status}.
177 > *
178 > * Host-managed: the host upserts entries with `session/inputNeededSet` as
179 > * chats raise requests and removes them with `session/inputNeededRemoved`
180 > * once the underlying request resolves.
181 > */
182 > inputNeeded?: SessionInputRequest[];
183 > /**
184 > * Additional provider-specific metadata for this session.
185 > *
186 > * Clients MAY look for well-known keys here to provide enhanced UI.
187 > * For example, a `git` key may provide extra git metadata about the session's
188 > * working directories.
189 > */
190 > _meta?: Record<string, unknown>;
191 > }
192 >
193 > /**
194 > * A client currently providing tools and interactive capabilities to a session.
195 > *
196 > * A session MAY have several active clients at once; entries in
197 > * {@link SessionState.activeClients} are keyed by `clientId`. The server SHOULD
198 > * automatically remove an active client when that client disconnects.
199 > *
200 > * @category Session State
201 > */
202 > export interface SessionActiveClient {
203 > /** Client identifier (matches `clientId` from `initialize`) */
204 > clientId: string;
205 > /** Human-readable client name (e.g. `"VS Code"`) */
206 > displayName?: string;
207 > /** Tools this client provides to the session */
208 > tools: ToolDefinition[];
209 > /**
210 > * Plugin customizations this client contributes to the session.
211 > *
212 > * Clients publish in [Open Plugins](https://open-plugins.com/) format
213 > * — i.e. always container-shaped plugins. They MAY synthesize virtual
214 > * plugins in memory and rely on the host to expand them into concrete
215 > * children inside {@link SessionState.customizations}.
216 > */
217 > customizations?: ClientPluginCustomization[];
218 > }
219 >
220 > // ─── Session Input Requests ──────────────────────────────────────────────────
221 >
222 > /**
223 > * Discriminant for the kinds of outstanding input a session can surface in
224 > * {@link SessionState.inputNeeded}.
225 > *
226 > * This is a general/typological union (not a lifecycle), so the discriminant is
227 > * a `*Kind`.
228 > *
229 > * @category Session Input Types
230 > */
231 > export const enum SessionInputRequestKind {
232 > /** A user-facing elicitation mirrored from an unresolved chat response part. */
233 > ChatInput = 'chatInput',
234 > /** A tool call awaiting parameter- or result-confirmation. */
235 > ToolConfirmation = 'toolConfirmation',
236 > /** A running tool the session wants an active client to execute. */
237 > ToolClientExecution = 'toolClientExecution',
238 > /** A tool call blocked on MCP authentication mid-execution. */
239 > ToolAuthentication = 'toolAuthentication',
240 > }
241 >
242 > /**
243 > * Fields common to every {@link SessionInputRequest} variant.
244 > *
245 > * @category Session Input Types
246 > */
247 > interface SessionInputRequestBase {
248 > /**
249 > * Stable key for this entry, unique within the session's
250 > * {@link SessionState.inputNeeded} list. The host derives it however it likes
251 > * (for example from the chat URI plus the underlying request or tool-call
252 > * id); consumers MUST treat it as opaque. It is the key for the
253 > * `session/inputNeededSet` / `session/inputNeededRemoved` upsert convention.
254 > */
255 > id: string;
256 > /**
257 > * The chat the underlying request lives in. This is the channel a client
258 > * dispatches its response to — it does not need to have subscribed to that
259 > * chat first.
260 > */
261 > chat: URI;
262 > }
263 >
264 > /**
265 > * A user-input elicitation surfaced at the session level, mirroring the request
266 > * from an unresolved {@link InputRequestResponsePart} in the owning chat.
267 > *
268 > * Respond by dispatching `chat/inputCompleted` (or syncing drafts with
269 > * `chat/inputAnswerChanged`) to {@link SessionInputRequestBase.chat | `chat`},
270 > * keyed by {@link ChatInputRequest.id | `request.id`}.
271 > *
272 > * @category Session Input Types
273 > */
274 > export interface SessionChatInputRequest extends SessionInputRequestBase {
275 > kind: SessionInputRequestKind.ChatInput;
276 > /** The mirrored chat input request. */
277 > request: ChatInputRequest;
278 > }
279 >
280 > /**
281 > * A tool call blocked on confirmation — either parameter confirmation before
282 > * execution or result confirmation after — surfaced at the session level.
283 > *
284 > * Respond by dispatching `chat/toolCallConfirmed` (for
285 > * {@link ToolCallPendingConfirmationState}) or `chat/toolCallResultConfirmed`
286 > * (for {@link ToolCallPendingResultConfirmationState}) to
287 > * {@link SessionInputRequestBase.chat | `chat`}, keyed by `turnId` and
288 > * `toolCall.toolCallId`.
289 > *
290 > * @category Session Input Types
291 > */
292 > export interface SessionToolConfirmationRequest extends SessionInputRequestBase {
293 > kind: SessionInputRequestKind.ToolConfirmation;
294 > /** The turn the tool call belongs to. */
295 > turnId: string;
296 > /** The tool call awaiting confirmation. */
297 > toolCall: ToolCallConfirmationState;
298 > }
299 >
300 > /**
301 > * A running tool whose execution is delegated to an active client. Surfaced so
302 > * a client that provides the tool can pick up the work without subscribing to
303 > * the owning chat.
304 > *
305 > * The {@link toolCall} is always a {@link ToolCallRunningState} (a
306 > * {@link ToolCallState} in `running` status) whose
307 > * {@link ToolCallRunningState.contributor | `contributor`} is a client
308 > * {@link ToolCallClientContributor} whose `clientId` matches the denormalized
309 > * {@link clientId} here. Execute and report the result by dispatching
310 > * `chat/toolCallComplete` (and optionally streaming with
311 > * `chat/toolCallContentChanged`) to {@link SessionInputRequestBase.chat |
312 > * `chat`}, keyed by `turnId` and `toolCall.toolCallId`.
313 > *
314 > * @category Session Input Types
315 > */
316 > export interface SessionToolClientExecutionRequest extends SessionInputRequestBase {
317 > kind: SessionInputRequestKind.ToolClientExecution;
318 > /** The turn the tool call belongs to. */
319 > turnId: string;
320 > /**
321 > * The `clientId` expected to execute the tool. Matches the `clientId` of the
322 > * tool call's client {@link ToolCallContributor}.
323 > */
324 > clientId: string;
325 > /**
326 > * The running tool call the session wants the owning client to execute. The
327 > * host only ever populates this with a {@link ToolCallRunningState} (i.e. a
328 > * {@link ToolCallState} in `running` status).
329 > */
330 > toolCall: ToolCallState;
331 > }
332 >
333 > /**
334 > * A tool call blocked on MCP authentication mid-execution, surfaced at the
335 > * session level.
336 > *
337 > * The {@link toolCall} is always a {@link ToolCallAuthRequiredState} (a
338 > * {@link ToolCallState} in `auth-required` status). Unlike
339 > * {@link SessionToolConfirmationRequest}, this is **not** answered by
340 > * dispatching a `chat/*` action directly: the client obtains a token for
341 > * {@link ToolCallAuthRequiredState.auth | `toolCall.auth`}`.resource` and
342 > * pushes it via the existing `authenticate` command (see
343 > * {@link /specification/authentication | Authentication}). The host resumes
344 > * the tool call and dispatches `chat/toolCallAuthResolved` once the token is
345 > * accepted, at which point it also removes this entry with
346 > * `session/inputNeededRemoved`.
347 > *
348 > * @category Session Input Types
349 > */
350 > export interface SessionToolAuthenticationRequest extends SessionInputRequestBase {
351 > kind: SessionInputRequestKind.ToolAuthentication;
352 > /** The turn the tool call belongs to. */
353 > turnId: string;
354 > /** The tool call awaiting authentication. */
355 > toolCall: ToolCallAuthRequiredState;
356 > }
357 >
358 > /**
359 > * One outstanding piece of input a session is blocked on, aggregated across all
360 > * chats in {@link SessionState.inputNeeded}.
361 > *
362 > * Each entry is self-sufficient: it carries the owning
363 > * {@link SessionInputRequestBase.chat | `chat`} URI plus every identifier needed
364 > * to construct the response, so a client can answer by dispatching the ordinary
365 > * `chat/*` action (`chat/inputCompleted`, `chat/toolCallConfirmed`,
366 > * `chat/toolCallComplete`, …) to that chat's channel **without having subscribed
367 > * to the chat** — except {@link SessionToolAuthenticationRequest}, which is
368 > * resolved via the `authenticate` command instead. The host removes the entry
369 > * with `session/inputNeededRemoved` once the underlying request resolves.
370 > *
371 > * @category Session Input Types
372 > */
373 > export type SessionInputRequest =
374 > | SessionChatInputRequest
375 > | SessionToolConfirmationRequest
376 > | SessionToolClientExecutionRequest
377 > | SessionToolAuthenticationRequest;
378 >
379 > /**
380 > * Server-owned project metadata for a session.
381 > *
382 > * @category Session State
383 > */
384 > export interface ProjectInfo {
385 > /** Project URI */
386 > uri: URI;
387 > /** Human-readable project name */
388 > displayName: string;
389 > }
390 >
391 > /**
392 > * Lightweight catalog entry summarizing one session. Surfaced via
393 > * {@link RootChannelCommands.listSessions | `root/listSessions`} and
394 > * `root/sessionAdded`/`root/sessionSummaryChanged` notifications.
395 > *
396 > * **Aggregation across chats.** Once a session contains more than one chat,
397 > * several `SessionSummary` fields are derived from the underlying
398 > * {@link SessionState.chats | chat catalog}. Producers SHOULD follow these
399 > * rules so clients that only consume the session summary (e.g. a session
400 > * list) still see meaningful state:
401 > *
402 > * - `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /
403 > * `Error` — bits 0–4) from the
404 > * {@link SessionState.defaultChat | default chat} when present, else from
405 > * the most recently modified chat. **Promote** `InputNeeded` whenever any
406 > * chat in the session needs input, and **promote** `Error` whenever any
407 > * chat is in an error state — both override the default-chat bits. The
408 > * orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.
409 > * - `activity`: mirror the activity string of the default chat, or of the
410 > * chat currently driving the promoted status bits when a non-default chat
411 > * wins (e.g. the chat that raised `InputNeeded`).
412 > * - `modifiedAt`: the max of all chats' `modifiedAt`.
413 > * - `workingDirectories`: the session-level set. Individual chats MAY restrict
414 > * to a subset via {@link ChatSummary.workingDirectories}; aggregating these
415 > * up is meaningless and SHOULD NOT be attempted.
416 > * - `changes`: optional roll-up across all chats. Producers MAY sum the
417 > * per-chat changeset stats or report the most expensive chat's stats —
418 > * whichever is cheaper for the host to compute.
419 > *
420 > * Sessions with a single chat trivially satisfy all of the above (the chat's
421 > * values pass through unchanged). The rules only matter once a session
422 > * carries multiple chats.
423 > *
424 > * @category Session State
425 > */
426 > export interface SessionSummary extends SessionMetadata {
427 > /** Session URI */
428 > resource: URI;
429 > /** Creation timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */
430 > createdAt: string;
431 > /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */
432 > modifiedAt: string;
433 > /**
434 > * Aggregate summary of file changes associated with this session. Servers
435 > * may populate this to give clients a quick at-a-glance view of the
436 > * session's footprint (e.g., for list rendering) without requiring the
437 > * client to subscribe to a changeset.
438 > */
439 > changes?: ChangesSummary;
440 > /**
441 > * Lightweight server-defined metadata clients may use for the session
442 > * presentation. The protocol does not interpret these values; producers
443 > * SHOULD keep the payload small because summaries appear in session lists
444 > * and session notifications.
445 > */
446 > _meta?: Record<string, unknown>;
447 > }
448 >
449 > /**
450 > * Aggregate counts describing the file changes associated with a session.
451 > *
452 > * All fields are optional so servers can populate only the metrics they
453 > * cheaply have available.
454 > *
455 > * @category Session State
456 > */
457 > export interface ChangesSummary {
458 > /** Total number of inserted lines across all changed files. */
459 > additions?: number;
460 > /** Total number of deleted lines across all changed files. */
461 > deletions?: number;
462 > /** Number of files that have changes. */
463 > files?: number;
464 > }
465 >
466 > // ─── Agent Selection ─────────────────────────────────────────────────────────
467 >
468 > /**
469 > * A selected custom agent for a session.
470 > *
471 > * The `uri` identifies a specific custom agent (matching an
472 > * {@link AgentCustomization.uri | `AgentCustomization.uri`} exposed via
473 > * the session's effective customizations). Consumers resolve the agent's
474 > * display name by looking up `uri` in the session's customization tree.
475 > *
476 > * A message with no `agent` selected uses the provider's default behavior.
477 > *
478 > * @category Session State
479 > */
480 > export interface AgentSelection {
481 > /** Stable agent URI (matches an {@link AgentCustomization.uri}). */
482 > uri: URI;
483 > }
484 >
485 > // ─── Session Config Types ────────────────────────────────────────────────────
486 >
487 > /**
488 > * A session configuration property descriptor.
489 > *
490 > * Extends the generic {@link ConfigPropertySchema} with session-specific
491 > * display extensions.
492 > *
493 > * @category Session Config Types
494 > */
495 > export interface SessionConfigPropertySchema extends ConfigPropertySchema {
496 > /**
497 > * Display extension: when `true`, the full set of allowed values is too large
498 > * to enumerate statically. The client SHOULD use `sessionConfigCompletions`
499 > * to fetch matching values based on user input. Any values in `enum` are
500 > * seed/recent values for initial display.
501 > */
502 > enumDynamic?: boolean;
503 > /** When `true`, the user may change this property after session creation */
504 > sessionMutable?: boolean;
505 > }
506 >
507 > /**
508 > * A JSON Schema object describing available session configuration metadata.
509 > *
510 > * @category Session Config Types
511 > */
512 > export interface SessionConfigSchema {
513 > /** JSON Schema: always `'object'` */
514 > type: 'object';
515 > /** JSON Schema: property descriptors keyed by property id */
516 > properties: Record<string, SessionConfigPropertySchema>;
517 > /** JSON Schema: list of required property ids */
518 > required?: string[];
519 > }
520 >
521 > /**
522 > * Live session configuration metadata.
523 > *
524 > * The schema describes the available configuration properties and the values
525 > * contain the current value for each resolved property.
526 > *
527 > * @category Session Config Types
528 > */
529 > export interface SessionConfigState {
530 > /** JSON Schema describing available configuration properties */
531 > schema: SessionConfigSchema;
532 > /** Current configuration values */
533 > values: Record<string, unknown>;
534 > }
535 >
536 > // ─── Tool Definition Types ───────────────────────────────────────────────────
537 >
538 > /**
539 > * Describes a tool available in a session, provided by either the server or the active client.
540 > *
541 > * @category Tool Definition Types
542 > */
543 > export interface ToolDefinition {
544 > /** Unique tool identifier */
545 > name: string;
546 > /** Human-readable display name */
547 > title?: string;
548 > /** Description of what the tool does */
549 > description?: string;
550 > /**
551 > * JSON Schema defining the expected input parameters.
552 > *
553 > * Optional because client-provided tools may not have formal schemas.
554 > * Mirrors MCP `Tool.inputSchema`.
555 > */
556 > inputSchema?: {
557 > type: 'object';
558 > properties?: Record<string, object>;
559 > required?: string[];
560 > };
561 > /**
562 > * JSON Schema defining the structure of the tool's output.
563 > *
564 > * Mirrors MCP `Tool.outputSchema`.
565 > */
566 > outputSchema?: {
567 > type: 'object';
568 > properties?: Record<string, object>;
569 > required?: string[];
570 > };
571 > /** Behavioral hints about the tool. All properties are advisory. */
572 > annotations?: ToolAnnotations;
573 > /**
574 > * Additional provider-specific metadata.
575 > *
576 > * Mirrors the MCP `_meta` convention.
577 > */
578 > _meta?: Record<string, unknown>;
579 > }
580 >
581 > /**
582 > * Behavioral hints about a tool. All properties are advisory and not
583 > * guaranteed to faithfully describe tool behavior.
584 > *
585 > * Mirrors MCP `ToolAnnotations` from the Model Context Protocol specification.
586 > *
587 > * @category Tool Definition Types
588 > */
589 > export interface ToolAnnotations {
590 > /** Alternate human-readable title */
591 > title?: string;
592 > /** Tool does not modify its environment (default: false) */
593 > readOnlyHint?: boolean;
594 > /** Tool may perform destructive updates (default: true) */
595 > destructiveHint?: boolean;
596 > /** Repeated calls with the same arguments have no additional effect (default: false) */
597 > idempotentHint?: boolean;
598 > /** Tool may interact with external entities (default: true) */
599 > openWorldHint?: boolean;
600 > }
601 >
602 > // ─── Customization Types ─────────────────────────────────────────────────────
603 >
604 > /**
605 > * Discriminant for the kind of customization.
606 > *
607 > * Top-level entries in {@link SessionState.customizations} and
608 > * {@link AgentInfo.customizations} are either container customizations
609 > * ({@link CustomizationType.Plugin | `Plugin`} or
610 > * {@link CustomizationType.Directory | `Directory`}) or
611 > * {@link CustomizationType.McpServer | `McpServer`} entries surfaced
612 > * directly by the host. The remaining types appear only as children of
613 > * a container.
614 > *
615 > * @category Customization Types
616 > */
617 > export const enum CustomizationType {
618 > Plugin = 'plugin',
619 > Directory = 'directory',
620 > Agent = 'agent',
621 > Skill = 'skill',
622 > Prompt = 'prompt',
623 > Rule = 'rule',
624 > Hook = 'hook',
625 > McpServer = 'mcpServer',
626 > }
627 >
628 > /**
629 > * Customization types that appear as children of a
630 > * {@link PluginCustomization} or {@link DirectoryCustomization}.
631 > *
632 > * @category Customization Types
633 > */
634 > export type ChildCustomizationType =
635 > | CustomizationType.Agent
636 > | CustomizationType.Skill
637 > | CustomizationType.Prompt
638 > | CustomizationType.Rule
639 > | CustomizationType.Hook
640 > | CustomizationType.McpServer;
641 >
642 > /**
643 > * Fields shared by every customization variant.
644 > *
645 > * @category Customization Types
646 > */
647 > interface CustomizationBase {
648 > /**
649 > * Session-unique opaque identifier. Used by every action that targets a
650 > * specific customization. Minted by whoever publishes the customization
651 > * (typically the agent host).
652 > */
653 > id: string;
654 > /**
655 > * Source URI for this customization. A plugin URL, a file URI, or a
656 > * directory URI.
657 > *
658 > * For declarations that live inside a larger file — e.g. an MCP
659 > * server declared inline in a `plugins.json` manifest — `uri` points
660 > * to the containing file and {@link CustomizationBase.range | `range`}
661 > * narrows it to the declaration's span.
662 > */
663 > uri: URI;
664 > /** Human-readable name. */
665 > name: string;
666 > /** Icons for UI display. */
667 > icons?: Icon[];
668 > /**
669 > * Optional span within {@link CustomizationBase.uri | `uri`} when this
670 > * customization is a subset of a larger file (for example, one entry
671 > * in an inline `mcpServers` block of a `plugins.json` manifest).
672 > * Absent when the customization covers the whole resource.
673 > */
674 > range?: TextRange;
675 > /**
676 > * Additional provider-specific metadata for this customization.
677 > *
678 > * Mirrors the MCP `_meta` convention. Optional and opaque to the
679 > * protocol; producers and consumers agree on its contents
680 > * out-of-band.
681 > */
682 > _meta?: Record<string, unknown>;
683 > }
684 >
685 > /**
686 > * Discriminant values for {@link CustomizationLoadState}.
687 > *
688 > * @category Customization Types
689 > */
690 > export const enum CustomizationLoadStatus {
691 > Loading = 'loading',
692 > Loaded = 'loaded',
693 > Degraded = 'degraded',
694 > Error = 'error',
695 > }
696 >
697 > /**
698 > * Container is being loaded by the host.
699 > *
700 > * @category Customization Types
701 > */
702 > export interface CustomizationLoadingState {
703 > kind: CustomizationLoadStatus.Loading;
704 > }
705 >
706 > /**
707 > * Container loaded successfully.
708 > *
709 > * @category Customization Types
710 > */
711 > export interface CustomizationLoadedState {
712 > kind: CustomizationLoadStatus.Loaded;
713 > }
714 >
715 > /**
716 > * Container partially loaded but has warnings.
717 > *
718 > * @category Customization Types
719 > */
720 > export interface CustomizationDegradedState {
721 > kind: CustomizationLoadStatus.Degraded;
722 > /** Human-readable description of the warning. */
723 > message: string;
724 > }
725 >
726 > /**
727 > * Container failed to load.
728 > *
729 > * @category Customization Types
730 > */
731 > export interface CustomizationErrorState {
732 > kind: CustomizationLoadStatus.Error;
733 > /** Human-readable error message. */
734 > message: string;
735 > }
736 >
737 > /**
738 > * Discriminated load state for a container customization
739 > * ({@link PluginCustomization} or {@link DirectoryCustomization}).
740 > *
741 > * @category Customization Types
742 > */
743 > export type CustomizationLoadState =
744 > | CustomizationLoadingState
745 > | CustomizationLoadedState
746 > | CustomizationDegradedState
747 > | CustomizationErrorState;
748 >
749 > /**
750 > * Fields shared by container customizations.
751 > *
752 > * @category Customization Types
753 > */
754 > interface ContainerCustomizationBase extends CustomizationBase {
755 > /** Whether this container is currently enabled. */
756 > enabled: boolean;
757 > /**
758 > * `clientId` of the client that contributed this container. Absent for
759 > * server-originated entries.
760 > */
761 > clientId?: string;
762 > /**
763 > * Host-reported load state. Absent means the host has not yet reported
764 > * a load state for this container.
765 > */
766 > load?: CustomizationLoadState;
767 > /**
768 > * Children discovered inside this container.
769 > *
770 > * Absent means the host has not parsed this container yet. An empty
771 > * array means the host parsed the container and it contributes
772 > * nothing.
773 > */
774 > children?: ChildCustomization[];
775 > }
776 >
777 > /**
778 > * An [Open Plugins](https://open-plugins.com/) plugin.
779 > *
780 > * @category Customization Types
781 > */
782 > export interface PluginCustomization extends ContainerCustomizationBase {
783 > type: CustomizationType.Plugin;
784 > /**
785 > * Version of the plugin, sourced from the
786 > * [Open Plugins](https://open-plugins.com/) manifest's optional
787 > * `version` field (semver, e.g. `"1.2.0"`). Absent when the manifest
788 > * declares no version — the field is optional there — or the source
789 > * has no version concept. Provenance / display only: the host neither
790 > * parses nor enforces it.
791 > */
792 > version?: string;
793 > }
794 >
795 > /**
796 > * A {@link PluginCustomization} as published by a client. Extends the
797 > * server-facing shape with an opaque `nonce` so the host can detect when
798 > * the client's view of a plugin has changed and re-parse only as needed.
799 > *
800 > * Clients SHOULD include a `nonce`. Server-side fields like
801 > * {@link ContainerCustomizationBase.children | `children`} and
802 > * {@link ContainerCustomizationBase.load | `load`} are typically left
803 > * absent on publication and populated by the host when the resolved
804 > * plugin appears in {@link SessionState.customizations}.
805 > *
806 > * @category Customization Types
807 > */
808 > export interface ClientPluginCustomization extends PluginCustomization {
809 > /** Opaque version token used by the host to detect changes. */
810 > nonce?: string;
811 > }
812 >
813 > /**
814 > * A directory the host watches for this session.
815 > *
816 > * Presence in the customization list signals that the host may discover
817 > * customizations from this directory. When `writable` is `true`, clients
818 > * MAY persist new customizations into the directory using
819 > * [`resourceWrite`](/reference/common#resourcewrite); the host will
820 > * then surface the resulting child via the customization actions.
821 > *
822 > * The directory may not yet exist on disk.
823 > *
824 > * @category Customization Types
825 > */
826 > export interface DirectoryCustomization extends ContainerCustomizationBase {
827 > type: CustomizationType.Directory;
828 > /** Which child customization type this directory holds. */
829 > contents: ChildCustomizationType;
830 > /** Whether clients may write into this directory. */
831 > writable: boolean;
832 > }
833 >
834 > /**
835 > * Fields shared by the leaf child customizations that live inside a
836 > * container — {@link AgentCustomization}, {@link SkillCustomization},
837 > * {@link PromptCustomization}, {@link RuleCustomization}, and
838 > * {@link HookCustomization}.
839 > *
840 > * {@link McpServerCustomization} is also a child but does not extend this
841 > * base: it always carries an explicit {@link McpServerCustomization.enabled}
842 > * because it can appear as a top-level customization too.
843 > *
844 > * @category Customization Types
845 > */
846 > interface ChildCustomizationBase extends CustomizationBase {
847 > /**
848 > * Whether this child is individually enabled. Absent means enabled, so a
849 > * producer only needs to set it to surface a child that exists but is
850 > * turned off on its own.
851 > *
852 > * This flag is independent of the parent container's: the **effective**
853 > * enabled state of a child is
854 > * `container.enabled && (child.enabled ?? true)`, so a disabled container
855 > * disables every child regardless of each child's own flag.
856 > *
857 > * A child is turned on or off by id with
858 > * {@link SessionCustomizationToggledAction | `session/customizationToggled`}.
859 > */
860 > enabled?: boolean;
861 > }
862 >
863 > /**
864 > * A custom agent contributed by a plugin or directory.
865 > *
866 > * Mirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)
867 > * format: a markdown file with YAML frontmatter, where the body is the
868 > * agent's system prompt.
869 > *
870 > * @category Customization Types
871 > */
872 > export interface AgentCustomization extends ChildCustomizationBase {
873 > type: CustomizationType.Agent;
874 > /**
875 > * Short description of what the agent specializes in and when to
876 > * invoke it. Sourced from the agent file's frontmatter `description`.
877 > */
878 > description?: string;
879 > /**
880 > * Model the agent is pinned to, sourced from the agent file's
881 > * frontmatter `model`. Absent means the agent inherits the session's
882 > * default model.
883 > */
884 > model?: string;
885 > /**
886 > * Allowlist of tool names the agent is scoped to, sourced from the
887 > * agent file's frontmatter `tools`. A non-empty list restricts the
888 > * agent to exactly those tools. Absent — or an empty list — imposes no
889 > * restriction beyond the session default: the agent may use any
890 > * available tool. Producers express "no restriction" by omitting the
891 > * field rather than sending an empty array, so an empty list carries no
892 > * meaning distinct from absence.
893 > */
894 > tools?: string[];
895 > /**
896 > * When `true`, the agent will not auto-delegate to this custom agent
897 > * as a sub-agent; it can only be selected by the user. Absent or
898 > * `false` means the agent may delegate to it.
899 > */
900 > disableModelInvocation?: boolean;
901 > /**
902 > * When `true`, the user cannot select this custom agent (for example,
903 > * in a picker); it remains available for the agent to auto-delegate
904 > * to. Absent or `false` means the user may select it.
905 > */
906 > disableUserInvocation?: boolean;
907 > }
908 >
909 > /**
910 > * A skill contributed by a plugin or directory.
911 > *
912 > * Covers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)
913 > * — the `skills/` directory layout (one subdirectory per skill, each with
914 > * a `SKILL.md`) and the flatter `commands/` directory of slash-command
915 > * skills.
916 > *
917 > * @category Customization Types
918 > */
919 > export interface SkillCustomization extends ChildCustomizationBase {
920 > type: CustomizationType.Skill;
921 > /**
922 > * Short description used for help text and auto-invocation matching.
923 > * Sourced from the skill's frontmatter `description`.
924 > */
925 > description?: string;
926 > /**
927 > * When `true`, only the user can invoke this skill — the agent will not
928 > * auto-invoke it. Sourced from the command skill's frontmatter
929 > * `disable-model-invocation` flag.
930 > */
931 > disableModelInvocation?: boolean;
932 > /**
933 > * When `true`, the user cannot directly invoke this skill (for example,
934 > * as a slash command); it remains available for the agent to
935 > * auto-invoke. Absent or `false` means the user may invoke it.
936 > */
937 > disableUserInvocation?: boolean;
938 > }
939 >
940 > /**
941 > * A prompt contributed by a plugin or directory.
942 > *
943 > * @category Customization Types
944 > */
945 > export interface PromptCustomization extends ChildCustomizationBase {
946 > type: CustomizationType.Prompt;
947 > /** Short description of what the prompt does. */
948 > description?: string;
949 > }
950 >
951 > /**
952 > * A rule contributed by a plugin or directory.
953 > *
954 > * Mirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)
955 > * format: a markdown file (e.g. `.mdc`) whose body is injected into
956 > * context while the rule is active. This type also covers tool-specific
957 > * "instruction" formats (e.g. VS Code Copilot's
958 > * `.github/instructions/*.md`), which differ only in naming — they
959 > * share the same semantics of `description`, optional always-on
960 > * activation, and optional glob scoping.
961 > *
962 > * @category Customization Types
963 > */
964 > export interface RuleCustomization extends ChildCustomizationBase {
965 > type: CustomizationType.Rule;
966 > /**
967 > * Description of what the rule enforces.
968 > */
969 > description?: string;
970 > /**
971 > * When `true`, the rule is always active (subject to `globs` if any).
972 > * When `false` or absent, the agent or user decides whether to apply
973 > * the rule.
974 > */
975 > alwaysApply?: boolean;
976 > /**
977 > * Glob patterns the rule applies to. When present, the rule is only
978 > * active for matching files.
979 > */
980 > globs?: string[];
981 > }
982 >
983 > /**
984 > * A hook manifest contributed by a plugin or directory.
985 > *
986 > * @category Customization Types
987 > */
988 > export interface HookCustomization extends ChildCustomizationBase {
989 > type: CustomizationType.Hook;
990 > }
991 >
992 > /**
993 > * An MCP server contributed by a plugin or directory.
994 > *
995 > * When the server is declared inline in the containing plugin manifest,
996 > * `uri` points at the manifest file and
997 > * {@link CustomizationBase.range | `range`} narrows it to the
998 > * declaration's span.
999 > *
1000 > * The MCP server customization also reflects its current status.
1001 > *
1002 > * @category Customization Types
1003 > */
1004 > export interface McpServerCustomization extends CustomizationBase {
1005 > type: CustomizationType.McpServer;
1006 > /**
1007 > * Whether this MCP server is currently enabled.
1008 > */
1009 > enabled: boolean;
1010 > /**
1011 > * Current lifecycle state of the MCP server.
1012 > */
1013 > state: McpServerState;
1014 > /**
1015 > * An `mcp://`-protocol channel the client uses to side-channel traffic
1016 > * into the upstream MCP server itself. The channel is NOT a fresh raw MCP
1017 > * connection: it piggybacks on the AHP transport
1018 > * and skips the MCP `initialize` sequence.
1019 > *
1020 > * The agent host MAY only serve a subset of MCP on this
1021 > * channel; the served subset is described by domain-specific
1022 > * capabilities such as those in
1023 > * {@link McpServerCustomizationApps.capabilities}.
1024 > *
1025 > * The channel URI SHOULD be stable across the server's lifetime, but
1026 > * the agent host MAY change it (for example across a restart) and
1027 > * MAY only expose it while the server is in
1028 > * {@link McpServerStatus.Ready | `Ready`}. Absence means no
1029 > * side-channel is currently available.
1030 > */
1031 > channel?: URI;
1032 > /**
1033 > * MCP App support. This property SHOULD be advertised for MCP servers
1034 > * which support apps.
1035 > */
1036 > mcpApp?: McpServerCustomizationApps;
1037 > }
1038 >
1039 > /**
1040 > * Information from the agent host needed to render MCP Apps served
1041 > * by this MCP server.
1042 > *
1043 > * @category MCP Server State
1044 > */
1045 > export interface McpServerCustomizationApps {
1046 > /**
1047 > * The subset of MCP App
1048 > * [`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)
1049 > * the AHP host can satisfy for Views backed by this server. The
1050 > * client feeds these straight through into the `hostCapabilities` of
1051 > * the `ui/initialize` response delivered to the View.
1052 > */
1053 > capabilities: AhpMcpUiHostCapabilities;
1054 > }
1055 >
1056 > /**
1057 > * The subset of MCP App
1058 > * [`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)
1059 > * an AHP host can derive from the upstream MCP server (and from AHP's own
1060 > * forwarding plumbing). Advertised on
1061 > * {@link McpServerCustomizationApps.capabilities} so clients can pass it
1062 > * through into the `hostCapabilities` of the `ui/initialize` response
1063 > * delivered to an MCP App View.
1064 > *
1065 > * Field names mirror the MCP Apps spec exactly, so the AHP-side producer
1066 > * can pass them straight through into the `hostCapabilities` of the
1067 > * `ui/initialize` response delivered to the View.
1068 > *
1069 > * Capabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,
1070 > * `experimental`) are decided locally by whichever AHP client renders the
1071 > * View and are NOT part of this AHP-level advertisement — only the
1072 > * server-derived subset is.
1073 > *
1074 > * An agent host MUST only advertise a capability when it actually accepts the
1075 > * corresponding methods/notifications on the `mcp://` channel:
1076 > *
1077 > * - {@link serverTools}: host proxies `tools/list` and `tools/call` to
1078 > * the MCP server. When `listChanged` is `true`, the host also forwards
1079 > * `notifications/tools/list_changed`.
1080 > * - {@link serverResources}: host proxies `resources/read`,
1081 > * `resources/list`, and `resources/templates/list` to the MCP server.
1082 > * When `listChanged` is `true`, the host also forwards
1083 > * `notifications/resources/list_changed`.
1084 > * - {@link logging}: host accepts `notifications/message` log entries
1085 > * from the App and forwards them via `mcpNotification` (and forwards
1086 > * `logging/setLevel` calls to the server).
1087 > * - {@link sampling}: host serves `sampling/createMessage` via
1088 > * `mcpMethodCall`. When `sampling.tools` is present, the host also
1089 > * accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks
1090 > * inside `CreateMessageRequest`.
1091 > *
1092 > * @category MCP Server State
1093 > * @see {@link https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx | MCP Apps spec (SEP-1865)}
1094 > */
1095 > export interface AhpMcpUiHostCapabilities {
1096 > /** Producer proxies the MCP `tools/*` methods to the upstream server. */
1097 > serverTools?: {
1098 > /** Producer forwards `notifications/tools/list_changed` from the server. */
1099 > listChanged?: boolean;
1100 > };
1101 > /** Producer proxies the MCP `resources/*` methods to the upstream server. */
1102 > serverResources?: {
1103 > /** Producer forwards `notifications/resources/list_changed` from the server. */
1104 > listChanged?: boolean;
1105 > };
1106 > /** Producer accepts `notifications/message` log entries from the App via `mcpNotification`. */
1107 > logging?: Record<string, never>;
1108 > /** Producer serves `sampling/createMessage` via `mcpMethodCall`. */
1109 > sampling?: {
1110 > /**
1111 > * Producer accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content
1112 > * blocks inside `CreateMessageRequest`.
1113 > */
1114 > tools?: Record<string, never>;
1115 > };
1116 > }
1117 >
1118 > /**
1119 > * Child customizations that live inside a {@link PluginCustomization} or
1120 > * {@link DirectoryCustomization}.
1121 > *
1122 > * @category Customization Types
1123 > */
1124 > export type ChildCustomization =
1125 > | AgentCustomization
1126 > | SkillCustomization
1127 > | PromptCustomization
1128 > | RuleCustomization
1129 > | HookCustomization
1130 > | McpServerCustomization;
1131 >
1132 > /**
1133 > * A top-level customization active in a session. Either a container
1134 > * ({@link PluginCustomization} or {@link DirectoryCustomization}) whose
1135 > * leaf customizations live in its
1136 > * {@link ContainerCustomizationBase.children | `children`} array, or a
1137 > * bare {@link McpServerCustomization} surfaced directly by the host.
1138 > *
1139 > * @category Customization Types
1140 > */
1141 > export type Customization =
1142 > | PluginCustomization
1143 > | DirectoryCustomization
1144 > | McpServerCustomization;
1145 >
1146 >
1147 > // ─── MCP Server State ────────────────────────────────────────────────────────
1148 >
1149 > /**
1150 > * Discriminant for the {@link McpServerState} union.
1151 > *
1152 > * @category MCP Server State
1153 > */
1154 > export const enum McpServerStatus {
1155 > /** Server has been registered but is not yet running. */
1156 > Starting = 'starting',
1157 > /** Server is running and serving requests. */
1158 > Ready = 'ready',
1159 > /**
1160 > * Server is reachable but requires additional authentication before it
1161 > * can start, or before it can serve a particular request. Carries the
1162 > * RFC 9728 Protected Resource Metadata the client needs to obtain a
1163 > * token; the client then pushes the token via the existing
1164 > * `authenticate` command.
1165 > */
1166 > AuthRequired = 'authRequired',
1167 > /** Server failed to start, crashed, or otherwise transitioned to a fatal error. */
1168 > Error = 'error',
1169 > /** Server has been shut down. */
1170 > Stopped = 'stopped',
1171 > }
1172 >
1173 > /**
1174 > * Why an MCP server is currently in the {@link McpServerStatus.AuthRequired}
1175 > * state. Mirrors the three failure modes defined by the
1176 > * [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.md).
1177 > *
1178 > * @category MCP Server State
1179 > */
1180 > export const enum McpAuthRequiredReason {
1181 > /** No token has been provided yet (HTTP 401, no prior token). */
1182 > Required = 'required',
1183 > /** A previously valid token expired or was revoked (HTTP 401). */
1184 > Expired = 'expired',
1185 > /**
1186 > * Step-up auth: a token is present but its scopes are insufficient for
1187 > * the requested operation (HTTP 403 with
1188 > * `WWW-Authenticate: Bearer error="insufficient_scope"`).
1189 > *
1190 > * Unlike {@link Required} and {@link Expired} — which typically surface
1191 > * before any tool work is in flight — `InsufficientScope` is almost
1192 > * always triggered by an MCP request issued mid-turn (a `tools/call`,
1193 > * `resources/read`, etc.). The host SHOULD pair the
1194 > * {@link McpServerAuthRequiredState} transition with
1195 > * {@link SessionStatus.InputNeeded} on
1196 > * {@link SessionSummary.status | the session} so the activity becomes
1197 > * visible at the session-summary level, and clients SHOULD watch for
1198 > * this kind on any
1199 > * {@link McpServerCustomization | MCP server} backing a running tool
1200 > * call so they can present an explicit "grant more access" affordance
1201 > * tied to the blocked tool call.
1202 > */
1203 > InsufficientScope = 'insufficientScope',
1204 > }
1205 >
1206 > /**
1207 > * Server is registered with the host but has not yet started.
1208 > *
1209 > * @category MCP Server State
1210 > */
1211 > export interface McpServerStartingState {
1212 > kind: McpServerStatus.Starting;
1213 > }
1214 >
1215 > /**
1216 > * Server is running and serving requests.
1217 > *
1218 > * @category MCP Server State
1219 > */
1220 > export interface McpServerReadyState {
1221 > kind: McpServerStatus.Ready;
1222 > }
1223 >
1224 > /**
1225 > * A pre-registered OAuth client that clients use instead of dynamic client
1226 > * registration when resolving an MCP authentication challenge.
1227 > *
1228 > * @category MCP Server State
1229 > */
1230 > export interface McpOAuthClient {
1231 > /** OAuth client identifier registered with the authorization server. */
1232 > clientId: string;
1233 > /**
1234 > * OAuth client secret for a confidential client. Absence means the client is
1235 > * public and uses a secretless flow such as authorization code with PKCE.
1236 > */
1237 > clientSecret?: string;
1238 > }
1239 >
1240 > /**
1241 > * Reusable MCP authentication challenge — the RFC 9728 discovery info a
1242 > * client needs to obtain a token and push it via the `authenticate` command.
1243 > * Deliberately carries **no token**: this describes what is being asked for,
1244 > * never the ****** itself.
1245 > *
1246 > * Shared by two independent state machines that describe the same OAuth
1247 > * challenge from different vantage points:
1248 > *
1249 > * - {@link McpServerAuthRequiredState} — the MCP server itself cannot serve
1250 > * *any* request until the client authenticates.
1251 > * - {@link ToolCallAuthRequiredState} — a specific in-flight tool call is
1252 > * paused pending authentication (typically
1253 > * {@link McpAuthRequiredReason.InsufficientScope} step-up auth
1254 > * mid-execution). The server state and the tool-call state remain
1255 > * separate on purpose: the server saying "I need auth" and a tool
1256 > * invocation saying "I am waiting on that auth" are different facts that
1257 > * can be true independently.
1258 > *
1259 > * @category MCP Server State
1260 > */
1261 > export interface McpAuthRequirement {
1262 > /** Why authentication is required. */
1263 > reason: McpAuthRequiredReason;
1264 > /**
1265 > * Pre-registered OAuth client to use for authorization. When present, clients
1266 > * MUST use these credentials instead of dynamic client registration.
1267 > */
1268 > oauthClient?: McpOAuthClient;
1269 > /**
1270 > * RFC 9728 Protected Resource Metadata. The `resource` field is the
1271 > * canonical MCP server URI per RFC 8707, used as the OAuth `resource`
1272 > * indicator. `authorization_servers` is REQUIRED by the MCP
1273 > * authorization spec.
1274 > */
1275 > resource: ProtectedResourceMetadata;
1276 > /**
1277 > * Scopes required for the current challenge, parsed from the
1278 > * `WWW-Authenticate: ******"…"` header (or `scopes_supported`
1279 > * fallback). Authoritative for the next authorization request — clients
1280 > * MUST NOT assume any subset/superset relationship to
1281 > * `resource.scopes_supported`.
1282 > */
1283 > requiredScopes?: string[];
1284 > /** Human-readable hint, typically from the OAuth `error_description`. */
1285 > description?: string;
1286 > }
1287 >
1288 > /**
1289 > * Server is reachable but cannot serve requests until the client
1290 > * authenticates. Mirrors the discovery flow defined by
1291 > * [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)
1292 > * (Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge
1293 > * semantics required by the MCP authorization spec.
1294 > *
1295 > * Clients react to this state by calling the existing `authenticate`
1296 > * command with the {@link ProtectedResourceMetadata.resource | resource}
1297 > * carried here. There is **no** `notify/authRequired` notification for
1298 > * MCP servers — the action stream is the single source of truth.
1299 > *
1300 > * When the transition is triggered by a request issued during a turn
1301 > * — most commonly
1302 > * {@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}
1303 > * surfacing mid-tool-call — the host SHOULD also raise
1304 > * {@link SessionStatus.InputNeeded} on the session so the block is
1305 > * visible at the summary level. Clients SHOULD watch this status on
1306 > * any MCP server backing a running tool call and surface an explicit
1307 > * affordance (e.g. a "grant additional access" prompt) tied to that
1308 > * tool call, rather than relying on the user to notice the
1309 > * customization’s status badge.
1310 > *
1311 > * @category MCP Server State
1312 > */
1313 > export interface McpServerAuthRequiredState extends McpAuthRequirement {
1314 > kind: McpServerStatus.AuthRequired;
1315 > }
1316 >
1317 > /**
1318 > * Server failed to start, crashed, or otherwise transitioned to a
1319 > * non-recoverable error. Use {@link McpServerStatus.AuthRequired}
1320 > * for authentication failures.
1321 > *
1322 > * @category MCP Server State
1323 > */
1324 > export interface McpServerErrorState {
1325 > kind: McpServerStatus.Error;
1326 > /** Error details. */
1327 > error: ErrorInfo;
1328 > }
1329 >
1330 > /**
1331 > * Server has been shut down. The host MAY remove the server from the
1332 > * session entirely shortly after this state.
1333 > *
1334 > * @category MCP Server State
1335 > */
1336 > export interface McpServerStoppedState {
1337 > kind: McpServerStatus.Stopped;
1338 > }
1339 >
1340 > /**
1341 > * Discriminated union of all MCP server lifecycle states.
1342 > * Discriminated by `kind` (a {@link McpServerStatus} value).
1343 > *
1344 > * @category MCP Server State
1345 > */
1346 > export type McpServerState =
1347 > | McpServerStartingState
1348 > | McpServerReadyState
1349 > | McpServerAuthRequiredState
1350 > | McpServerErrorState
1351 > | McpServerStoppedState;
src/vs/base/common/async.ts 1256 covered LOC · 209 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- async.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken, CancellationTokenSource } from './cancellation.js';
7 > import { BugIndicatingError, CancellationError, isCancellationError } from './errors.js';
8 > import { Emitter, Event } from './event.js';
9 > import { Disposable, DisposableMap, DisposableStore, IDisposable, isDisposable, MutableDisposable, toDisposable } from './lifecycle.js';
10 > import { extUri as defaultExtUri, IExtUri } from './resources.js';
11 > import { URI } from './uri.js';
12 > import { setTimeout0 } from './platform.js';
13 > import { MicrotaskDelay } from './symbols.js';
14 > import { Lazy } from './lazy.js';
15 >
16 > export function isThenable<T>(obj: unknown): obj is Promise<T> {
17 return !!obj && typeof (obj as unknown as Promise<T>).then === 'function';
18 }
19 > async.ts
20 > export interface CancelablePromise<T> extends Promise<T> {
21 > cancel(): void;
22 > }
23 >
24 > /**
25 > * Returns a promise that can be cancelled using the provided cancellation token.
26 > *
27 > * @remarks When cancellation is requested, the promise will be rejected with a {@link CancellationError}.
28 > * If the promise resolves to a disposable object, it will be automatically disposed when cancellation
29 > * is requested.
30 > *
31 > * @param callback A function that accepts a cancellation token and returns a promise
32 > * @returns A promise that can be cancelled
33 > */
34 > export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> {
35 const source = new CancellationTokenSource();
36
80 };
81 }
82 > async.ts
83 > /**
84 > * Returns a promise that resolves with `undefined` as soon as the passed token is cancelled.
85 > * @see {@link raceCancellationError}
86 > */
87 > export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken): Promise<T | undefined>;
88 >
89 > /**
90 > * Returns a promise that resolves with `defaultValue` as soon as the passed token is cancelled.
91 > * @see {@link raceCancellationError}
92 > */
93 > export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue: T): Promise<T>;
94 >
95 > export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue?: T): Promise<T | undefined> {
96 return new Promise((resolve, reject) => {
97 const ref = token.onCancellationRequested(() => {
102 });
103 }
104 > async.ts
105 > /**
106 > * Returns a promise that rejects with an {@CancellationError} as soon as the passed token is cancelled.
107 > * @see {@link raceCancellation}
108 > */
109 > export function raceCancellationError<T>(promise: Promise<T>, token: CancellationToken): Promise<T> {
110 return new Promise((resolve, reject) => {
111 const ref = token.onCancellationRequested(() => {
116 });
117 }
118 > async.ts
119 > export function rejectIfNotCanceled(err: unknown): undefined {
120 if (isCancellationError(err)) {
121 return undefined;
123 return Promise.reject(err) as never;
124 }
125 > async.ts
126 > /**
127 > * Wraps a cancellable promise such that it is no cancellable. Can be used to
128 > * avoid issues with shared promises that would normally be returned as
129 > * cancellable to consumers.
130 > */
131 > export function notCancellablePromise<T>(promise: CancelablePromise<T>): Promise<T> {
132 return new Promise<T>((resolve, reject) => {
133 promise.then(resolve, reject);
134 });
135 }
136 > async.ts
137 > /**
138 > * Returns as soon as one of the promises resolves or rejects and cancels remaining promises
139 > */
140 > export function raceCancellablePromises<T>(cancellablePromises: (CancelablePromise<T> | Promise<T>)[]): CancelablePromise<T> {
141 let resolvedPromiseIndex = -1;
142 const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; }));
154 return promise;
155 }
156 > async.ts
157 > export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {
158 let promiseResolve: ((value: T | undefined) => void) | undefined = undefined;
159
168 ]);
169 }
170 > async.ts
171 > export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
172 return new Promise<T>((resolve, reject) => {
173 const item = callback();
179 });
180 }
181 > async.ts
182 > /**
183 > * Creates and returns a new promise, plus its `resolve` and `reject` callbacks.
184 > *
185 > * Replace with standardized [`Promise.withResolvers`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) once it is supported
186 > */
187 > export function promiseWithResolvers<T>(): { promise: Promise<T>; resolve: (value: T | PromiseLike<T>) => void; reject: (err?: any) => void } {
188 let resolve: (value: T | PromiseLike<T>) => void;
189 let reject: (reason?: any) => void;
194 return { promise, resolve: resolve!, reject: reject! };
195 }
196 > async.ts
197 > export interface ITask<T> {
198 > (): T;
199 > }
200 >
201 > export interface ICancellableTask<T> {
202 > (token: CancellationToken): T;
203 > }
204 >
205 > /**
206 > * A helper to prevent accumulation of sequential async tasks.
207 > *
208 > * Imagine a mail man with the sole task of delivering letters. As soon as
209 > * a letter submitted for delivery, he drives to the destination, delivers it
210 > * and returns to his base. Imagine that during the trip, N more letters were submitted.
211 > * When the mail man returns, he picks those N letters and delivers them all in a
212 > * single trip. Even though N+1 submissions occurred, only 2 deliveries were made.
213 > *
214 > * The throttler implements this via the queue() method, by providing it a task
215 > * factory. Following the example:
216 > *
217 > * const throttler = new Throttler();
218 > * const letters = [];
219 > *
220 > * function deliver() {
221 > * const lettersToDeliver = letters;
222 > * letters = [];
223 > * return makeTheTrip(lettersToDeliver);
224 > * }
225 > *
226 > * function onLetterReceived(l) {
227 > * letters.push(l);
228 > * throttler.queue(deliver);
229 > * }
230 > */
231 > export class Throttler implements IDisposable {
232 >
233 > private activePromise: Promise<any> | null;
234 > private queuedPromise: Promise<any> | null;
235 > private queuedPromiseFactory: ICancellableTask<Promise<any>> | null;
236 > private cancellationTokenSource: CancellationTokenSource;
237 >
238 > constructor() {
239 this.activePromise = null;
240 this.queuedPromise = null;
243 this.cancellationTokenSource = new CancellationTokenSource();
244 }
245 > async.ts
246 > queue<T>(promiseFactory: ICancellableTask<Promise<T>>): Promise<T> {
247 if (this.cancellationTokenSource.token.isCancellationRequested) {
248 return Promise.reject(new Error('Throttler is disposed'));
288 });
289 }
290 > async.ts
291 > dispose(): void {
292 this.cancellationTokenSource.cancel();
293 }
294 > } async.ts
295 >
296 > export class Sequencer {
297
298 private current: Promise<unknown> = Promise.resolve(null);
299 > async.ts
300 > queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
301 return this.current = this.current.then(() => promiseTask(), () => promiseTask());
302 }
303 > } async.ts
304 >
305 > /**
306 > * A {@link Throttler} per key. Calls for the same key coalesce (only the most
307 > * recently queued task runs after the active one settles); calls for different
308 > * keys are independent. Idle keys are cleaned up automatically.
309 > */
310 > export class ThrottlerByKey<TKey> implements IDisposable {
311
312 private readonly throttlers = new Map<TKey, { throttler: Throttler; count: number }>();
313 > async.ts
314 > queue<T>(key: TKey, task: ITask<Promise<T>>): Promise<T> {
315 let entry = this.throttlers.get(key);
316 if (!entry) {
327 });
328 }
329 > async.ts
330 > dispose(): void {
331 for (const { throttler } of this.throttlers.values()) {
332 throttler.dispose();
334 this.throttlers.clear();
335 }
336 > } async.ts
337 >
338 > export class SequencerByKey<TKey> {
339
340 private promiseMap = new Map<TKey, Promise<unknown>>();
341 > async.ts
342 > queue<T>(key: TKey, promiseTask: ITask<Promise<T>>): Promise<T> {
343 const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
344 const newPromise = runningPromise
353 return newPromise;
354 }
355 > async.ts
356 > peek(key: TKey): Promise<unknown> | undefined {
357 return this.promiseMap.get(key) || undefined;
358 }
359 > async.ts
360 > keys(): IterableIterator<TKey> {
361 return this.promiseMap.keys();
362 }
363 > } async.ts
364 >
365 > interface IScheduledLater extends IDisposable {
366 > isTriggered(): boolean;
367 > }
368 >
369 > const timeoutDeferred = (timeout: number, fn: () => void): IScheduledLater => {
370 let scheduled = true;
371 const handle = setTimeout(() => {
381 };
382 };
383 > async.ts
384 > const microtaskDeferred = (fn: () => void): IScheduledLater => {
385 let scheduled = true;
386 queueMicrotask(() => {
396 };
397 };
398 > async.ts
399 > /**
400 > * A helper to delay (debounce) execution of a task that is being requested often.
401 > *
402 > * Following the throttler, now imagine the mail man wants to optimize the number of
403 > * trips proactively. The trip itself can be long, so he decides not to make the trip
404 > * as soon as a letter is submitted. Instead he waits a while, in case more
405 > * letters are submitted. After said waiting period, if no letters were submitted, he
406 > * decides to make the trip. Imagine that N more letters were submitted after the first
407 > * one, all within a short period of time between each other. Even though N+1
408 > * submissions occurred, only 1 delivery was made.
409 > *
410 > * The delayer offers this behavior via the trigger() method, into which both the task
411 > * to be executed and the waiting period (delay) must be passed in as arguments. Following
412 > * the example:
413 > *
414 > * const delayer = new Delayer(WAITING_PERIOD);
415 > * const letters = [];
416 > *
417 > * function letterReceived(l) {
418 > * letters.push(l);
419 > * delayer.trigger(() => { return makeTheTrip(); });
420 > * }
421 > */
422 > export class Delayer<T> implements IDisposable {
423 >
424 > private deferred: IScheduledLater | null;
425 > private completionPromise: Promise<any> | null;
426 > private doResolve: ((value?: any | Promise<any>) => void) | null;
427 > private doReject: ((err: unknown) => void) | null;
428 > private task: ITask<T | Promise<T>> | null;
429 >
430 > constructor(public defaultDelay: number | typeof MicrotaskDelay) {
431 this.deferred = null;
432 this.completionPromise = null;
435 this.task = null;
436 }
437 > async.ts
438 > trigger(task: ITask<T | Promise<T>>, delay = this.defaultDelay): Promise<T> {
439 this.task = task;
440 this.cancelTimeout();
465 return this.completionPromise;
466 }
467 > async.ts
468 > isTriggered(): boolean {
469 return !!this.deferred?.isTriggered();
470 }
471 > async.ts
472 > cancel(): void {
473 this.cancelTimeout();
474
478 }
479 }
480 > async.ts
481 > private cancelTimeout(): void {
482 this.deferred?.dispose();
483 this.deferred = null;
484 }
485 > async.ts
486 > dispose(): void {
487 this.cancel();
488 }
489 > } async.ts
490 >
491 > /**
492 > * A helper to delay execution of a task that is being requested often, while
493 > * preventing accumulation of consecutive executions, while the task runs.
494 > *
495 > * The mail man is clever and waits for a certain amount of time, before going
496 > * out to deliver letters. While the mail man is going out, more letters arrive
497 > * and can only be delivered once he is back. Once he is back the mail man will
498 > * do one more trip to deliver the letters that have accumulated while he was out.
499 > */
500 > export class ThrottledDelayer<T> {
501 >
502 > private delayer: Delayer<Promise<T>>;
503 > private throttler: Throttler;
504 >
505 > constructor(defaultDelay: number) {
506 this.delayer = new Delayer(defaultDelay);
507 this.throttler = new Throttler();
508 }
509 > async.ts
510 > trigger(promiseFactory: ICancellableTask<Promise<T>>, delay?: number): Promise<T> {
511 return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise<T>;
512 }
513 > async.ts
514 > isTriggered(): boolean {
515 return this.delayer.isTriggered();
516 }
517 > async.ts
518 > cancel(): void {
519 this.delayer.cancel();
520 }
521 > async.ts
522 > dispose(): void {
523 this.delayer.dispose();
524 this.throttler.dispose();
525 }
526 > } async.ts
527 >
528 > /**
529 > * A barrier that is initially closed and then becomes opened permanently.
530 > */
531 > export class Barrier {
532 > private _isOpen: boolean;
533 > private _promise: Promise<boolean>;
534 > private _completePromise!: (v: boolean) => void;
535 >
536 > constructor() {
537 this._isOpen = false;
538 this._promise = new Promise<boolean>((c, e) => {
540 });
541 }
542 > async.ts
543 > isOpen(): boolean {
544 return this._isOpen;
545 }
546 > async.ts
547 > open(): void {
548 this._isOpen = true;
549 this._completePromise(true);
550 }
551 > async.ts
552 > wait(): Promise<boolean> {
553 return this._promise;
554 }
555 > } async.ts
556 >
557 > /**
558 > * A barrier that is initially closed and then becomes opened permanently after a certain period of
559 > * time or when open is called explicitly
560 > */
561 > export class AutoOpenBarrier extends Barrier {
562 >
563 > private readonly _timeout: Timeout;
564 >
565 > constructor(autoOpenTimeMs: number) {
566 super();
567 this._timeout = setTimeout(() => this.open(), autoOpenTimeMs);
568 }
569 > async.ts
570 > override open(): void {
571 clearTimeout(this._timeout);
572 super.open();
573 }
574 > } async.ts
575 >
576 > export function timeout(millis: number): CancelablePromise<void>;
577 > export function timeout(millis: number, token: CancellationToken): Promise<void>;
578 > export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Promise<void> {
579 if (!token) {
580 return createCancelablePromise(token => timeout(millis, token));
593 });
594 }
595 > async.ts
596 > /**
597 > * Creates a timeout that can be disposed using its returned value.
598 > * @param handler The timeout handler.
599 > * @param timeout An optional timeout in milliseconds.
600 > * @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
601 > *
602 > * @example
603 > * const store = new DisposableStore;
604 > * // Call the timeout after 1000ms at which point it will be automatically
605 > * // evicted from the store.
606 > * const timeoutDisposable = disposableTimeout(() => {}, 1000, store);
607 > *
608 > * if (foo) {
609 > * // Cancel the timeout and evict it from store.
610 > * timeoutDisposable.dispose();
611 > * }
612 > */
613 > export function disposableTimeout(handler: () => void, timeout = 0, store?: DisposableStore): IDisposable {
614 const timer = setTimeout(() => {
615 handler();
625 return disposable;
626 }
627 > async.ts
628 > /**
629 > * The largest delay (in milliseconds) a single `setTimeout` can represent.
630 > * Larger values overflow its internal 32-bit signed integer and fire (almost)
631 > * immediately instead of waiting.
632 > */
633 > export const MAX_TIMEOUT_DELAY = 2 ** 31 - 1; // ~24.8 days
634 >
635 > /**
636 > * Like {@link disposableTimeout}, but supports delays larger than
637 > * {@link MAX_TIMEOUT_DELAY} (~24.8 days), which a single `setTimeout` cannot
638 > * represent. The wait is split into chunks and re-armed until the target time is
639 > * reached, so the handler fires at approximately `Date.now() + timeout`.
640 > *
641 > * Note: like `setTimeout`, firing is best-effort and may drift across system
642 > * sleep or wall-clock changes; do not rely on it for precise scheduling.
643 > *
644 > * @param handler The timeout handler.
645 > * @param timeout The timeout in milliseconds. May exceed {@link MAX_TIMEOUT_DELAY}.
646 > * @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
647 > */
648 > export function disposableLongTimeout(handler: () => void, timeout: number, store?: DisposableStore): IDisposable {
649 const target = Date.now() + timeout;
650 let timer: Timeout;
671 return disposable;
672 }
673 > async.ts
674 > /**
675 > * Runs the provided list of promise factories in sequential order. The returned
676 > * promise will complete to an array of results from each promise.
677 > */
678 >
679 > export function sequence<T>(promiseFactories: ITask<Promise<T>>[]): Promise<T[]> {
680 const results: T[] = [];
681 let index = 0;
701 return Promise.resolve(null).then(thenHandler);
702 }
703 > async.ts
704 > export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
705 let index = 0;
706 const len = promiseFactories.length;
725 return loop();
726 }
727 > async.ts
728 > /**
729 > * Returns the result of the first promise that matches the "shouldStop",
730 > * running all promises in parallel. Supports cancelable promises.
731 > */
732 > export function firstParallel<T>(promiseList: Promise<T>[], shouldStop?: (t: T) => boolean, defaultValue?: T | null): Promise<T | null>;
733 > export function firstParallel<T, R extends T>(promiseList: Promise<T>[], shouldStop: (t: T) => t is R, defaultValue?: R | null): Promise<R | null>;
734 > export function firstParallel<T>(promiseList: Promise<T>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null) {
735 if (promiseList.length === 0) {
736 return Promise.resolve(defaultValue);
764 });
765 }
766 > async.ts
767 > interface ILimitedTaskFactory<T> {
768 > factory: ITask<Promise<T>>;
769 > c: (value: T | Promise<T>) => void;
770 > e: (error?: unknown) => void;
771 > }
772 >
773 > export interface ILimiter<T> {
774 >
775 > readonly size: number;
776 >
777 > queue(factory: ITask<Promise<T>>): Promise<T>;
778 >
779 > clear(): void;
780 > }
781 >
782 > /**
783 > * A helper to queue N promises and run them all with a max degree of parallelism. The helper
784 > * ensures that at any time no more than M promises are running at the same time.
785 > */
786 > export class Limiter<T> implements ILimiter<T> {
787 >
788 > private _size = 0;
789 > private _isDisposed = false;
790 > private runningPromises: number;
791 > private readonly maxDegreeOfParalellism: number;
792 > private readonly outstandingPromises: ILimitedTaskFactory<T>[];
793 > private readonly _onDrained: Emitter<void>;
794 >
795 > constructor(maxDegreeOfParalellism: number) {
796 this.maxDegreeOfParalellism = maxDegreeOfParalellism;
797 this.outstandingPromises = [];
799 this._onDrained = new Emitter<void>();
800 }
801 > async.ts
802 > /**
803 > *
804 > * @returns A promise that resolved when all work is done (onDrained) or when
805 > * there is nothing to do
806 > */
807 > whenIdle(): Promise<void> {
808 return this.size > 0
809 ? Event.toPromise(this.onDrained)
810 : Promise.resolve();
811 }
812 > async.ts
813 > get onDrained(): Event<void> {
814 return this._onDrained.event;
815 }
816 > async.ts
817 > get size(): number {
818 return this._size;
819 }
820 > async.ts
821 > queue(factory: ITask<Promise<T>>): Promise<T> {
822 if (this._isDisposed) {
823 throw new Error('Object has been disposed');
830 });
831 }
832 > async.ts
833 > private consume(): void {
834 while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
835 const iLimitedTask = this.outstandingPromises.shift()!;
841 }
842 }
843 > async.ts
844 > private consumed(): void {
845 if (this._isDisposed) {
846 return;
855 }
856 }
857 > async.ts
858 > clear(): void {
859 if (this._isDisposed) {
860 throw new Error('Object has been disposed');
863 this._size = this.runningPromises;
864 }
865 > async.ts
866 > dispose(): void {
867 this._isDisposed = true;
868 this.outstandingPromises.length = 0; // stop further processing
870 this._onDrained.dispose();
871 }
872 > } async.ts
873 >
874 > /**
875 > * A queue is handles one promise at a time and guarantees that at any time only one promise is executing.
876 > */
877 > export class Queue<T> extends Limiter<T> {
878 >
879 > constructor() {
880 super(1);
881 }
882 > } async.ts
883 >
884 > /**
885 > * Same as `Queue`, ensures that only 1 task is executed at the same time. The difference to `Queue` is that
886 > * there is only 1 task about to be scheduled next. As such, calling `queue` while a task is executing will
887 > * replace the currently queued task until it executes.
888 > *
889 > * As such, the returned promise may not be from the factory that is passed in but from the next factory that
890 > * is running after having called `queue`.
891 > */
892 > export class LimitedQueue {
893
894 private readonly sequentializer = new TaskSequentializer();
895
896 private tasks = 0;
897 > async.ts
898 > queue(factory: ITask<Promise<void>>): Promise<void> {
899 if (!this.sequentializer.isRunning()) {
900 return this.sequentializer.run(this.tasks++, factory());
905 });
906 }
907 > } async.ts
908 >
909 > /**
910 > * A helper to organize queues per resource. The ResourceQueue makes sure to manage queues per resource
911 > * by disposing them once the queue is empty.
912 > */
913 > export class ResourceQueue implements IDisposable {
914
915 private readonly queues = new Map<string, Queue<void>>();
919 private drainListeners: DisposableMap<number> | undefined = undefined;
920 private drainListenerCount = 0;
921 > async.ts
922 > async whenDrained(): Promise<void> {
923 if (this.isDrained()) {
924 return;
930 return promise.p;
931 }
932 > async.ts
933 > private isDrained(): boolean {
934 for (const [, queue] of this.queues) {
935 if (queue.size > 0) {
940 return true;
941 }
942 > async.ts
943 > queueSize(resource: URI, extUri: IExtUri = defaultExtUri): number {
944 const key = extUri.getComparisonKey(resource);
945
946 return this.queues.get(key)?.size ?? 0;
947 }
948 > async.ts
949 > queueFor(resource: URI, factory: ITask<Promise<void>>, extUri: IExtUri = defaultExtUri): Promise<void> {
950 const key = extUri.getComparisonKey(resource);
951
977 return queue.queue(factory);
978 }
979 > async.ts
980 > private onDidQueueDrain(): void {
981 if (!this.isDrained()) {
982 return; // not done yet
985 this.releaseDrainers();
986 }
987 > async.ts
988 > private releaseDrainers(): void {
989 for (const drainer of this.drainers) {
990 drainer.complete();
993 this.drainers.clear();
994 }
995 > async.ts
996 > dispose(): void {
997 for (const [, queue] of this.queues) {
998 queue.dispose();
1011 this.drainListeners?.dispose();
1012 }
1013 > } async.ts
1014 >
1015 > export type Task<T = void> = () => (Promise<T> | T);
1016 >
1017 > /**
1018 > * Wrap a type in an optional promise. This can be useful to avoid the runtime
1019 > * overhead of creating a promise.
1020 > */
1021 > export type MaybePromise<T> = Promise<T> | T;
1022 >
1023 > /**
1024 > * Processes tasks in the order they were scheduled.
1025 > */
1026 > export class TaskQueue {
1027 private _runningTask: Task<any> | undefined = undefined;
1028 private _pendingTasks: { task: Task<any>; deferred: DeferredPromise<any>; setUndefinedWhenCleared: boolean }[] = [];
1029 > async.ts
1030 > /**
1031 > * Waits for the current and pending tasks to finish, then runs and awaits the given task.
1032 > * If the task is skipped because of clearPending, the promise is rejected with a CancellationError.
1033 > */
1034 > public schedule<T>(task: Task<T>): Promise<T> {
1035 const deferred = new DeferredPromise<T>();
1036 this._pendingTasks.push({ task, deferred, setUndefinedWhenCleared: false });
1038 return deferred.p;
1039 }
1040 > async.ts
1041 > /**
1042 > * Waits for the current and pending tasks to finish, then runs and awaits the given task.
1043 > * If the task is skipped because of clearPending, the promise is resolved with undefined.
1044 > */
1045 > public scheduleSkipIfCleared<T>(task: Task<T>): Promise<T | undefined> {
1046 const deferred = new DeferredPromise<T>();
1047 this._pendingTasks.push({ task, deferred, setUndefinedWhenCleared: true });
1049 return deferred.p;
1050 }
1051 > async.ts
1052 > private _runIfNotRunning(): void {
1053 if (this._runningTask === undefined) {
1054 this._processQueue();
1055 }
1056 }
1057 > async.ts
1058 > private async _processQueue(): Promise<void> {
1059 if (this._pendingTasks.length === 0) {
1060 return;
1082 }
1083 }
1084 > async.ts
1085 > /**
1086 > * Clears all pending tasks. Does not cancel the currently running task.
1087 > */
1088 > public clearPending(): void {
1089 const tasks = this._pendingTasks;
1090 this._pendingTasks = [];
1097 }
1098 }
1099 > } async.ts
1100 >
1101 > export class TimeoutTimer implements IDisposable {
1102 > private _token: Timeout | undefined;
1103 > private _isDisposed = false;
1104 >
1105 > constructor();
1106 > constructor(runner: () => void, timeout: number);
1107 > constructor(runner?: () => void, timeout?: number) {
1108 this._token = undefined;
1109
1112 }
1113 }
1114 > async.ts
1115 > dispose(): void {
1116 this.cancel();
1117 this._isDisposed = true;
1118 }
1119 > async.ts
1120 > cancel(): void {
1121 if (this._token !== undefined) {
1122 clearTimeout(this._token);
1124 }
1125 }
1126 > async.ts
1127 > cancelAndSet(runner: () => void, timeout: number): void {
1128 if (this._isDisposed) {
1129 throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);
1136 }, timeout);
1137 }
1138 > async.ts
1139 > setIfNotSet(runner: () => void, timeout: number): void {
1140 if (this._isDisposed) {
1141 throw new BugIndicatingError(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);
1151 }, timeout);
1152 }
1153 > } async.ts
1154 >
1155 > export class IntervalTimer implements IDisposable {
1156
1157 private disposable: IDisposable | undefined = undefined;
1158 private isDisposed = false;
1159 > async.ts
1160 > cancel(): void {
1161 this.disposable?.dispose();
1162 this.disposable = undefined;
1163 }
1164 > async.ts
1165 > cancelAndSet(runner: () => void, interval: number, context = globalThis): void {
1166 if (this.isDisposed) {
1167 throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed IntervalTimer`);
1178 });
1179 }
1180 > async.ts
1181 > dispose(): void {
1182 this.cancel();
1183 this.isDisposed = true;
1184 }
1185 > } async.ts
1186 >
1187 > export class RunOnceScheduler<Runner extends (...args: any[]) => any = () => any> implements IDisposable {
1188 >
1189 > protected runner: Runner | null;
1190 >
1191 > private timeoutToken: Timeout | undefined;
1192 > private timeout: number;
1193 > private timeoutHandler: () => void;
1194 >
1195 > constructor(runner: Runner, delay: number) {
1196 > this.timeoutToken = undefined; async.ts
1197 > this.runner = runner;
1198 > this.timeout = delay;
1199 > this.timeoutHandler = this.onTimeout.bind(this);
1200 > }
1201 > async.ts
1202 > /**
1203 > * Dispose RunOnceScheduler
1204 > */
1205 > dispose(): void {
1206 > this.cancel(); async.ts
1207 > this.runner = null;
1208 > }
1209 > async.ts
1210 > /**
1211 > * Cancel current scheduled runner (if any).
1212 > */
1213 > cancel(): void {
1214 > if (this.isScheduled()) { async.ts
1215 > clearTimeout(this.timeoutToken); async.ts
1216 > this.timeoutToken = undefined;
1217 > }
1218 > } async.ts
1219 > async.ts
1220 > /**
1221 > * Cancel previous runner (if any) & schedule a new runner.
1222 > */
1223 > schedule(delay = this.timeout): void {
1224 > this.cancel(); async.ts
1225 > this.timeoutToken = setTimeout(this.timeoutHandler, delay);
1226 > }
1227 > async.ts
1228 > get delay(): number {
1229 return this.timeout;
1230 }
1231 > async.ts
1232 > set delay(value: number) {
1233 this.timeout = value;
1234 }
1235 > async.ts
1236 > /**
1237 > * Returns true if scheduled.
1238 > */
1239 > isScheduled(): boolean {
1240 > return this.timeoutToken !== undefined; async.ts
1241 > }
1242 > async.ts
1243 > flush(): void {
1244 if (this.isScheduled()) {
1245 this.cancel();
1247 }
1248 }
1249 > async.ts
1250 > private onTimeout() {
1251 this.timeoutToken = undefined;
1252 if (this.runner) {
1254 }
1255 }
1256 > async.ts
1257 > protected doRun(): void {
1258 this.runner?.();
1259 }
1260 > } async.ts
1261 >
1262 > /**
1263 > * Same as `RunOnceScheduler`, but doesn't count the time spent in sleep mode.
1264 > * > **NOTE**: Only offers 1s resolution.
1265 > *
1266 > * When calling `setTimeout` with 3hrs, and putting the computer immediately to sleep
1267 > * for 8hrs, `setTimeout` will fire **as soon as the computer wakes from sleep**. But
1268 > * this scheduler will execute 3hrs **after waking the computer from sleep**.
1269 > */
1270 > export class ProcessTimeRunOnceScheduler {
1271 >
1272 > private runner: (() => void) | null;
1273 > private timeout: number;
1274 >
1275 > private counter: number;
1276 > private intervalToken: Timeout | undefined;
1277 > private intervalHandler: () => void;
1278 >
1279 > constructor(runner: () => void, delay: number) {
1280 if (delay % 1000 !== 0) {
1281 console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${delay}ms is not a multiple of 1000ms.`);
1287 this.intervalHandler = this.onInterval.bind(this);
1288 }
1289 > async.ts
1290 > dispose(): void {
1291 this.cancel();
1292 this.runner = null;
1293 }
1294 > async.ts
1295 > cancel(): void {
1296 if (this.isScheduled()) {
1297 clearInterval(this.intervalToken);
1299 }
1300 }
1301 > async.ts
1302 > /**
1303 > * Cancel previous runner (if any) & schedule a new runner.
1304 > */
1305 > schedule(delay = this.timeout): void {
1306 if (delay % 1000 !== 0) {
1307 console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${delay}ms is not a multiple of 1000ms.`);
1311 this.intervalToken = setInterval(this.intervalHandler, 1000);
1312 }
1313 > async.ts
1314 > /**
1315 > * Returns true if scheduled.
1316 > */
1317 > isScheduled(): boolean {
1318 return this.intervalToken !== undefined;
1319 }
1320 > async.ts
1321 > private onInterval() {
1322 this.counter--;
1323 if (this.counter > 0) {
1331 this.runner?.();
1332 }
1333 > } async.ts
1334 >
1335 > export class RunOnceWorker<T> extends RunOnceScheduler<(units: T[]) => void> {
1336 >
1337 > private units: T[] = [];
1338 >
1339 > constructor(runner: (units: T[]) => void, timeout: number) {
1340 super(runner, timeout);
1341 }
1342 > async.ts
1343 > work(unit: T): void {
1344 this.units.push(unit);
1345
1348 }
1349 }
1350 > async.ts
1351 > protected override doRun(): void {
1352 const units = this.units;
1353 this.units = [];
1355 this.runner?.(units);
1356 }
1357 > async.ts
1358 > override dispose(): void {
1359 this.units = [];
1360
1361 super.dispose();
1362 }
1363 > } async.ts
1364 >
1365 > export interface IThrottledWorkerOptions {
1366 >
1367 > /**
1368 > * maximum of units the worker will pass onto handler at once
1369 > */
1370 > maxWorkChunkSize: number;
1371 >
1372 > /**
1373 > * maximum of units the worker will keep in memory for processing
1374 > */
1375 > maxBufferedWork: number | undefined;
1376 >
1377 > /**
1378 > * delay before processing the next round of chunks when chunk size exceeds limits
1379 > */
1380 > throttleDelay: number;
1381 >
1382 > /**
1383 > * When enabled will guarantee that two distinct calls to `work()` are not executed
1384 > * without throttle delay between them.
1385 > * Otherwise if the worker isn't currently throttling it will execute work immediately.
1386 > */
1387 > waitThrottleDelayBetweenWorkUnits?: boolean;
1388 > }
1389 >
1390 > /**
1391 > * The `ThrottledWorker` will accept units of work `T`
1392 > * to handle. The contract is:
1393 > * * there is a maximum of units the worker can handle at once (via `maxWorkChunkSize`)
1394 > * * there is a maximum of units the worker will keep in memory for processing (via `maxBufferedWork`)
1395 > * * after having handled `maxWorkChunkSize` units, the worker needs to rest (via `throttleDelay`)
1396 > */
1397 > export class ThrottledWorker<T> extends Disposable {
1398 >
1399 > private readonly pendingWork: T[] = [];
1400 >
1401 > private readonly throttler = this._register(new MutableDisposable<RunOnceScheduler>());
1402 > private disposed = false;
1403 > private lastExecutionTime = 0;
1404 >
1405 > constructor(
1406 private options: IThrottledWorkerOptions,
1407 private readonly handler: (units: T[]) => void
1409 super();
1410 }
1411 > async.ts
1412 > /**
1413 > * The number of work units that are pending to be processed.
1414 > */
1415 > get pending(): number { return this.pendingWork.length; }
1416 >
1417 > /**
1418 > * Add units to be worked on. Use `pending` to figure out
1419 > * how many units are not yet processed after this method
1420 > * was called.
1421 > *
1422 > * @returns whether the work was accepted or not. If the
1423 > * worker is disposed, it will not accept any more work.
1424 > * If the number of pending units would become larger
1425 > * than `maxPendingWork`, more work will also not be accepted.
1426 > */
1427 > work(units: readonly T[]): boolean {
1428 if (this.disposed) {
1429 return false; // work not accepted: disposed
1469 return true; // work accepted
1470 }
1471 > async.ts
1472 > private doWork(): void {
1473 this.lastExecutionTime = Date.now();
1474
1481 }
1482 }
1483 > async.ts
1484 > private scheduleThrottler(delay = this.options.throttleDelay): void {
1485 this.throttler.value = new RunOnceScheduler(() => {
1486 this.throttler.clear();
1490 this.throttler.value.schedule();
1491 }
1492 > async.ts
1493 > override dispose(): void {
1494 super.dispose();
1495
1497 this.disposed = true;
1498 }
1499 > } async.ts
1500 >
1501 > //#region -- run on idle tricks ------------
1502 >
1503 > export interface IdleDeadline {
1504 > readonly didTimeout: boolean;
1505 > timeRemaining(): number;
1506 > }
1507 >
1508 > type IdleApi = Pick<typeof globalThis, 'requestIdleCallback' | 'cancelIdleCallback'>;
1509 >
1510 >
1511 > /**
1512 > * Execute the callback the next time the browser is idle, returning an
1513 > * {@link IDisposable} that will cancel the callback when disposed. This wraps
1514 > * [requestIdleCallback] so it will fallback to [setTimeout] if the environment
1515 > * doesn't support it.
1516 > *
1517 > * @param callback The callback to run when idle, this includes an
1518 > * [IdleDeadline] that provides the time alloted for the idle callback by the
1519 > * browser. Not respecting this deadline will result in a degraded user
1520 > * experience.
1521 > * @param timeout A timeout at which point to queue no longer wait for an idle
1522 > * callback but queue it on the regular event loop (like setTimeout). Typically
1523 > * this should not be used.
1524 > *
1525 > * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
1526 > * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
1527 > * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
1528 > *
1529 > * **Note** that there is `dom.ts#runWhenWindowIdle` which is better suited when running inside a browser
1530 > * context
1531 > */
1532 > export let runWhenGlobalIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
1533 >
1534 > export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
1535 >
1536 > (function () {
1537 > const safeGlobal: any = globalThis;
1538 > if (typeof safeGlobal.requestIdleCallback !== 'function' || typeof safeGlobal.cancelIdleCallback !== 'function') {
1539 > _runWhenIdle = (_targetWindow, runner, timeout?) => {
1540 setTimeout0(() => {
1541 if (disposed) {
1561 };
1562 };
1563 > } else { async.ts
1564 _runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => {
1565 const handle: number = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
1576 };
1577 }
1578 > runWhenGlobalIdle = (runner, timeout) => _runWhenIdle(globalThis, runner, timeout); async.ts
1579 > })();
1580 >
1581 > export function installFakeRunWhenIdle(fakeImpl: typeof _runWhenIdle): IDisposable {
1582 const origRunWhenIdle = _runWhenIdle;
1583 const origRunWhenGlobalIdle = runWhenGlobalIdle;
1589 });
1590 }
1591 > async.ts
1592 > export abstract class AbstractIdleValue<T> {
1593 >
1594 > private readonly _executor: () => void;
1595 > private readonly _handle: IDisposable;
1596 >
1597 > private _didRun: boolean = false;
1598 > private _value?: T;
1599 > private _error: unknown;
1600 >
1601 > constructor(targetWindow: IdleApi, executor: () => T) {
1602 this._executor = () => {
1603 try {
1611 this._handle = _runWhenIdle(targetWindow, () => this._executor());
1612 }
1613 > async.ts
1614 > dispose(): void {
1615 this._handle.dispose();
1616 }
1617 > async.ts
1618 > get value(): T {
1619 if (!this._didRun) {
1620 this._handle.dispose();
1626 return this._value!;
1627 }
1628 > async.ts
1629 > get isInitialized(): boolean {
1630 return this._didRun;
1631 }
1632 > } async.ts
1633 >
1634 > /**
1635 > * An `IdleValue` that always uses the current window (which might be throttled or inactive)
1636 > *
1637 > * **Note** that there is `dom.ts#WindowIdleValue` which is better suited when running inside a browser
1638 > * context
1639 > */
1640 > export class GlobalIdleValue<T> extends AbstractIdleValue<T> {
1641 >
1642 > constructor(executor: () => T) {
1643 super(globalThis, executor);
1644 }
1645 > } async.ts
1646 >
1647 > //#endregion
1648 >
1649 export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries: number): Promise<T> {
1650 let lastError: Error | undefined;
1662 throw lastError;
1663 }
1664 > async.ts
1665 > //#region Task Sequentializer
1666 >
1667 > interface IRunningTask {
1668 > readonly taskId: number;
1669 > readonly cancel: () => void;
1670 > readonly promise: Promise<void>;
1671 > }
1672 >
1673 > interface IQueuedTask {
1674 > readonly promise: Promise<void>;
1675 > readonly promiseResolve: () => void;
1676 > readonly promiseReject: (error: Error) => void;
1677 > run: ITask<Promise<void>>;
1678 > }
1679 >
1680 > export interface ITaskSequentializerWithRunningTask {
1681 > readonly running: Promise<void>;
1682 > }
1683 >
1684 > export interface ITaskSequentializerWithQueuedTask {
1685 > readonly queued: IQueuedTask;
1686 > }
1687 >
1688 > /**
1689 > * @deprecated use `LimitedQueue` instead for an easier to use API
1690 > */
1691 > export class TaskSequentializer {
1692 >
1693 > private _running?: IRunningTask;
1694 > private _queued?: IQueuedTask;
1695 >
1696 > isRunning(taskId?: number): this is ITaskSequentializerWithRunningTask {
1697 if (typeof taskId === 'number') {
1698 return this._running?.taskId === taskId;
1701 return !!this._running;
1702 }
1703 > async.ts
1704 > get running(): Promise<void> | undefined {
1705 return this._running?.promise;
1706 }
1707 > async.ts
1708 > cancelRunning(): void {
1709 this._running?.cancel();
1710 }
1711 > async.ts
1712 > run(taskId: number, promise: Promise<void>, onCancel?: () => void,): Promise<void> {
1713 this._running = { taskId, cancel: () => onCancel?.(), promise };
1714
1717 return promise;
1718 }
1719 > async.ts
1720 > private doneRunning(taskId: number): void {
1721 if (this._running && taskId === this._running.taskId) {
1722
1728 }
1729 }
1730 > async.ts
1731 > private runQueued(): void {
1732 if (this._queued) {
1733 const queued = this._queued;
1738 }
1739 }
1740 > async.ts
1741 > /**
1742 > * Note: the promise to schedule as next run MUST itself call `run`.
1743 > * Otherwise, this sequentializer will report `false` for `isRunning`
1744 > * even when this task is running. Missing this detail means that
1745 > * suddenly multiple tasks will run in parallel.
1746 > */
1747 > queue(run: ITask<Promise<void>>): Promise<void> {
1748
1749 // this is our first queued task, so we create associated promise with it
1767 return this._queued.promise;
1768 }
1769 > async.ts
1770 > hasQueued(): this is ITaskSequentializerWithQueuedTask {
1771 return !!this._queued;
1772 }
1773 > async.ts
1774 > async join(): Promise<void> {
1775 return this._queued?.promise ?? this._running?.promise;
1776 }
1777 > } async.ts
1778 >
1779 > //#endregion
1780 >
1781 > //#region
1782 >
1783 > /**
1784 > * The `IntervalCounter` allows to count the number
1785 > * of calls to `increment()` over a duration of
1786 > * `interval`. This utility can be used to conditionally
1787 > * throttle a frequent task when a certain threshold
1788 > * is reached.
1789 > */
1790 > export class IntervalCounter {
1791 >
1792 > private lastIncrementTime = 0;
1793 >
1794 > private value = 0;
1795 >
1796 > constructor(private readonly interval: number, private readonly nowFn = () => Date.now()) { }
1797 >
1798 > increment(): number {
1799 const now = this.nowFn();
1800
1810 return this.value;
1811 }
1812 > } async.ts
1813 >
1814 > //#endregion
1815 >
1816 > //#region
1817 >
1818 > export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
1819 >
1820 > const enum DeferredOutcome {
1821 > Resolved,
1822 > Rejected
1823 > }
1824 >
1825 > /**
1826 > * Creates a promise whose resolution or rejection can be controlled imperatively.
1827 > */
1828 > export class DeferredPromise<T> {
1829 >
1830 > public static fromPromise<T>(promise: Promise<T>): DeferredPromise<T> {
1831 const deferred = new DeferredPromise<T>();
1832 deferred.settleWith(promise);
1833 return deferred;
1834 }
1835 > async.ts
1836 > private completeCallback!: ValueCallback<T>;
1837 > private errorCallback!: (err: unknown) => void;
1838 > private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T };
1839 >
1840 > public get isRejected() {
1841 return this.outcome?.outcome === DeferredOutcome.Rejected;
1842 }
1843 > async.ts
1844 > public get isResolved() {
1845 return this.outcome?.outcome === DeferredOutcome.Resolved;
1846 }
1847 > async.ts
1848 > public get isSettled() {
1849 return !!this.outcome;
1850 }
1851 > async.ts
1852 > public get value() {
1853 return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
1854 }
1855 > async.ts
1856 > public readonly p: Promise<T>;
1857 >
1858 > constructor() {
1859 this.p = new Promise<T>((c, e) => {
1860 this.completeCallback = c;
1862 });
1863 }
1864 > async.ts
1865 > public complete(value: T) {
1866 if (this.isSettled) {
1867 return Promise.resolve();
1874 });
1875 }
1876 > async.ts
1877 > public error(err: unknown) {
1878 if (this.isSettled) {
1879 return Promise.resolve();
1886 });
1887 }
1888 > async.ts
1889 > public settleWith(promise: Promise<T>): Promise<void> {
1890 return promise.then(
1891 value => this.complete(value),
1893 );
1894 }
1895 > async.ts
1896 > public cancel() {
1897 return this.error(new CancellationError());
1898 }
1899 > } async.ts
1900 >
1901 > //#endregion
1902 >
1903 > //#region Promises
1904 >
1905 > export namespace Promises {
1906 >
1907 > /**
1908 > * A drop-in replacement for `Promise.all` with the only difference
1909 > * that the method awaits every promise to either fulfill or reject.
1910 > *
1911 > * Similar to `Promise.all`, only the first error will be returned
1912 > * if any.
1913 > */
1914 > export async function settled<T>(promises: Promise<T>[]): Promise<T[]> {
1915 let firstError: Error | undefined = undefined;
1916
1929 return result as unknown as T[]; // cast is needed and protected by the `throw` above
1930 }
1931 > async.ts
1932 > /**
1933 > * A helper to create a new `Promise<T>` with a body that is a promise
1934 > * itself. By default, an error that raises from the async body will
1935 > * end up as a unhandled rejection, so this utility properly awaits the
1936 > * body and rejects the promise as a normal promise does without async
1937 > * body.
1938 > *
1939 > * This method should only be used in rare cases where otherwise `async`
1940 > * cannot be used (e.g. when callbacks are involved that require this).
1941 > */
1942 > export function withAsyncBody<T, E = Error>(bodyFn: (resolve: (value: T) => unknown, reject: (error: E) => unknown) => Promise<unknown>): Promise<T> {
1943 // eslint-disable-next-line no-async-promise-executor
1944 return new Promise<T>(async (resolve, reject) => {
1950 });
1951 }
1952 > } async.ts
1953 >
1954 > export class StatefulPromise<T> {
1955 > private _value: T | undefined = undefined;
1956 > get value(): T | undefined { return this._value; }
1957 >
1958 > private _error: unknown = undefined;
1959 > get error(): unknown { return this._error; }
1960 >
1961 > private _isResolved = false;
1962 > get isResolved() { return this._isResolved; }
1963 >
1964 > public readonly promise: Promise<T>;
1965 >
1966 > constructor(promise: Promise<T>) {
1967 this.promise = promise.then(
1968 value => {
1978 );
1979 }
1980 > async.ts
1981 > /**
1982 > * Returns the resolved value.
1983 > * Throws if the promise is not resolved yet.
1984 > */
1985 > public requireValue(): T {
1986 if (!this._isResolved) {
1987 throw new BugIndicatingError('Promise is not resolved yet');
1992 return this._value!;
1993 }
1994 > } async.ts
1995 >
1996 > export class LazyStatefulPromise<T> {
1997 > private readonly _promise = new Lazy(() => new StatefulPromise(this._compute()));
1998 >
1999 > constructor(
2000 private readonly _compute: () => Promise<T>,
2001 ) { }
2002 > async.ts
2003 > /**
2004 > * Returns the resolved value.
2005 > * Throws if the promise is not resolved yet.
2006 > */
2007 > public requireValue(): T {
2008 return this._promise.value.requireValue();
2009 }
2010 > async.ts
2011 > /**
2012 > * Returns the promise (and triggers a computation of the promise if not yet done so).
2013 > */
2014 > public getPromise(): Promise<T> {
2015 return this._promise.value.promise;
2016 }
2017 > async.ts
2018 > /**
2019 > * Reads the current value without triggering a computation of the promise.
2020 > */
2021 > public get currentValue(): T | undefined {
2022 return this._promise.rawValue?.value;
2023 }
2024 > } async.ts
2025 >
2026 > //#endregion
2027 >
2028 > //#region
2029 >
2030 > const enum AsyncIterableSourceState {
2031 > Initial,
2032 > DoneOK,
2033 > DoneError,
2034 > }
2035 >
2036 > /**
2037 > * An object that allows to emit async values asynchronously or bring the iterable to an error state using `reject()`.
2038 > * This emitter is valid only for the duration of the executor (until the promise returned by the executor settles).
2039 > */
2040 > export interface AsyncIterableEmitter<T> {
2041 > /**
2042 > * The value will be appended at the end.
2043 > *
2044 > * **NOTE** If `reject()` has already been called, this method has no effect.
2045 > */
2046 > emitOne(value: T): void;
2047 > /**
2048 > * The values will be appended at the end.
2049 > *
2050 > * **NOTE** If `reject()` has already been called, this method has no effect.
2051 > */
2052 > emitMany(values: T[]): void;
2053 > /**
2054 > * Writing an error will permanently invalidate this iterable.
2055 > * The current users will receive an error thrown, as will all future users.
2056 > *
2057 > * **NOTE** If `reject()` have already been called, this method has no effect.
2058 > */
2059 > reject(error: Error): void;
2060 > }
2061 >
2062 > /**
2063 > * An executor for the `AsyncIterableObject` that has access to an emitter.
2064 > */
2065 > export interface AsyncIterableExecutor<T> {
2066 > /**
2067 > * @param emitter An object that allows to emit async values valid only for the duration of the executor.
2068 > */
2069 > (emitter: AsyncIterableEmitter<T>): unknown | Promise<unknown>;
2070 > }
2071 >
2072 > /**
2073 > * A rich implementation for an `AsyncIterable<T>`.
2074 > */
2075 > export class AsyncIterableObject<T> implements AsyncIterable<T> {
2076 >
2077 > public static fromArray<T>(items: T[]): AsyncIterableObject<T> {
2078 > return new AsyncIterableObject<T>((writer) => {
2079 > writer.emitMany(items);
2080 > });
2081 > }
2082 >
2083 > public static fromPromise<T>(promise: Promise<T[]>): AsyncIterableObject<T> {
2084 return new AsyncIterableObject<T>(async (emitter) => {
2085 emitter.emitMany(await promise);
2086 });
2087 }
2088 > async.ts
2089 > public static fromPromisesResolveOrder<T>(promises: Promise<T>[]): AsyncIterableObject<T> {
2090 return new AsyncIterableObject<T>(async (emitter) => {
2091 await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
2092 });
2093 }
2094 > async.ts
2095 > public static merge<T>(iterables: AsyncIterable<T>[]): AsyncIterableObject<T> {
2096 return new AsyncIterableObject(async (emitter) => {
2097 await Promise.all(iterables.map(async (iterable) => {
2102 });
2103 }
2104 > async.ts
2105 > public static EMPTY = AsyncIterableObject.fromArray<any>([]);
2106 >
2107 > private _state: AsyncIterableSourceState;
2108 > private _results: T[];
2109 > private _error: Error | null;
2110 > private readonly _onReturn?: () => void | Promise<void>;
2111 > private readonly _onStateChanged: Emitter<void>;
2112 >
2113 > constructor(executor: AsyncIterableExecutor<T>, onReturn?: () => void | Promise<void>) {
2114 > this._state = AsyncIterableSourceState.Initial;
2115 > this._results = [];
2116 > this._error = null;
2117 > this._onReturn = onReturn;
2118 > this._onStateChanged = new Emitter<void>();
2119 >
2120 > queueMicrotask(async () => {
2121 > const writer: AsyncIterableEmitter<T> = {
2122 > emitOne: (item) => this.emitOne(item),
2123 > emitMany: (items) => this.emitMany(items),
2124 > reject: (error) => this.reject(error)
2125 > };
2126 > try {
2127 > await Promise.resolve(executor(writer));
2128 > this.resolve();
2129 > } catch (err) {
2130 this.reject(err);
2131 > } finally { async.ts
2132 > // The executor has settled; emitting afterwards must be a no-op per the
2133 > // documented "no effect after resolve()/reject()" contract (see emitOne).
2134 > writer.emitOne = () => { };
2135 > writer.emitMany = () => { };
2136 > writer.reject = () => { };
2137 > }
2138 > });
2139 > }
2140 >
2141 > [Symbol.asyncIterator](): AsyncIterator<T, undefined, undefined> {
2142 let i = 0;
2143 return {
2162 };
2163 }
2164 > async.ts
2165 > public static map<T, R>(iterable: AsyncIterable<T>, mapFn: (item: T) => R): AsyncIterableObject<R> {
2166 return new AsyncIterableObject<R>(async (emitter) => {
2167 for await (const item of iterable) {
2170 });
2171 }
2172 > async.ts
2173 > public map<R>(mapFn: (item: T) => R): AsyncIterableObject<R> {
2174 return AsyncIterableObject.map(this, mapFn);
2175 }
2176 > async.ts
2177 > public static filter<T>(iterable: AsyncIterable<T>, filterFn: (item: T) => boolean): AsyncIterableObject<T> {
2178 return new AsyncIterableObject<T>(async (emitter) => {
2179 for await (const item of iterable) {
2184 });
2185 }
2186 > async.ts
2187 > public filter<T2 extends T>(filterFn: (item: T) => item is T2): AsyncIterableObject<T2>;
2188 > public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T>;
2189 > public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T> {
2190 return AsyncIterableObject.filter(this, filterFn);
2191 }
2192 > async.ts
2193 > public static coalesce<T>(iterable: AsyncIterable<T | undefined | null>): AsyncIterableObject<T> {
2194 return <AsyncIterableObject<T>>AsyncIterableObject.filter(iterable, item => !!item);
2195 }
2196 > async.ts
2197 > public coalesce(): AsyncIterableObject<NonNullable<T>> {
2198 return AsyncIterableObject.coalesce(this) as AsyncIterableObject<NonNullable<T>>;
2199 }
2200 > async.ts
2201 > public static async toPromise<T>(iterable: AsyncIterable<T>): Promise<T[]> {
2202 const result: T[] = [];
2203 for await (const item of iterable) {
2206 return result;
2207 }
2208 > async.ts
2209 > public toPromise(): Promise<T[]> {
2210 return AsyncIterableObject.toPromise(this);
2211 }
2212 > async.ts
2213 > /**
2214 > * The value will be appended at the end.
2215 > *
2216 > * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
2217 > */
2218 > private emitOne(value: T): void {
2219 if (this._state !== AsyncIterableSourceState.Initial) {
2220 return;
2225 this._onStateChanged.fire();
2226 }
2227 > async.ts
2228 > /**
2229 > * The values will be appended at the end.
2230 > *
2231 > * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
2232 > */
2233 > private emitMany(values: T[]): void {
2234 > if (this._state !== AsyncIterableSourceState.Initial) {
2235 return;
2236 }
2237 > // it is important to add new values at the end, async.ts
2238 > // as we may have iterators already running on the array
2239 > this._results = this._results.concat(values);
2240 > this._onStateChanged.fire();
2241 > }
2242 >
2243 > /**
2244 > * Calling `resolve()` will mark the result array as complete.
2245 > *
2246 > * **NOTE** `resolve()` must be called, otherwise all consumers of this iterable will hang indefinitely, similar to a non-resolved promise.
2247 > * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
2248 > */
2249 > private resolve(): void {
2250 > if (this._state !== AsyncIterableSourceState.Initial) {
2251 return;
2252 }
2253 > this._state = AsyncIterableSourceState.DoneOK; async.ts
2254 > this._onStateChanged.fire();
2255 > }
2256 >
2257 > /**
2258 > * Writing an error will permanently invalidate this iterable.
2259 > * The current users will receive an error thrown, as will all future users.
2260 > *
2261 > * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
2262 > */
2263 > private reject(error: Error) {
2264 if (this._state !== AsyncIterableSourceState.Initial) {
2265 return;
2269 this._onStateChanged.fire();
2270 }
2271 > } async.ts
2272 >
2273 >
2274 > export function createCancelableAsyncIterableProducer<T>(callback: (token: CancellationToken) => AsyncIterable<T>): CancelableAsyncIterableProducer<T> {
2275 const source = new CancellationTokenSource();
2276 const innerIterable = callback(source.token);
2299 });
2300 }
2301 > async.ts
2302 > export class AsyncIterableSource<T> {
2303 >
2304 > private readonly _deferred = new DeferredPromise<void>();
2305 > private readonly _asyncIterable: AsyncIterableObject<T>;
2306 >
2307 > private _errorFn: (error: Error) => void;
2308 > private _emitOneFn: (item: T) => void;
2309 > private _emitManyFn: (item: T[]) => void;
2310 >
2311 > /**
2312 > *
2313 > * @param onReturn A function that will be called when consuming the async iterable
2314 > * has finished by the consumer, e.g the for-await-loop has be existed (break, return) early.
2315 > * This is NOT called when resolving this source by its owner.
2316 > */
2317 > constructor(onReturn?: () => Promise<void> | void) {
2318 this._asyncIterable = new AsyncIterableObject(emitter => {
2319
2354 };
2355 }
2356 > async.ts
2357 > get asyncIterable(): AsyncIterableObject<T> {
2358 return this._asyncIterable;
2359 }
2360 > async.ts
2361 > resolve(): void {
2362 this._deferred.complete();
2363 }
2364 > async.ts
2365 > reject(error: Error): void {
2366 this._errorFn(error);
2367 this._deferred.complete();
2368 }
2369 > async.ts
2370 > emitOne(item: T): void {
2371 this._emitOneFn(item);
2372 }
2373 > async.ts
2374 > emitMany(items: T[]) {
2375 this._emitManyFn(items);
2376 }
2377 > } async.ts
2378 >
2379 > export function cancellableIterable<T>(iterableOrIterator: AsyncIterator<T> | AsyncIterable<T>, token: CancellationToken): AsyncIterableIterator<T> {
2380 const iterator = Symbol.asyncIterator in iterableOrIterator ? iterableOrIterator[Symbol.asyncIterator]() : iterableOrIterator;
2381
2395 };
2396 }
2397 > async.ts
2398 > type ProducerConsumerValue<T> = {
2399 > ok: true;
2400 > value: T;
2401 > } | {
2402 > ok: false;
2403 > error: Error;
2404 > };
2405 >
2406 > class ProducerConsumer<T> {
2407 > private readonly _unsatisfiedConsumers: DeferredPromise<T>[] = [];
2408 > private readonly _unconsumedValues: ProducerConsumerValue<T>[] = [];
2409 > private _finalValue: ProducerConsumerValue<T> | undefined;
2410 >
2411 > public get hasFinalValue(): boolean {
2412 > return !!this._finalValue;
2413 > }
2414 >
2415 > produce(value: ProducerConsumerValue<T>): void {
2416 this._ensureNoFinalValue();
2417 if (this._unsatisfiedConsumers.length > 0) {
2422 }
2423 }
2424 > async.ts
2425 > produceFinal(value: ProducerConsumerValue<T>): void {
2426 > this._ensureNoFinalValue();
2427 > this._finalValue = value;
2428 > for (const deferred of this._unsatisfiedConsumers) {
2429 this._resolveOrRejectDeferred(deferred, value);
2430 }
2431 > this._unsatisfiedConsumers.length = 0; async.ts
2432 > }
2433 >
2434 > private _ensureNoFinalValue(): void {
2435 > if (this._finalValue) {
2436 throw new BugIndicatingError('ProducerConsumer: cannot produce after final value has been set');
2437 }
2438 > } async.ts
2439 >
2440 > private _resolveOrRejectDeferred(deferred: DeferredPromise<T>, value: ProducerConsumerValue<T>): void {
2441 if (value.ok) {
2442 deferred.complete(value.value);
2445 }
2446 }
2447 > async.ts
2448 > consume(): Promise<T> {
2449 if (this._unconsumedValues.length > 0 || this._finalValue) {
2450 const value = this._unconsumedValues.length > 0 ? this._unconsumedValues.shift()! : this._finalValue!;
2460 }
2461 }
2462 > } async.ts
2463 >
2464 > /**
2465 > * Important difference to AsyncIterableObject:
2466 > * If it is iterated two times, the second iterator will not see the values emitted by the first iterator.
2467 > */
2468 > export class AsyncIterableProducer<T> implements AsyncIterable<T> {
2469 > private readonly _producerConsumer = new ProducerConsumer<IteratorResult<T>>();
2470 >
2471 > constructor(executor: AsyncIterableExecutor<T>, private readonly _onReturn?: () => void) {
2472 > queueMicrotask(async () => {
2473 > const p = executor({
2474 > emitOne: value => this._producerConsumer.produce({ ok: true, value: { done: false, value: value } }),
2475 > emitMany: values => {
2476 > for (const value of values) {
2477 this._producerConsumer.produce({ ok: true, value: { done: false, value: value } });
2478 }
2479 > }, async.ts
2480 > reject: error => this._finishError(error),
2481 > });
2482 >
2483 > if (!this._producerConsumer.hasFinalValue) {
2484 > try {
2485 > await p;
2486 > this._finishOk();
2487 > } catch (error) {
2488 this._finishError(error);
2489 }
2490 > } async.ts
2491 > });
2492 > }
2493 >
2494 > public static fromArray<T>(items: T[]): AsyncIterableProducer<T> {
2495 > return new AsyncIterableProducer<T>((writer) => {
2496 > writer.emitMany(items);
2497 > });
2498 > }
2499 >
2500 > public static fromPromise<T>(promise: Promise<T[]>): AsyncIterableProducer<T> {
2501 return new AsyncIterableProducer<T>(async (emitter) => {
2502 emitter.emitMany(await promise);
2503 });
2504 }
2505 > async.ts
2506 > public static fromPromisesResolveOrder<T>(promises: Promise<T>[]): AsyncIterableProducer<T> {
2507 return new AsyncIterableProducer<T>(async (emitter) => {
2508 await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
2509 });
2510 }
2511 > async.ts
2512 > public static merge<T>(iterables: AsyncIterable<T>[]): AsyncIterableProducer<T> {
2513 return new AsyncIterableProducer(async (emitter) => {
2514 await Promise.all(iterables.map(async (iterable) => {
2519 });
2520 }
2521 > async.ts
2522 > public static EMPTY = AsyncIterableProducer.fromArray<any>([]);
2523 >
2524 > public static map<T, R>(iterable: AsyncIterable<T>, mapFn: (item: T) => R): AsyncIterableProducer<R> {
2525 return new AsyncIterableProducer<R>(async (emitter) => {
2526 for await (const item of iterable) {
2529 });
2530 }
2531 > async.ts
2532 > public static tee<T>(iterable: AsyncIterable<T>): [AsyncIterableProducer<T>, AsyncIterableProducer<T>] {
2533 let emitter1: AsyncIterableEmitter<T> | undefined;
2534 let emitter2: AsyncIterableEmitter<T> | undefined;
2565 return [p1, p2];
2566 }
2567 > async.ts
2568 > public map<R>(mapFn: (item: T) => R): AsyncIterableProducer<R> {
2569 return AsyncIterableProducer.map(this, mapFn);
2570 }
2571 > async.ts
2572 > public static coalesce<T>(iterable: AsyncIterable<T | undefined | null>): AsyncIterableProducer<T> {
2573 return <AsyncIterableProducer<T>>AsyncIterableProducer.filter(iterable, item => !!item);
2574 }
2575 > async.ts
2576 > public coalesce(): AsyncIterableProducer<NonNullable<T>> {
2577 return AsyncIterableProducer.coalesce(this) as AsyncIterableProducer<NonNullable<T>>;
2578 }
2579 > async.ts
2580 > public static filter<T>(iterable: AsyncIterable<T>, filterFn: (item: T) => boolean): AsyncIterableProducer<T> {
2581 return new AsyncIterableProducer<T>(async (emitter) => {
2582 for await (const item of iterable) {
2587 });
2588 }
2589 > async.ts
2590 > public filter<T2 extends T>(filterFn: (item: T) => item is T2): AsyncIterableProducer<T2>;
2591 > public filter(filterFn: (item: T) => boolean): AsyncIterableProducer<T>;
2592 > public filter(filterFn: (item: T) => boolean): AsyncIterableProducer<T> {
2593 return AsyncIterableProducer.filter(this, filterFn);
2594 }
2595 > async.ts
2596 > private _finishOk(): void {
2597 > if (!this._producerConsumer.hasFinalValue) {
2598 > this._producerConsumer.produceFinal({ ok: true, value: { done: true, value: undefined } });
2599 > }
2600 > }
2601 >
2602 > private _finishError(error: Error): void {
2603 if (!this._producerConsumer.hasFinalValue) {
2604 this._producerConsumer.produceFinal({ ok: false, error: error });
2606 // Warning: this can cause to dropped errors.
2607 }
2608 > async.ts
2609 > private readonly _iterator: AsyncIterator<T, void, void> = {
2610 > next: () => this._producerConsumer.consume(),
2611 > return: () => {
2612 this._onReturn?.();
2613 return Promise.resolve({ done: true, value: undefined });
2614 },
2615 > throw: async (e) => { async.ts
2616 this._finishError(e);
2617 return { done: true, value: undefined };
2618 },
2619 > }; async.ts
2620 >
2621 > [Symbol.asyncIterator](): AsyncIterator<T, void, void> {
2622 return this._iterator;
2623 }
2624 > } async.ts
2625 >
2626 > export class CancelableAsyncIterableProducer<T> extends AsyncIterableProducer<T> {
2627 > constructor(
2628 private readonly _source: CancellationTokenSource,
2629 executor: AsyncIterableExecutor<T>
2631 super(executor);
2632 }
2633 > async.ts
2634 > cancel(): void {
2635 this._source.cancel();
2636 }
2637 > } async.ts
2638 >
2639 > //#endregion
2640 >
2641 > export const AsyncReaderEndOfStream = Symbol('AsyncReaderEndOfStream');
2642 >
2643 > export class AsyncReader<T> {
2644 > private _buffer: T[] = [];
2645 > private _atEnd = false;
2646 >
2647 > public get endOfStream(): boolean { return this._buffer.length === 0 && this._atEnd; }
2648 > private _extendBufferPromise: Promise<void> | undefined;
2649 >
2650 > constructor(
2651 private readonly _source: AsyncIterator<T>
2652 ) {
2653 }
2654 > async.ts
2655 > public async read(): Promise<T | typeof AsyncReaderEndOfStream> {
2656 if (this._buffer.length === 0 && !this._atEnd) {
2657 await this._extendBuffer();
2662 return this._buffer.shift()!;
2663 }
2664 > async.ts
2665 > public async readWhile(predicate: (value: T) => boolean, callback: (element: T) => unknown): Promise<void> {
2666 do {
2667 const piece = await this.peek();
2676 } while (true);
2677 }
2678 > async.ts
2679 > public readBufferedOrThrow(): T | typeof AsyncReaderEndOfStream {
2680 const value = this.peekBufferedOrThrow();
2681 this._buffer.shift();
2682 return value;
2683 }
2684 > async.ts
2685 > public async consumeToEnd(): Promise<void> {
2686 while (!this.endOfStream) {
2687 await this.read();
2688 }
2689 }
2690 > async.ts
2691 > public async peek(): Promise<T | typeof AsyncReaderEndOfStream> {
2692 if (this._buffer.length === 0 && !this._atEnd) {
2693 await this._extendBuffer();
2698 return this._buffer[0];
2699 }
2700 > async.ts
2701 > public peekBufferedOrThrow(): T | typeof AsyncReaderEndOfStream {
2702 if (this._buffer.length === 0) {
2703 if (this._atEnd) {
2709 return this._buffer[0];
2710 }
2711 > async.ts
2712 > public async peekTimeout(timeoutMs: number): Promise<T | typeof AsyncReaderEndOfStream | undefined> {
2713 if (this._buffer.length === 0 && !this._atEnd) {
2714 await raceTimeout(this._extendBuffer(), timeoutMs);
2722 return this._buffer[0];
2723 }
2724 > async.ts
2725 > private _extendBuffer(): Promise<void> {
2726 if (this._atEnd) {
2727 return Promise.resolve();
2742 return this._extendBufferPromise;
2743 }
2744 > } async.ts
2745 >
2746 > export function createTimeout(ms: number, cb: () => void): IDisposable {
2747 const t = setTimeout(cb, ms);
2748 return toDisposable(() => clearTimeout(t));
src/vs/platform/agentHost/common/state/protocol/common/commands.ts 1071 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- commands.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI, Snapshot } from './state.js';
10 > import type { ActionEnvelope, StateAction } from './actions.js';
11 > import type { TelemetryCapabilities } from '../channels-otlp/state.js';
12 >
13 > // ─── BaseParams ──────────────────────────────────────────────────────────────
14 >
15 > /**
16 > * Base shape every command's params extends.
17 > *
18 > * `channel` identifies the channel the command targets, mirroring the
19 > * `channel` field on every protocol notification. For commands that operate
20 > * on a specific channel (a session, terminal, or changeset), `channel` is
21 > * that channel's URI. For commands that are connection-level rather than
22 > * channel-scoped (e.g. {@link InitializeParams | `initialize`},
23 > * {@link PingParams | `ping`}, {@link ListSessionsParams | `listSessions`},
24 > * the `resource*` filesystem commands, and {@link AuthenticateParams |
25 > * `authenticate`}), the params type narrows `channel` to the literal
26 > * root URI `'ahp-root://'`.
27 > *
28 > * This invariant lets implementations route every incoming message —
29 > * request, response, or notification — by inspecting `params.channel`
30 > * without needing to know the per-method param shape.
31 > *
32 > * @category Commands
33 > */
34 > export interface BaseParams {
35 > /** Channel URI this command targets. */
36 > channel: URI;
37 > }
38 >
39 > // ─── Pagination ──────────────────────────────────────────────────────────────
40 >
41 > /**
42 > * Cursor-based pagination inputs, mixed into the params of any list command
43 > * that can page a large result set (e.g. {@link ListSessionsParams |
44 > * `listSessions`}). The paired output is {@link PaginatedResult}.
45 > *
46 > * Pagination is **opaque and cursor-based**, mirroring the shape `fetchTurns`
47 > * already uses for chat history: the server owns the ordering and keyset, and
48 > * the client walks pages by echoing the cursor from the previous
49 > * {@link PaginatedResult.nextCursor} back on the next request.
50 > *
51 > * The contract every paginated command shares:
52 > *
53 > * - To fetch the first page, omit `cursor`. Supply `limit` to bound the page.
54 > * - If the result carries a {@link PaginatedResult.nextCursor}, more entries
55 > * exist — pass it back as `cursor` to fetch the following page. A missing
56 > * `nextCursor` signals the end of the collection.
57 > * - Cursors are **server-defined and opaque**: clients MUST NOT parse, modify,
58 > * or persist them across connections. An unrecognised cursor SHOULD be
59 > * rejected with an `InvalidParams` error.
60 > * - Pagination is **fully additive**: a client that omits `limit`/`cursor` and
61 > * ignores `nextCursor` sees the pre-pagination behaviour (subject to any
62 > * server-imposed cap), and a server that does not paginate ignores the inputs
63 > * and returns everything in a single page.
64 > *
65 > * @category Commands
66 > */
67 > export interface PaginatedParams {
68 > /**
69 > * Maximum number of entries to return in this page. The server SHOULD respect
70 > * this bound but MAY return fewer entries and MAY impose its own upper cap.
71 > * Omit to let the server choose the page size.
72 > */
73 > limit?: number;
74 > /**
75 > * Opaque pagination cursor from a previous {@link PaginatedResult.nextCursor}.
76 > * Omit to fetch the first page. Cursors are server-defined and MUST be treated
77 > * as opaque — do not parse, modify, or persist them across connections. An
78 > * unrecognised cursor SHOULD be rejected with an `InvalidParams` error.
79 > */
80 > cursor?: string;
81 > }
82 >
83 > /**
84 > * Cursor-based pagination output, extended by the result of any list command
85 > * that can page a large result set (e.g. {@link ListSessionsResult |
86 > * `listSessions`}). See {@link PaginatedParams} for the full pagination
87 > * contract shared by every paginated command.
88 > *
89 > * @category Commands
90 > */
91 > export interface PaginatedResult {
92 > /**
93 > * Opaque cursor for the next page. Present when more entries exist beyond the
94 > * returned page; absent signals the end of the collection. Pass it back as
95 > * {@link PaginatedParams.cursor} to fetch the following page.
96 > */
97 > nextCursor?: string;
98 > }
99 >
100 > // ─── initialize ──────────────────────────────────────────────────────────────
101 >
102 > /**
103 > * Identifies a protocol implementation — the software (and build) on one end
104 > * of the connection, as distinct from the {@link AgentInfo | agent persona} it
105 > * hosts. Carried as {@link InitializeParams.clientInfo | `clientInfo`} on the
106 > * client side and {@link InitializeResult.serverInfo | `serverInfo`} on the
107 > * server side, mirroring LSP's `clientInfo`/`serverInfo` and MCP's
108 > * `Implementation`.
109 > *
110 > * This is **informational only**: it exists for logging, telemetry, an
111 > * about/status affordance, and — as a last resort — a known-issue workaround
112 > * for a specific buggy build. It is **not** a feature-detection mechanism.
113 > * Feature availability stays with the capability model
114 > * ({@link ClientCapabilities} and the various `*.capabilities` declarations);
115 > * implementations SHOULD NOT gate protocol behaviour on parsing
116 > * {@link Implementation.version | `version`}.
117 > *
118 > * @category Commands
119 > */
120 > export interface Implementation {
121 > /** Implementation name, e.g. a product or package identifier. */
122 > name: string;
123 > /**
124 > * Implementation version. A [SemVer](https://semver.org) string is
125 > * recommended but not required.
126 > */
127 > version?: string;
128 > /** Optional human-readable display name. */
129 > title?: string;
130 > }
131 >
132 > /**
133 > * Establishes a new connection and negotiates the protocol version.
134 > * This MUST be the first message sent by the client.
135 > *
136 > * @category Commands
137 > * @method initialize
138 > * @direction Client → Server
139 > * @messageType Request
140 > * @version 1
141 > * @see {@link /specification/lifecycle | Lifecycle} for the full handshake flow.
142 > */
143 > export interface InitializeParams extends BaseParams {
144 > channel: 'ahp-root://';
145 > /**
146 > * Protocol versions the client is willing to speak, ordered from most
147 > * preferred to least preferred. Each entry is a [SemVer](https://semver.org)
148 > * `MAJOR.MINOR.PATCH` string (e.g. `"0.1.0"`).
149 > *
150 > * The server selects one entry and returns it as `InitializeResult.protocolVersion`.
151 > * If the server cannot speak any of the offered versions, it MUST return
152 > * error code `-32005` (`UnsupportedProtocolVersion`).
153 > */
154 > protocolVersions: string[];
155 > /** Unique client identifier */
156 > clientId: string;
157 > /**
158 > * Optional identity of the client implementation (name and version).
159 > * Informational only — see {@link Implementation} for how it may and may not
160 > * be used. Distinct from {@link InitializeParams.clientId | `clientId`},
161 > * which is an opaque per-connection identifier used for reconnection, not a
162 > * human-readable implementation name.
163 > */
164 > clientInfo?: Implementation;
165 > /** URIs to subscribe to during handshake */
166 > initialSubscriptions?: URI[];
167 > /**
168 > * IETF BCP 47 language tag indicating the client's preferred locale
169 > * (e.g. `"en-US"`, `"ja"`). The server SHOULD use this to localise
170 > * user-facing strings such as confirmation option labels.
171 > */
172 > locale?: string;
173 > /**
174 > * Optional client capability declarations.
175 > *
176 > * Servers SHOULD only advertise features whose corresponding client
177 > * capability is set here. Absent means "not declared" — the server
178 > * MUST assume the client does not support the feature.
179 > */
180 > capabilities?: ClientCapabilities;
181 > }
182 >
183 > /**
184 > * Optional capabilities a client declares during `initialize`.
185 > *
186 > * Each field is a presence flag: an empty object `{}` means "supported",
187 > * absence means "not supported". Sub-fields on individual capabilities
188 > * are reserved for future per-capability options.
189 > *
190 > * @category Commands
191 > */
192 > export interface ClientCapabilities {
193 > /**
194 > * Client can render
195 > * [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) — i.e.
196 > * it can host the View sandbox, run the `ui/*` protocol against it,
197 > * and forward `mcp://`-channel traffic on the App's behalf.
198 > *
199 > * Hosts SHOULD only populate
200 > * {@link McpServerCustomization.mcpApp | `McpServerCustomization.mcpApp`}
201 > * (and expose the corresponding
202 > * {@link McpServerCustomization.channel | `mcp://` channel}) when this
203 > * capability is declared. Clients that omit it MUST treat
204 > * App-bearing tool calls as ordinary MCP tool calls.
205 > */
206 > mcpApps?: Record<string, never>;
207 > }
208 >
209 > /**
210 > * Result of the `initialize` command.
211 > *
212 > * `protocolVersion` is the version the server has selected from the client's
213 > * `protocolVersions` list. The client and server MUST use this version for
214 > * the rest of the connection. If the server cannot speak any of the offered
215 > * versions it MUST return error code `-32005` (`UnsupportedProtocolVersion`)
216 > * instead of a result.
217 > */
218 > export interface InitializeResult {
219 > /**
220 > * Protocol version selected by the server. MUST be one of the entries in
221 > * `InitializeParams.protocolVersions`. Formatted as a [SemVer](https://semver.org)
222 > * `MAJOR.MINOR.PATCH` string (e.g. `"0.1.0"`).
223 > */
224 > protocolVersion: string;
225 > /** Current server sequence number */
226 > serverSeq: number;
227 > /**
228 > * Optional identity of the server implementation (name and version).
229 > * Informational only — see {@link Implementation} for how it may and may not
230 > * be used. Whereas {@link InitializeResult.protocolVersion | `protocolVersion`}
231 > * identifies the negotiated protocol, `serverInfo` identifies the host
232 > * software behind it.
233 > */
234 > serverInfo?: Implementation;
235 > /** Snapshots for each `initialSubscriptions` URI */
236 > snapshots: Snapshot[];
237 > /** Suggested default directory for remote filesystem browsing */
238 > defaultDirectory?: URI;
239 > /**
240 > * Characters that, when typed in a {@link Message} input, SHOULD cause
241 > * the client to issue a `completions` request with
242 > * {@link CompletionItemKind.UserMessage}. Typically includes characters like
243 > * `'@'` or `'/'`.
244 > */
245 > completionTriggerCharacters?: string[];
246 > /**
247 > * Prefix that the host recognizes at the start of a user {@link Message.text}
248 > * as a shorthand for executing the remainder as a terminal command. Currently
249 > * the standardized convention is `"!"`; absence means the host does not
250 > * support command prefixes.
251 > */
252 > terminalCommandPrefix?: string;
253 > /**
254 > * OTLP telemetry channels the host emits, if any. Each populated field is
255 > * either a literal `ahp-otlp:` channel URI or an RFC 6570 URI template a
256 > * client expands before subscribing (currently only the `logs` channel
257 > * defines a template variable, `{level}`, for subscriber-side severity
258 > * filtering). Clients MAY ignore signals they cannot process.
259 > *
260 > * @see {@link /specification/telemetry-channel | Telemetry Channel}
261 > */
262 > telemetry?: TelemetryCapabilities;
263 > }
264 >
265 > // ─── ping ────────────────────────────────────────────────────────────────────
266 >
267 > /**
268 > * Verifies that the AHP connection is still alive and keeps it from being
269 > * closed by idle-timeout intermediaries (proxies, load balancers, etc.).
270 > *
271 > * The server MUST respond regardless of whether the client has completed
272 > * `initialize` or holds any subscriptions. Ping carries no payload in either
273 > * direction; the response itself is the signal.
274 > *
275 > * @category Commands
276 > * @method ping
277 > * @direction Client → Server
278 > * @messageType Request
279 > * @version 1
280 > */
281 > export interface PingParams extends BaseParams {
282 > channel: 'ahp-root://';
283 > }
284 >
285 > // ─── reconnect ───────────────────────────────────────────────────────────────
286 >
287 > /**
288 > * Discriminant for reconnect result types.
289 > *
290 > * @category Commands
291 > */
292 > export const enum ReconnectResultType {
293 > Replay = 'replay',
294 > Snapshot = 'snapshot',
295 > }
296 >
297 > /**
298 > * Re-establishes a dropped connection. The server replays missed actions or
299 > * provides fresh snapshots.
300 > *
301 > * @category Commands
302 > * @method reconnect
303 > * @direction Client → Server
304 > * @messageType Request
305 > * @version 1
306 > * @see {@link /specification/lifecycle | Lifecycle} for details.
307 > */
308 > export interface ReconnectParams extends BaseParams {
309 > channel: 'ahp-root://';
310 > /** Client identifier from the original connection */
311 > clientId: string;
312 > /** Last `serverSeq` the client received */
313 > lastSeenServerSeq: number;
314 > /** URIs the client was subscribed to */
315 > subscriptions: URI[];
316 > }
317 >
318 > /**
319 > * Reconnect result when the server can replay from the requested sequence.
320 > *
321 > * The server MUST include all replayed data in the response.
322 > */
323 > export interface ReconnectReplayResult {
324 > /** Discriminant */
325 > type: ReconnectResultType.Replay;
326 > /** Missed action envelopes since `lastSeenServerSeq` */
327 > actions: ActionEnvelope[];
328 > /**
329 > * URIs from `ReconnectParams.subscriptions` that the server cannot resume.
330 > * This includes resources that no longer exist (e.g. disposed sessions or
331 > * terminals) as well as resources the client is no longer permitted to
332 > * observe. Clients SHOULD drop these from their local subscription set.
333 > */
334 > missing: URI[];
335 > }
336 >
337 > /**
338 > * Reconnect result when the gap exceeds the replay buffer.
339 > */
340 > export interface ReconnectSnapshotResult {
341 > /** Discriminant */
342 > type: ReconnectResultType.Snapshot;
343 > /** Fresh snapshots for each subscription */
344 > snapshots: Snapshot[];
345 > }
346 >
347 > /** Result of the `reconnect` command. */
348 > export type ReconnectResult = ReconnectReplayResult | ReconnectSnapshotResult;
349 >
350 > // ─── subscribe ───────────────────────────────────────────────────────────────
351 >
352 > /**
353 > * Subscribe to a URI-identified channel.
354 > *
355 > * A channel MAY have state associated with it (e.g. root, sessions,
356 > * terminals) or be stateless (pure pub/sub for streaming data). For
357 > * state-bearing channels the result includes a snapshot; for stateless
358 > * channels `snapshot` is omitted.
359 > *
360 > * @category Commands
361 > * @method subscribe
362 > * @direction Client → Server
363 > * @messageType Request
364 > * @version 1
365 > * @see {@link /specification/subscriptions | Subscriptions}
366 > */
367 > export interface SubscribeParams extends BaseParams {
368 > /**
369 > * Optional delivery preferences for this subscription.
370 > *
371 > * Servers MAY use these preferences to buffer and coalesce high-frequency
372 > * updates while preserving the same reduced state. Omit this field for the
373 > * server's default delivery behavior.
374 > */
375 > delivery?: SubscriptionDeliveryOptions;
376 > /**
377 > * Optional client-requested shape for the returned snapshot.
378 > *
379 > * Servers that do not understand a requested view ignore it and return their
380 > * default snapshot. Clients MUST tolerate receiving more state than requested.
381 > */
382 > view?: SubscribeView;
383 > }
384 >
385 > /**
386 > * Optional client-requested shape for a subscription snapshot.
387 > *
388 > * @category Commands
389 > */
390 > export interface SubscribeView {
391 > /**
392 > * Advisory number of most-recent completed turns to expose in a chat
393 > * snapshot.
394 > *
395 > * Servers MAY return more or fewer turns than requested. When omitted, the
396 > * host MUST return all retained turns. When older turns remain available, the
397 > * returned {@link ChatState} carries `turnsNextCursor`; clients pass that
398 > * cursor to `fetchTurns` to ask the host to page more turns into the chat
399 > * state.
400 > */
401 > turns?: number;
402 > }
403 >
404 > /**
405 > * Advisory delivery preferences for a single subscription.
406 > *
407 > * @category Commands
408 > */
409 > export interface SubscriptionDeliveryOptions {
410 > /**
411 > * Maximum time, in milliseconds, that the server may intentionally delay
412 > * delivery while buffering/coalescing updates for this subscription.
413 > *
414 > * A value of `0` requests immediate delivery with no intentional coalescing.
415 > */
416 > maxLatencyMs?: number;
417 > }
418 >
419 > /**
420 > * Result of the `subscribe` command.
421 > *
422 > * `snapshot` is present when the subscribed channel has associated state, and
423 > * absent for stateless channels.
424 > */
425 > export interface SubscribeResult {
426 > /** Snapshot of the subscribed channel's state (omitted for stateless channels) */
427 > snapshot?: Snapshot;
428 > }
429 >
430 > // ─── unsubscribe ─────────────────────────────────────────────────────────────
431 >
432 > /**
433 > * Stop receiving updates for a channel.
434 > *
435 > * @category Commands
436 > * @method unsubscribe
437 > * @direction Client → Server
438 > * @messageType Notification
439 > * @version 1
440 > * @see {@link /specification/subscriptions | Subscriptions}
441 > */
442 > export interface UnsubscribeParams {
443 > /** Channel URI to unsubscribe from */
444 > channel: URI;
445 > }
446 >
447 > // ─── dispatchAction ──────────────────────────────────────────────────────────
448 >
449 > /**
450 > * Fire-and-forget action dispatch (write-ahead). The client applies actions
451 > * optimistically to local state and the server echoes them back as an
452 > * {@link ActionEnvelope} once accepted.
453 > *
454 > * The client → server method is named `dispatchAction`; the server's reply
455 > * arrives on the server → client `action` notification (params:
456 > * {@link ActionEnvelope}).
457 > *
458 > * @category Commands
459 > * @method dispatchAction
460 > * @direction Client → Server
461 > * @messageType Notification
462 > * @version 1
463 > * @see {@link /guide/actions | Actions} for the full list of client-dispatchable actions.
464 > */
465 > export interface DispatchActionParams {
466 > /** Channel URI this action targets */
467 > channel: URI;
468 > /** Client sequence number */
469 > clientSeq: number;
470 > /** The action to dispatch */
471 > action: StateAction;
472 > }
473 >
474 > // ─── resourceRead ────────────────────────────────────────────────────────
475 >
476 > /**
477 > * Encoding of fetched content data.
478 > *
479 > * @category Commands
480 > */
481 > export const enum ContentEncoding {
482 > Base64 = 'base64',
483 > Utf8 = 'utf-8',
484 > }
485 >
486 > /**
487 > * Reads the content of a resource by URI.
488 > *
489 > * Content references keep the state tree small by storing large data (images,
490 > * long tool outputs) by reference rather than inline.
491 > *
492 > * Binary content (images, etc.) MUST use `base64` encoding. Text content MAY
493 > * use `utf-8` encoding.
494 > *
495 > * Like all `resource*` methods, `resourceRead` is symmetrical and MAY be
496 > * sent in either direction. Hosts use it to fetch content from a
497 > * client-published URI (e.g. `virtual://my-client/...` plugins); clients
498 > * use it to read host-side files. The receiver enforces access via the
499 > * same permission/`resourceRequest` flow regardless of which peer initiated.
500 > *
501 > * @category Commands
502 > * @method resourceRead
503 > * @direction Client ↔ Server
504 > * @messageType Request
505 > * @version 1
506 > * @throws `NotFound` (`-32008`) if the URI does not exist.
507 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to read the URI.
508 > * @example
509 > * ```jsonc
510 > * // Client → Server
511 > * { "jsonrpc": "2.0", "id": 10, "method": "resourceRead",
512 > * "params": { "uri": "ahp-session:/<uuid>/content/img-1" } }
513 > *
514 > * // Server → Client
515 > * { "jsonrpc": "2.0", "id": 10, "result": {
516 > * "data": "iVBORw0KGgo...",
517 > * "encoding": "base64",
518 > * "contentType": "image/png"
519 > * }}
520 > * ```
521 > */
522 > export interface ResourceReadParams extends BaseParams {
523 > channel: 'ahp-root://';
524 > /** Content URI from a `ContentRef` */
525 > uri: string;
526 > /** Preferred encoding for the returned data (default: server-chosen) */
527 > encoding?: ContentEncoding;
528 > }
529 >
530 > /**
531 > * Result of the `resourceRead` command.
532 > *
533 > * The server SHOULD honor the `encoding` requested in the params. If the
534 > * server cannot provide the requested encoding, it MUST fall back to either
535 > * `base64` or `utf-8`.
536 > */
537 > export interface ResourceReadResult {
538 > /** Content encoded as a string */
539 > data: string;
540 > /** How `data` is encoded */
541 > encoding: ContentEncoding;
542 > /** Content type (e.g. `"image/png"`, `"text/plain"`) */
543 > contentType?: string;
544 > }
545 >
546 > // ─── resourceWrite ───────────────────────────────────────────────────────────
547 >
548 > /**
549 > * How {@link ResourceWriteParams.data} is placed within the target file.
550 > *
551 > * Each mode interprets {@link ResourceWriteParams.position} differently:
552 > *
553 > * - `truncate` (default): rooted at the **start** of the file. The file is
554 > * truncated at `position` (0 by default) and `data` is written from that
555 > * offset, so the resulting file is `existing[0..position] + data`. With
556 > * `position` omitted this is a full overwrite.
557 > * - `append`: rooted at the **end** of the file. `position` counts bytes
558 > * backwards from EOF, so `position: 0` (the default) writes at EOF —
559 > * POSIX append — and `position: 5` inserts `data` 5 bytes before the
560 > * current EOF, shifting those trailing 5 bytes after the inserted region.
561 > * The server MUST evaluate the effective EOF and write atomically with
562 > * respect to other appenders so concurrent `append` writes do not
563 > * clobber each other.
564 > * - `insert`: rooted at the **start** of the file. `position` (0 by default)
565 > * is the byte offset at which `data` is spliced in; bytes at or after
566 > * `position` are shifted right by `data.length`. `insert` always grows
567 > * the file — use `truncate` to overwrite bytes in place.
568 > *
569 > * @category Commands
570 > */
571 > export const enum ResourceWriteMode {
572 > Truncate = 'truncate',
573 > Append = 'append',
574 > Insert = 'insert',
575 > }
576 >
577 > /**
578 > * Writes content to a file on the server's filesystem.
579 > *
580 > * Binary content (images, etc.) MUST use `base64` encoding. Text content MAY
581 > * use `utf-8` encoding.
582 > *
583 > * If the file does not exist, it is created. If the file already exists, the
584 > * effect on existing bytes depends on {@link ResourceWriteParams.mode}:
585 > * `truncate` (default) overwrites from the chosen offset onward, `append`
586 > * preserves all existing bytes and adds `data` at a position rooted at EOF,
587 > * and `insert` preserves all existing bytes and splices `data` in at an
588 > * offset rooted at the start of the file.
589 > *
590 > * Like all `resource*` methods, `resourceWrite` is symmetrical and MAY be
591 > * sent in either direction.
592 > *
593 > * @category Commands
594 > * @method resourceWrite
595 > * @direction Client ↔ Server
596 > * @messageType Request
597 > * @version 1
598 > * @throws `NotFound` (`-32008`) if the parent directory does not exist.
599 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to write to the path.
600 > * @throws `AlreadyExists` (`-32010`) if `createOnly` is set and the file already exists.
601 > * @throws `Conflict` (`-32011`) if `ifMatch` is set and the current `etag` does not match.
602 > * @example
603 > * ```jsonc
604 > * // Client → Server
605 > * { "jsonrpc": "2.0", "id": 11, "method": "resourceWrite",
606 > * "params": { "uri": "file:///workspace/hello.txt", "data": "SGVsbG8=",
607 > * "encoding": "base64", "contentType": "text/plain" } }
608 > *
609 > * // Server → Client
610 > * { "jsonrpc": "2.0", "id": 11, "result": {} }
611 > * ```
612 > */
613 > export interface ResourceWriteParams extends BaseParams {
614 > channel: 'ahp-root://';
615 > /** Target file URI on the server filesystem */
616 > uri: URI;
617 > /** Content encoded as a string */
618 > data: string;
619 > /** How `data` is encoded */
620 > encoding: ContentEncoding;
621 > /** Content type (e.g. `"text/plain"`, `"image/png"`) */
622 > contentType?: string;
623 > /**
624 > * If `true`, the server MUST fail if the file already exists instead of
625 > * overwriting it. Useful for safe creation of new files.
626 > */
627 > createOnly?: boolean;
628 > /**
629 > * How `data` is placed within the target file. Defaults to `'truncate'`
630 > * (full overwrite) when omitted. See {@link ResourceWriteMode} for the
631 > * meaning of each mode and how it interprets {@link position}.
632 > */
633 > mode?: ResourceWriteMode;
634 > /**
635 > * Byte offset interpreted according to {@link mode}. Defaults to `0`.
636 > * - `truncate`: offset from the start of the file at which to truncate
637 > * before writing.
638 > * - `append`: bytes back from EOF at which to insert `data`.
639 > * - `insert`: offset from the start of the file at which to splice in
640 > * `data`.
641 > */
642 > position?: number;
643 > /**
644 > * Optimistic-concurrency token previously returned by
645 > * {@link ResourceResolveResult.etag}. When set, the server MUST fail with
646 > * `Conflict` if the current `etag` does not match — preventing lost
647 > * updates between a `resourceResolve` and a subsequent `resourceWrite`.
648 > */
649 > ifMatch?: string;
650 > }
651 >
652 > /**
653 > * Result of the `resourceWrite` command.
654 > *
655 > * An empty object on success.
656 > */
657 > export interface ResourceWriteResult {
658 > }
659 >
660 > // ─── resourceList ────────────────────────────────────────────────────────
661 >
662 > /**
663 > * Lists directory entries at a file URI on the server's filesystem.
664 > *
665 > * This is intended for remote folder pickers and similar UI that needs to let
666 > * users navigate the server's local filesystem.
667 > *
668 > * The server MUST return success only if the target exists and is a directory.
669 > * If the target does not exist, is not a directory, or cannot be accessed, the
670 > * server MUST return a JSON-RPC error.
671 > *
672 > * Like all `resource*` methods, `resourceList` is symmetrical and MAY be
673 > * sent in either direction.
674 > *
675 > * @category Commands
676 > * @method resourceList
677 > * @direction Client ↔ Server
678 > * @messageType Request
679 > * @version 1
680 > * @throws `NotFound` (`-32008`) if the directory does not exist.
681 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to browse the directory.
682 > */
683 > export interface ResourceListParams extends BaseParams {
684 > channel: 'ahp-root://';
685 > /** Directory URI on the server filesystem */
686 > uri: URI;
687 > }
688 >
689 > /**
690 > * Directory entry returned by `resourceList`.
691 > */
692 > export interface DirectoryEntry {
693 > /** Base name of the entry */
694 > name: string;
695 > /** Whether the entry is a file or directory */
696 > type: 'file' | 'directory';
697 > }
698 >
699 > /**
700 > * Result of the `resourceList` command.
701 > */
702 > export interface ResourceListResult {
703 > /** Entries directly contained in the requested directory */
704 > entries: DirectoryEntry[];
705 > }
706 >
707 > // ─── resourceCopy ────────────────────────────────────────────────────────────
708 >
709 > /**
710 > * Copies a resource from one URI to another on the server's filesystem.
711 > *
712 > * If the destination already exists, it is overwritten unless `failIfExists`
713 > * is set.
714 > *
715 > * Like all `resource*` methods, `resourceCopy` is symmetrical and MAY be
716 > * sent in either direction.
717 > *
718 > * @category Commands
719 > * @method resourceCopy
720 > * @direction Client ↔ Server
721 > * @messageType Request
722 > * @version 1
723 > * @throws `NotFound` (`-32008`) if the source does not exist.
724 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to read the source or write to the destination.
725 > * @throws `AlreadyExists` (`-32010`) if `failIfExists` is set and the destination already exists.
726 > */
727 > export interface ResourceCopyParams extends BaseParams {
728 > channel: 'ahp-root://';
729 > /** Source URI to copy from */
730 > source: URI;
731 > /** Destination URI to copy to */
732 > destination: URI;
733 > /**
734 > * If `true`, the server MUST fail if the destination already exists instead
735 > * of overwriting it.
736 > */
737 > failIfExists?: boolean;
738 > }
739 >
740 > /**
741 > * Result of the `resourceCopy` command.
742 > *
743 > * An empty object on success.
744 > */
745 > export interface ResourceCopyResult {
746 > }
747 >
748 > // ─── resourceDelete ──────────────────────────────────────────────────────────
749 >
750 > /**
751 > * Deletes a resource at a URI on the server's filesystem.
752 > *
753 > * Like all `resource*` methods, `resourceDelete` is symmetrical and MAY be
754 > * sent in either direction.
755 > *
756 > * @category Commands
757 > * @method resourceDelete
758 > * @direction Client ↔ Server
759 > * @messageType Request
760 > * @version 1
761 > * @throws `NotFound` (`-32008`) if the resource does not exist.
762 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to delete the resource.
763 > */
764 > export interface ResourceDeleteParams extends BaseParams {
765 > channel: 'ahp-root://';
766 > /** URI of the resource to delete */
767 > uri: URI;
768 > /**
769 > * If `true` and the target is a directory, delete it and all its contents
770 > * recursively. If `false` (default), deleting a non-empty directory MUST fail.
771 > */
772 > recursive?: boolean;
773 > }
774 >
775 > /**
776 > * Result of the `resourceDelete` command.
777 > *
778 > * An empty object on success.
779 > */
780 > export interface ResourceDeleteResult {
781 > }
782 >
783 > // ─── resourceRequest ─────────────────────────────────────────────────────────
784 >
785 > /**
786 > * Requests permission to access a resource on the receiver's filesystem.
787 > *
788 > * `resourceRequest` is symmetrical and MAY be sent in either direction: a
789 > * client asks the server to grant access to a server-side resource, or a
790 > * server asks the client to grant access to a client-side resource. The
791 > * receiver decides whether to allow, deny, or prompt the user for the
792 > * requested access.
793 > *
794 > * If the receiver denies access, it MUST respond with `PermissionDenied`
795 > * (-32009). The error data MAY include a `ResourceRequestParams` value
796 > * describing the access the caller would need to be granted for the
797 > * operation to succeed; see `PermissionDeniedErrorData` in
798 > * `types/errors.ts`.
799 > *
800 > * After a successful `resourceRequest`, the caller MAY use the corresponding
801 > * `resource*` commands (e.g. `resourceRead`, `resourceWrite`) to perform the
802 > * operation. Receivers MAY rescind access at any time by returning
803 > * `PermissionDenied` on subsequent operations.
804 > *
805 > * Either `read`, `write`, or both SHOULD be set to `true`. A request with
806 > * neither flag set is treated as `read: true` by receivers.
807 > *
808 > * @category Commands
809 > * @method resourceRequest
810 > * @direction Client ↔ Server
811 > * @messageType Request
812 > * @version 1
813 > * @throws `PermissionDenied` (`-32009`) if access is denied.
814 > */
815 > export interface ResourceRequestParams extends BaseParams {
816 > channel: 'ahp-root://';
817 > /**
818 > * Resource URI being requested. Typically a `file:` URI on the receiver's
819 > * filesystem, but any URI scheme that the receiver mediates access to is
820 > * allowed.
821 > */
822 > uri: URI;
823 > /** Whether the caller needs read access to the resource. */
824 > read?: boolean;
825 > /** Whether the caller needs write access to the resource. */
826 > write?: boolean;
827 > }
828 >
829 > /**
830 > * Result of the `resourceRequest` command.
831 > *
832 > * An empty object on success.
833 > */
834 > export interface ResourceRequestResult {
835 > }
836 >
837 > // ─── resourceMove ────────────────────────────────────────────────────────────
838 >
839 > /**
840 > * Moves (renames) a resource from one URI to another on the server's filesystem.
841 > *
842 > * If the destination already exists, it is overwritten unless `failIfExists`
843 > * is set.
844 > *
845 > * Like all `resource*` methods, `resourceMove` is symmetrical and MAY be
846 > * sent in either direction.
847 > *
848 > * @category Commands
849 > * @method resourceMove
850 > * @direction Client ↔ Server
851 > * @messageType Request
852 > * @version 1
853 > * @throws `NotFound` (`-32008`) if the source does not exist.
854 > * @throws `PermissionDenied` (`-32009`) if the client is not permitted to move the resource.
855 > * @throws `AlreadyExists` (`-32010`) if `failIfExists` is set and the destination already exists.
856 > */
857 > export interface ResourceMoveParams extends BaseParams {
858 > channel: 'ahp-root://';
859 > /** Source URI to move from */
860 > source: URI;
861 > /** Destination URI to move to */
862 > destination: URI;
863 > /**
864 > * If `true`, the server MUST fail if the destination already exists instead
865 > * of overwriting it.
866 > */
867 > failIfExists?: boolean;
868 > }
869 >
870 > /**
871 > * Result of the `resourceMove` command.
872 > *
873 > * An empty object on success.
874 > */
875 > export interface ResourceMoveResult {
876 > }
877 >
878 > // ─── resourceResolve ─────────────────────────────────────────────────────────
879 >
880 > /**
881 > * Discriminant for {@link ResourceResolveResult.type}.
882 > *
883 > * @category Commands
884 > */
885 > export const enum ResourceType {
886 > File = 'file',
887 > Directory = 'directory',
888 > Symlink = 'symlink',
889 > }
890 >
891 > /**
892 > * Resolves a resource — the combination of POSIX `stat` and `realpath`.
893 > *
894 > * `resourceResolve` returns metadata about the resource together with its
895 > * canonical URI after symlink resolution. Use this in place of any
896 > * `resourceExists` shim: a missing resource MUST surface as a `NotFound`
897 > * JSON-RPC error rather than a success with a sentinel value. Callers that
898 > * truly need a boolean check should attempt `resourceResolve` and treat
899 > * `NotFound` as "does not exist".
900 > *
901 > * Like all `resource*` methods, `resourceResolve` is symmetrical and MAY be
902 > * sent in either direction.
903 > *
904 > * @category Commands
905 > * @method resourceResolve
906 > * @direction Client ↔ Server
907 > * @messageType Request
908 > * @version 1
909 > * @throws `NotFound` (`-32008`) if the resource does not exist.
910 > * @throws `PermissionDenied` (`-32009`) if the caller is not permitted to stat the URI.
911 > * @example
912 > * ```jsonc
913 > * // Client → Server
914 > * { "jsonrpc": "2.0", "id": 20, "method": "resourceResolve",
915 > * "params": { "channel": "ahp-root://", "uri": "file:///workspace/hello.txt" } }
916 > *
917 > * // Server → Client
918 > * { "jsonrpc": "2.0", "id": 20, "result": {
919 > * "uri": "file:///workspace/hello.txt",
920 > * "type": "file",
921 > * "size": 5,
922 > * "mtime": "2026-01-15T12:34:56.789Z",
923 > * "etag": "W/\"5-abc123\""
924 > * }}
925 > * ```
926 > */
927 > export interface ResourceResolveParams extends BaseParams {
928 > channel: 'ahp-root://';
929 > /** URI to resolve */
930 > uri: URI;
931 > /**
932 > * When `true` (default), follow symlinks and report the metadata of the
933 > * link target — and set `uri` in the result to the canonical (realpath)
934 > * URI. When `false`, stat the link itself (lstat semantics) and report
935 > * `type: 'symlink'`.
936 > */
937 > followSymlinks?: boolean;
938 > }
939 >
940 > /**
941 > * Result of the `resourceResolve` command.
942 > */
943 > export interface ResourceResolveResult {
944 > /**
945 > * Canonical URI after symlink resolution. Equal to the requested URI when
946 > * `followSymlinks` is `false` or the URI does not traverse a symlink.
947 > */
948 > uri: URI;
949 > /** Resource kind. */
950 > type: ResourceType;
951 > /**
952 > * Size in bytes. Omitted for directories when the provider cannot
953 > * cheaply compute it.
954 > */
955 > size?: number;
956 > /** Last-modified time in ISO 8601 format, when known. */
957 > mtime?: string;
958 > /** Creation time in ISO 8601 format, when known. */
959 > ctime?: string;
960 > /** Sniffed MIME type, when known (e.g. `"text/plain"`, `"image/png"`). */
961 > contentType?: string;
962 > /**
963 > * Opaque per-provider version token. When present, pass it as
964 > * {@link ResourceWriteParams.ifMatch} on a subsequent `resourceWrite` to
965 > * detect concurrent modifications.
966 > */
967 > etag?: string;
968 > }
969 >
970 > // ─── resourceMkdir ───────────────────────────────────────────────────────────
971 >
972 > /**
973 > * Creates a directory on the server's filesystem with `mkdir -p` semantics.
974 > *
975 > * The server MUST create any missing parent directories. Creating a
976 > * directory that already exists is a no-op success. If `uri` already
977 > * exists but is **not** a directory, the server MUST fail with
978 > * `AlreadyExists`.
979 > *
980 > * Like all `resource*` methods, `resourceMkdir` is symmetrical and MAY be
981 > * sent in either direction.
982 > *
983 > * @category Commands
984 > * @method resourceMkdir
985 > * @direction Client ↔ Server
986 > * @messageType Request
987 > * @version 1
988 > * @throws `PermissionDenied` (`-32009`) if the caller is not permitted to create the directory.
989 > * @throws `AlreadyExists` (`-32010`) if `uri` already exists as a non-directory.
990 > */
991 > export interface ResourceMkdirParams extends BaseParams {
992 > channel: 'ahp-root://';
993 > /** Directory URI to create (parents created as needed). */
994 > uri: URI;
995 > }
996 >
997 > /**
998 > * Result of the `resourceMkdir` command.
999 > *
1000 > * An empty object on success.
1001 > */
1002 > export interface ResourceMkdirResult {
1003 > }
1004 >
1005 > // ─── authenticate ────────────────────────────────────────────────────────────
1006 >
1007 > /**
1008 > * Pushes a ****** for a protected resource. The `resource` field MUST
1009 > * match a protected-resource identifier the client has discovered from the
1010 > * server — whether declared statically in `AgentInfo.protectedResources`,
1011 > * or discovered dynamically from a live `McpServerAuthRequiredState.resource`
1012 > * or `ToolCallAuthRequiredState.auth.resource` (both surfaced only once the
1013 > * corresponding MCP server or tool call actually challenges for auth).
1014 > * Servers MUST accept any `resource` value they have themselves advertised
1015 > * through one of these three mechanisms.
1016 > *
1017 > * Tokens are delivered using [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)
1018 > * (****** Usage) semantics. The client obtains the token from the
1019 > * authorization server(s) listed in the resource's metadata and pushes it
1020 > * to the server via this command.
1021 > *
1022 > * @category Commands
1023 > * @method authenticate
1024 > * @direction Client → Server
1025 > * @messageType Request
1026 > * @version 1
1027 > * @see {@link /specification/authentication | Authentication}
1028 > * @example
1029 > * ```jsonc
1030 > * // Client → Server
1031 > * { "jsonrpc": "2.0", "id": 3, "method": "authenticate",
1032 > * "params": { "channel": "ahp-root://", "resource": "https://api.github.com", "token": "gho_xxxx" } }
1033 > *
1034 > * // Server → Client (success)
1035 > * { "jsonrpc": "2.0", "id": 3, "result": {} }
1036 > *
1037 > * // Server → Client (failure — invalid token)
1038 > * { "jsonrpc": "2.0", "id": 3, "error": { "code": -32007, "message": "Invalid token" } }
1039 > * ```
1040 > */
1041 > export interface AuthenticateParams extends BaseParams {
1042 > channel: 'ahp-root://';
1043 > /**
1044 > * The protected resource identifier. MUST match a `resource` value the
1045 > * server has advertised — via `ProtectedResourceMetadata` in
1046 > * `AgentInfo.protectedResources`, or via a live
1047 > * `McpServerAuthRequiredState.resource` / `ToolCallAuthRequiredState.auth.resource`.
1048 > */
1049 > resource: string;
1050 > /** ****** obtained from the resource's authorization server */
1051 > token: string;
1052 > /**
1053 > * OAuth scopes the token grants, when known. Lets the server determine
1054 > * whether a specific challenge — e.g. the `requiredScopes` on a live
1055 > * `McpServerAuthRequiredState` or `ToolCallAuthRequiredState.auth` — is
1056 > * satisfied without decoding the (opaque, server-specific) token itself.
1057 > * Omit when the client doesn't track granted scopes separately from the
1058 > * token.
1059 > */
1060 > scopes?: string[];
1061 > }
1062 >
1063 > /**
1064 > * Result of the `authenticate` command.
1065 > *
1066 > * An empty object on success. If the token is invalid or the resource is
1067 > * unrecognized, the server MUST return a JSON-RPC error (e.g. `AuthRequired`
1068 > * `-32007` or `InvalidParams` `-32602`).
1069 > */
1070 > export interface AuthenticateResult {
1071 > }
src/vs/base/common/event.ts 936 covered LOC · 128 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- event.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancelablePromise } from './async.js';
7 > import { CancellationToken } from './cancellation.js';
8 > import { diffSets } from './collections.js';
9 > import { onUnexpectedError } from './errors.js';
10 > import { createSingleCallFunction } from './functional.js';
11 > import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from './lifecycle.js';
12 > import { LinkedList } from './linkedList.js';
13 > import { IObservable, IObservableWithChange, IObserver } from './observable.js';
14 > import { env } from './process.js';
15 > import { StopWatch } from './stopwatch.js';
16 > import { MicrotaskDelay } from './symbols.js';
17 >
18 >
19 > // -----------------------------------------------------------------------------------------------------------------------
20 > // Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell.
21 > // -----------------------------------------------------------------------------------------------------------------------
22 > const _enableDisposeWithListenerWarning = false
23 > // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed
24 > ;
25 >
26 >
27 > // -----------------------------------------------------------------------------------------------------------------------
28 > // Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup.
29 > // See https://github.com/microsoft/vscode/issues/142851
30 > // -----------------------------------------------------------------------------------------------------------------------
31 > const _enableSnapshotPotentialLeakWarning = false
32 > // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed
33 > ;
34 >
35 >
36 > const _bufferLeakWarnCountThreshold = 100;
37 > const _bufferLeakWarnTimeThreshold = 60_000; // 1 minute
38 >
39 function _isBufferLeakWarningEnabled(): boolean {
40 return !!env['VSCODE_DEV'];
41 }
42 > event.ts
43 > /**
44 > * An event with zero or one parameters that can be subscribed to. The event is a function itself.
45 > */
46 > export interface Event<T> {
47 > (listener: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;
48 > }
49 >
50 > export namespace Event {
51 > export const None: Event<any> = () => Disposable.None;
52 >
53 > function _addLeakageTraceLogic(options: EmitterOptions) {
54 if (_enableSnapshotPotentialLeakWarning) {
55 const { onDidAddListener: origListenerDidAdd } = options;
65 }
66 }
67 > event.ts
68 > /**
69 > * Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared
70 > * `setTimeout`. The event is converted into a signal (`Event<void>`) to avoid additional object creation as a
71 > * result of merging events and to try prevent race conditions that could arise when using related deferred and
72 > * non-deferred events.
73 > *
74 > * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work
75 > * (eg. latency of keypress to text rendered).
76 > *
77 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
78 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
79 > * returned event causes this utility to leak a listener on the original event.
80 > *
81 > * @param event The event source for the new event.
82 > * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not
83 > * specified, some events could go missing. Use this if it's important that all events are processed, even if the
84 > * listener gets disposed before the debounced event fires.
85 > * @param disposable A disposable store to add the new EventEmitter to.
86 > */
87 > export function defer(event: Event<unknown>, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event<void> {
88 return debounce<unknown, void>(event, () => void 0, 0, undefined, flushOnListenerRemove ?? true, undefined, disposable);
89 }
90 > event.ts
91 > /**
92 > * Given an event, returns another event which only fires once.
93 > *
94 > * @param event The event source for the new event.
95 > */
96 > export function once<T>(event: Event<T>): Event<T> {
97 return (listener, thisArgs = null, disposables?) => {
98 // we need this, in case the event fires during the listener call
118 };
119 }
120 > event.ts
121 > /**
122 > * Given an event, returns another event which only fires once, and only when the condition is met.
123 > *
124 > * @param event The event source for the new event.
125 > */
126 > export function onceIf<T>(event: Event<T>, condition: (e: T) => boolean): Event<T> {
127 return Event.once(Event.filter(event, condition));
128 }
129 > event.ts
130 > /**
131 > * Maps an event of one type into an event of another type using a mapping function, similar to how
132 > * `Array.prototype.map` works.
133 > *
134 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
135 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
136 > * returned event causes this utility to leak a listener on the original event.
137 > *
138 > * @param event The event source for the new event.
139 > * @param map The mapping function.
140 > * @param disposable A disposable store to add the new EventEmitter to.
141 > */
142 > export function map<I, O>(event: Event<I>, map: (i: I) => O, disposable?: DisposableStore): Event<O> {
143 return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable);
144 }
145 > event.ts
146 > /**
147 > * Wraps an event in another event that performs some function on the event object before firing.
148 > *
149 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
150 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
151 > * returned event causes this utility to leak a listener on the original event.
152 > *
153 > * @param event The event source for the new event.
154 > * @param each The function to perform on the event object.
155 > * @param disposable A disposable store to add the new EventEmitter to.
156 > */
157 > export function forEach<I>(event: Event<I>, each: (i: I) => void, disposable?: DisposableStore): Event<I> {
158 return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable);
159 }
160 > event.ts
161 > /**
162 > * Wraps an event in another event that fires only when some condition is met.
163 > *
164 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
165 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
166 > * returned event causes this utility to leak a listener on the original event.
167 > *
168 > * @param event The event source for the new event.
169 > * @param filter The filter function that defines the condition. The event will fire for the object if this function
170 > * returns true.
171 > * @param disposable A disposable store to add the new EventEmitter to.
172 > */
173 > export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event<T>;
174 > export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T>;
175 > export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R, disposable?: DisposableStore): Event<R>;
176 > export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T> {
177 return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable);
178 }
179 > event.ts
180 > /**
181 > * Given an event, returns the same event but typed as `Event<void>`.
182 > */
183 > export function signal<T>(event: Event<T>): Event<void> {
184 return event as Event<any> as Event<void>;
185 }
186 > event.ts
187 > /**
188 > * Given a collection of events, returns a single event which emits whenever any of the provided events emit.
189 > */
190 > export function any<T>(...events: Event<T>[]): Event<T>;
191 > export function any(...events: Event<any>[]): Event<void>;
192 > export function any<T>(...events: Event<T>[]): Event<T> {
193 return (listener, thisArgs = null, disposables?) => {
194 const disposable = combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e))));
196 };
197 }
198 > event.ts
199 > /**
200 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
201 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
202 > * returned event causes this utility to leak a listener on the original event.
203 > */
204 > export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O, disposable?: DisposableStore): Event<O> {
205 let output: O | undefined = initial;
206
210 }, disposable);
211 }
212 > event.ts
213 > function snapshot<T>(event: Event<T>, disposable: DisposableStore | undefined): Event<T> {
214 let listener: IDisposable | undefined;
215
233 return emitter.event;
234 }
235 > event.ts
236 > /**
237 > * Adds the IDisposable to the store if it's set, and returns it. Useful to
238 > * Event function implementation.
239 > */
240 > function addAndReturnDisposable<T extends IDisposable>(d: T, store: DisposableStore | IDisposable[] | undefined): T {
241 if (store instanceof Array) {
242 store.push(d);
246 return d;
247 }
248 > event.ts
249 > /**
250 > * Given an event, creates a new emitter that event that will debounce events based on {@link delay} and give an
251 > * array event object of all events that fired.
252 > *
253 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
254 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
255 > * returned event causes this utility to leak a listener on the original event.
256 > *
257 > * @param event The original event to debounce.
258 > * @param merge A function that reduces all events into a single event.
259 > * @param delay The number of milliseconds to debounce.
260 > * @param leading Whether to fire a leading event without debouncing.
261 > * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not
262 > * specified, some events could go missing. Use this if it's important that all events are processed, even if the
263 > * listener gets disposed before the debounced event fires.
264 > * @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}.
265 > * @param disposable A disposable store to register the debounce emitter to.
266 > */
267 > export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;
268 > export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;
269 > export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {
270 let subscription: IDisposable;
271 let output: O | undefined = undefined;
330 return emitter.event;
331 }
332 > event.ts
333 > /**
334 > * Debounces an event, firing after some delay (default=0) with an array of all event original objects.
335 > *
336 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
337 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
338 > * returned event causes this utility to leak a listener on the original event.
339 > *
340 > * @param event The event source for the new event.
341 > * @param delay The number of milliseconds to debounce.
342 > * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not
343 > * specified, some events could go missing. Use this if it's important that all events are processed, even if the
344 > * listener gets disposed before the debounced event fires.
345 > * @param disposable A disposable store to add the new EventEmitter to.
346 > */
347 > export function accumulate<T>(event: Event<T>, delay: number | typeof MicrotaskDelay = 0, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event<T[]> {
348 return Event.debounce<T, T[]>(event, (last, e) => {
349 if (!last) {
354 }, delay, undefined, flushOnListenerRemove ?? true, undefined, disposable);
355 }
356 > event.ts
357 > /**
358 > * Throttles an event, ensuring the event is fired at most once during the specified delay period.
359 > * Unlike debounce, throttle will fire immediately on the leading edge and/or after the delay on the trailing edge.
360 > *
361 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
362 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
363 > * returned event causes this utility to leak a listener on the original event.
364 > *
365 > * @param event The event source for the new event.
366 > * @param merge An accumulator function that merges events if multiple occur during the throttle period.
367 > * @param delay The number of milliseconds to throttle.
368 > * @param leading Whether to fire on the leading edge (immediately on first event).
369 > * @param trailing Whether to fire on the trailing edge (after delay with the last value).
370 > * @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}.
371 > * @param disposable A disposable store to register the throttle emitter to.
372 > */
373 > export function throttle<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;
374 > export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;
375 > export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = true, trailing = true, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {
376 let subscription: IDisposable;
377 let output: O | undefined = undefined;
437 return emitter.event;
438 }
439 > event.ts
440 > /**
441 > * Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate
442 > * event objects from different sources do not fire the same event object.
443 > *
444 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
445 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
446 > * returned event causes this utility to leak a listener on the original event.
447 > *
448 > * @param event The event source for the new event.
449 > * @param equals The equality condition.
450 > * @param disposable A disposable store to add the new EventEmitter to.
451 > *
452 > * @example
453 > * ```
454 > * // Fire only one time when a single window is opened or focused
455 > * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow))
456 > * ```
457 > */
458 > export function latch<T>(event: Event<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b, disposable?: DisposableStore): Event<T> {
459 let firstCall = true;
460 let cache: T;
467 }, disposable);
468 }
469 > event.ts
470 > /**
471 > * Splits an event whose parameter is a union type into 2 separate events for each type in the union.
472 > *
473 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
474 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
475 > * returned event causes this utility to leak a listener on the original event.
476 > *
477 > * @example
478 > * ```
479 > * const event = new EventEmitter<number | undefined>().event;
480 > * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined);
481 > * ```
482 > *
483 > * @param event The event source for the new event.
484 > * @param isT A function that determines what event is of the first type.
485 > * @param disposable A disposable store to add the new EventEmitter to.
486 > */
487 > export function split<T, U>(event: Event<T | U>, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event<T>, Event<U>] {
488 return [
489 Event.filter(event, isT, disposable),
491 ];
492 }
493 > event.ts
494 > /**
495 > * Buffers an event until it has a listener attached.
496 > *
497 > * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
498 > * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
499 > * returned event causes this utility to leak a listener on the original event.
500 > *
501 > * @param event The event source for the new event.
502 > * @param debugName A name for this buffer, used in leak detection warnings.
503 > * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a
504 > * `setTimeout` when the first event listener is added.
505 > * @param _buffer Internal: A source event array used for tests.
506 > *
507 > * @example
508 > * ```
509 > * // Start accumulating events, when the first listener is attached, flush
510 > * // the event after a timeout such that multiple listeners attached before
511 > * // the timeout would receive the event
512 > * this.onInstallExtension = Event.buffer(service.onInstallExtension, 'onInstallExtension', true);
513 > * ```
514 > */
515 > export function buffer<T>(event: Event<T>, debugName: string, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event<T> {
516 let buffer: T[] | null = _buffer.slice();
517
600 return emitter.event;
601 }
602 > /** event.ts
603 > * Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style.
604 > *
605 > * @example
606 > * ```
607 > * // Normal
608 > * const onEnterPressNormal = Event.filter(
609 > * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)),
610 > * e.keyCode === KeyCode.Enter
611 > * ).event;
612 > *
613 > * // Using chain
614 > * const onEnterPressChain = Event.chain(onKeyPress.event, $ => $
615 > * .map(e => new StandardKeyboardEvent(e))
616 > * .filter(e => e.keyCode === KeyCode.Enter)
617 > * );
618 > * ```
619 > */
620 > export function chain<T, R>(event: Event<T>, sythensize: ($: IChainableSythensis<T>) => IChainableSythensis<R>): Event<R> {
621 const fn: Event<R> = (listener, thisArgs, disposables) => {
622 const cs = sythensize(new ChainableSynthesis()) as ChainableSynthesis;
631 return fn;
632 }
633 > event.ts
634 > const HaltChainable = Symbol('HaltChainable');
635 >
636 > class ChainableSynthesis implements IChainableSythensis<any> {
637 private readonly steps: ((input: any) => unknown)[] = [];
638 > event.ts
639 > map<O>(fn: (i: any) => O): this {
640 this.steps.push(fn);
641 return this;
642 }
643 > event.ts
644 > forEach(fn: (i: any) => void): this {
645 this.steps.push(v => {
646 fn(v);
649 return this;
650 }
651 > event.ts
652 > filter(fn: (e: any) => boolean): this {
653 this.steps.push(v => fn(v) ? v : HaltChainable);
654 return this;
655 }
656 > event.ts
657 > reduce<R>(merge: (last: R | undefined, event: any) => R, initial?: R | undefined): this {
658 let last = initial;
659 this.steps.push(v => {
663 return this;
664 }
665 > event.ts
666 > latch(equals: (a: any, b: any) => boolean = (a, b) => a === b): ChainableSynthesis {
667 let firstCall = true;
668 let cache: any;
676 return this;
677 }
678 > event.ts
679 > public evaluate(value: any) {
680 for (const step of this.steps) {
681 value = step(value);
687 return value;
688 }
689 > } event.ts
690 >
691 > export interface IChainableSythensis<T> {
692 > map<O>(fn: (i: T) => O): IChainableSythensis<O>;
693 > forEach(fn: (i: T) => void): IChainableSythensis<T>;
694 > filter<R extends T>(fn: (e: T) => e is R): IChainableSythensis<R>;
695 > filter(fn: (e: T) => boolean): IChainableSythensis<T>;
696 > reduce<R>(merge: (last: R, event: T) => R, initial: R): IChainableSythensis<R>;
697 > reduce<R>(merge: (last: R | undefined, event: T) => R): IChainableSythensis<R>;
698 > latch(equals?: (a: T, b: T) => boolean): IChainableSythensis<T>;
699 > }
700 >
701 > export interface NodeEventEmitter {
702 > on(event: string | symbol, listener: Function): unknown;
703 > removeListener(event: string | symbol, listener: Function): unknown;
704 > }
705 >
706 > /**
707 > * Creates an {@link Event} from a node event emitter.
708 > */
709 > export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
710 const fn = (...args: unknown[]) => result.fire(map(...args));
711 const onFirstListenerAdd = () => emitter.on(eventName, fn);
715 return result.event;
716 }
717 > event.ts
718 > export interface DOMEventEmitter {
719 > addEventListener(event: string | symbol, listener: Function): void;
720 > removeEventListener(event: string | symbol, listener: Function): void;
721 > }
722 >
723 > /**
724 > * Creates an {@link Event} from a DOM event emitter.
725 > */
726 > export function fromDOMEventEmitter<T>(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
727 const fn = (...args: unknown[]) => result.fire(map(...args));
728 const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
732 return result.event;
733 }
734 > event.ts
735 > /**
736 > * Creates a promise out of an event, using the {@link Event.once} helper.
737 > */
738 > export function toPromise<T>(event: Event<T>, disposables?: IDisposable[] | DisposableStore): CancelablePromise<T> {
739 let cancelRef: () => void;
740 let listener: IDisposable;
756 return promise;
757 }
758 > event.ts
759 > /**
760 > * A convenience function for forwarding an event to another emitter which
761 > * improves readability.
762 > *
763 > * This is similar to {@link Relay} but allows instantiating and forwarding
764 > * on a single line and also allows for multiple source events.
765 > * @param from The event to forward.
766 > * @param to The emitter to forward the event to.
767 > * @example
768 > * Event.forward(event, emitter);
769 > * // equivalent to
770 > * event(e => emitter.fire(e));
771 > * // equivalent to
772 > * event(emitter.fire, emitter);
773 > */
774 > export function forward<T>(from: Event<T>, to: Emitter<T>): IDisposable {
775 return from(e => to.fire(e));
776 }
777 > event.ts
778 > /**
779 > * Adds a listener to an event and calls the listener immediately with undefined as the event object.
780 > *
781 > * @example
782 > * ```
783 > * // Initialize the UI and update it when dataChangeEvent fires
784 > * runAndSubscribe(dataChangeEvent, () => this._updateUI());
785 > * ```
786 > */
787 > export function runAndSubscribe<T>(event: Event<T>, handler: (e: T) => unknown, initial: T): IDisposable;
788 > export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => unknown): IDisposable;
789 > export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => unknown, initial?: T): IDisposable {
790 handler(initial);
791 return event(e => handler(e));
792 }
793 > event.ts
794 > class EmitterObserver<T> implements IObserver {
795 >
796 > readonly emitter: Emitter<T>;
797 >
798 > private _counter = 0;
799 > private _hasChanged = false;
800 >
801 > constructor(readonly _observable: IObservable<T>, store: DisposableStore | undefined) {
802 const options: EmitterOptions = {
803 onWillAddFirstListener: () => {
819 }
820 }
821 > event.ts
822 > beginUpdate<T>(_observable: IObservable<T>): void {
823 // assert(_observable === this.obs);
824 this._counter++;
825 }
826 > event.ts
827 > handlePossibleChange<T>(_observable: IObservable<T>): void {
828 // assert(_observable === this.obs);
829 }
830 > event.ts
831 > handleChange<T, TChange>(_observable: IObservableWithChange<T, TChange>, _change: TChange): void {
832 // assert(_observable === this.obs);
833 this._hasChanged = true;
834 }
835 > event.ts
836 > endUpdate<T>(_observable: IObservable<T>): void {
837 // assert(_observable === this.obs);
838 this._counter--;
845 }
846 }
847 > } event.ts
848 >
849 > /**
850 > * Creates an event emitter that is fired when the observable changes.
851 > * Each listeners subscribes to the emitter.
852 > */
853 > export function fromObservable<T>(obs: IObservable<T>, store?: DisposableStore): Event<T> {
854 const observer = new EmitterObserver(obs, store);
855 return observer.emitter.event;
856 }
857 > event.ts
858 > /**
859 > * Each listener is attached to the observable directly.
860 > */
861 > export function fromObservableLight(observable: IObservable<unknown>): Event<void> {
862 return (listener, thisArgs, disposables) => {
863 let count = 0;
897 };
898 }
899 > } event.ts
900 >
901 > export interface EmitterOptions {
902 > /**
903 > * Optional function that's called *before* the very first listener is added
904 > */
905 > onWillAddFirstListener?: Function;
906 > /**
907 > * Optional function that's called *after* the very first listener is added
908 > */
909 > onDidAddFirstListener?: Function;
910 > /**
911 > * Optional function that's called after a listener is added
912 > */
913 > onDidAddListener?: Function;
914 > /**
915 > * Optional function that's called *after* remove the very last listener
916 > */
917 > onDidRemoveLastListener?: Function;
918 > /**
919 > * Optional function that's called *before* a listener is removed
920 > */
921 > onWillRemoveListener?: Function;
922 > /**
923 > * Optional function that's called when a listener throws an error. Defaults to
924 > * {@link onUnexpectedError}
925 > */
926 > onListenerError?: (e: any) => void;
927 > /**
928 > * Number of listeners that are allowed before assuming a leak. Default to
929 > * a globally configured value
930 > *
931 > * @see setGlobalLeakWarningThreshold
932 > */
933 > leakWarningThreshold?: number;
934 > /**
935 > * Human-readable name for the emitter, included in leak warning error
936 > * messages to help identify which emitter is leaking in telemetry.
937 > */
938 > leakWarningName?: string;
939 > /**
940 > * Pass in a delivery queue, which is useful for ensuring
941 > * in order event delivery across multiple emitters.
942 > */
943 > deliveryQueue?: EventDeliveryQueue;
944 >
945 > /** ONLY enable this during development */
946 > _profName?: string;
947 > }
948 >
949 >
950 > export class EventProfiling {
951 >
952 > static readonly all = new Set<EventProfiling>();
953 >
954 > private static _idPool = 0;
955 >
956 > readonly name: string;
957 > public listenerCount: number = 0;
958 > public invocationCount = 0;
959 > public elapsedOverall = 0;
960 > public durations: number[] = [];
961 >
962 > private _stopWatch?: StopWatch;
963 >
964 > constructor(name: string) {
965 this.name = `${name}_${EventProfiling._idPool++}`;
966 EventProfiling.all.add(this);
967 }
968 > event.ts
969 > start(listenerCount: number): void {
970 this._stopWatch = new StopWatch();
971 this.listenerCount = listenerCount;
972 }
973 > event.ts
974 > stop(): void {
975 if (this._stopWatch) {
976 const elapsed = this._stopWatch.elapsed();
981 }
982 }
983 > } event.ts
984 >
985 > let _globalLeakWarningThreshold = -1;
986 > export function setGlobalLeakWarningThreshold(n: number): IDisposable {
987 const oldValue = _globalLeakWarningThreshold;
988 _globalLeakWarningThreshold = n;
993 };
994 }
995 > event.ts
996 > class LeakageMonitor {
997 >
998 > private static _idPool = 1;
999 >
1000 > private _stacks: Map<string, number> | undefined;
1001 > private _warnCountdown: number = 0;
1002 >
1003 > constructor(
1004 private readonly _errorHandler: (err: Error) => void,
1005 readonly threshold: number,
1006 readonly name: string = (LeakageMonitor._idPool++).toString(16).padStart(3, '0')
1007 ) { }
1008 > event.ts
1009 > dispose(): void {
1010 this._stacks?.clear();
1011 }
1012 > event.ts
1013 > check(stack: Stacktrace, listenerCount: number): undefined | (() => void) {
1014
1015 const threshold = this.threshold;
1046 };
1047 }
1048 > event.ts
1049 > getMostFrequentStack(): [string, number] | undefined {
1050 if (!this._stacks) {
1051 return undefined;
1061 return topStack;
1062 }
1063 > } event.ts
1064 >
1065 > class Stacktrace {
1066 >
1067 > static create() {
1068 > const err = new Error();
1069 > return new Stacktrace(err.stack ?? '');
1070 > }
1071 >
1072 > private constructor(readonly value: string) { }
1073 >
1074 > print() {
1075 console.warn(this.value.split('\n').slice(2).join('\n'));
1076 }
1077 > } event.ts
1078 >
1079 > // error that is logged when going over the configured listener threshold
1080 > export class ListenerLeakError extends Error {
1081 > readonly kind: string;
1082 > readonly listenerCount: number;
1083 > /**
1084 > * The detailed message including listener count and most frequent stack.
1085 > * Available locally for debugging but intentionally not used as the error
1086 > * `message`. When `emitterName` is provided, errors group by emitter name
1087 > * and kind in telemetry; otherwise they group by kind alone.
1088 > */
1089 > readonly details: string;
1090 > constructor(kind: 'dominated' | 'popular', details: string, stack: string, listenerCount: number, emitterName?: string) {
1091 super(emitterName
1092 ? `[${emitterName}] potential listener LEAK detected, ${kind}`
1098 this.stack = stack;
1099 }
1100 > event.ts
1101 > static is(err: unknown): err is ListenerLeakError {
1102 return err instanceof ListenerLeakError
1103 || (err instanceof Error && typeof (err as Error & { kind: unknown; listenerCount: unknown }).kind === 'string' && typeof (err as Error & { kind: unknown; listenerCount: unknown }).listenerCount === 'number');
1104 }
1105 > } event.ts
1106 >
1107 > // SEVERE error that is logged when having gone way over the configured listener
1108 > // threshold so that the emitter refuses to accept more listeners
1109 > export class ListenerRefusalError extends ListenerLeakError {
1110 > constructor(kind: 'dominated' | 'popular', details: string, stack: string, listenerCount: number, emitterName?: string) {
1111 super(kind, details, stack, listenerCount, emitterName);
1112 this.name = 'ListenerRefusalError';
1113 }
1114 > } event.ts
1115 >
1116 > let id = 0;
1117 > class UniqueContainer<T> {
1118 > stack?: Stacktrace;
1119 > public id = id++;
1120 > constructor(public readonly value: T) { }
1121 > }
1122 > const compactionThreshold = 2;
1123 >
1124 > type ListenerContainer<T> = UniqueContainer<(data: T) => void>;
1125 > type ListenerOrListeners<T> = (ListenerContainer<T> | undefined)[] | ListenerContainer<T>;
1126 >
1127 > const forEachListener = <T>(listeners: ListenerOrListeners<T>, fn: (c: ListenerContainer<T>) => void) => {
1128 if (listeners instanceof UniqueContainer) {
1129 fn(listeners);
1137 }
1138 };
1139 > event.ts
1140 > /**
1141 > * The Emitter can be used to expose an Event to the public
1142 > * to fire it from the insides.
1143 > * Sample:
1144 > class Document {
1145 >
1146 > private readonly _onDidChange = new Emitter<(value:string)=>any>();
1147 >
1148 > public onDidChange = this._onDidChange.event;
1149 >
1150 > // getter-style
1151 > // get onDidChange(): Event<(value:string)=>any> {
1152 > // return this._onDidChange.event;
1153 > // }
1154 >
1155 > private _doIt() {
1156 > //...
1157 > this._onDidChange.fire(value);
1158 > }
1159 > }
1160 > */
1161 > export class Emitter<T> {
1162 >
1163 > private readonly _options?: EmitterOptions;
1164 > private readonly _leakageMon?: LeakageMonitor;
1165 > private readonly _perfMon?: EventProfiling;
1166 > private _disposed?: true;
1167 > private _event?: Event<T>;
1168 >
1169 > /**
1170 > * A listener, or list of listeners. A single listener is the most common
1171 > * for event emitters (#185789), so we optimize that special case to avoid
1172 > * wrapping it in an array (just like Node.js itself.)
1173 > *
1174 > * A list of listeners never 'downgrades' back to a plain function if
1175 > * listeners are removed, for two reasons:
1176 > *
1177 > * 1. That's complicated (especially with the deliveryQueue)
1178 > * 2. A listener with >1 listener is likely to have >1 listener again at
1179 > * some point, and swapping between arrays and functions may[citation needed]
1180 > * introduce unnecessary work and garbage.
1181 > *
1182 > * The array listeners can be 'sparse', to avoid reallocating the array
1183 > * whenever any listener is added or removed. If more than `1 / compactionThreshold`
1184 > * of the array is empty, only then is it resized.
1185 > */
1186 > protected _listeners?: ListenerOrListeners<T>;
1187 >
1188 > /**
1189 > * Always to be defined if _listeners is an array. It's no longer a true
1190 > * queue, but holds the dispatching 'state'. If `fire()` is called on an
1191 > * emitter, any work left in the _deliveryQueue is finished first.
1192 > */
1193 > private _deliveryQueue?: EventDeliveryQueuePrivate;
1194 > protected _size = 0;
1195 >
1196 > constructor(options?: EmitterOptions) {
1197 > this._options = options; event.ts
1198 > this._leakageMon = (_globalLeakWarningThreshold > 0 || this._options?.leakWarningThreshold)
1199 ? new LeakageMonitor(options?.onListenerError ?? onUnexpectedError, this._options?.leakWarningThreshold ?? _globalLeakWarningThreshold, this._options?.leakWarningName) :
1200 > undefined; event.ts
1201 > this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined;
1202 > this._deliveryQueue = this._options?.deliveryQueue as EventDeliveryQueuePrivate | undefined;
1203 > }
1204 > event.ts
1205 > dispose() {
1206 > if (!this._disposed) { event.ts
1207 > this._disposed = true;
1208 >
1209 > // It is bad to have listeners at the time of disposing an emitter, it is worst to have listeners keep the emitter
1210 > // alive via the reference that's embedded in their disposables. Therefore we loop over all remaining listeners and
1211 > // unset their subscriptions/disposables. Looping and blaming remaining listeners is done on next tick because the
1212 > // the following programming pattern is very popular:
1213 > //
1214 > // const someModel = this._disposables.add(new ModelObject()); // (1) create and register model
1215 > // this._disposables.add(someModel.onDidChange(() => { ... }); // (2) subscribe and register model-event listener
1216 > // ...later...
1217 > // this._disposables.dispose(); disposes (1) then (2): don't warn after (1) but after the "overall dispose" is done
1218 >
1219 > if (this._deliveryQueue?.current === this) {
1220 this._deliveryQueue.reset();
1221 }
1222 > if (this._listeners) { event.ts
1223 > if (_enableDisposeWithListenerWarning) { event.ts
1224 const listeners = this._listeners;
1225 queueMicrotask(() => {
1227 });
1228 }
1229 > event.ts
1230 > this._listeners = undefined;
1231 > this._size = 0;
1232 > }
1233 > this._options?.onDidRemoveLastListener?.(); event.ts
1234 > this._leakageMon?.dispose();
1235 > }
1236 > }
1237 > event.ts
1238 > /**
1239 > * For the public to allow to subscribe
1240 > * to events from this Emitter
1241 > */
1242 > get event(): Event<T> {
1243 > this._event ??= (callback: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { event.ts
1244 > if (this._leakageMon && this._size > this._leakageMon.threshold ** 2) { event.ts
1245 const message = `[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;
1246 console.warn(message);
1254 return Disposable.None;
1255 }
1256 > event.ts
1257 > if (this._disposed) {
1258 // todo: should we warn if a listener is added to a disposed emitter? This happens often
1259 return Disposable.None;
1260 }
1261 > event.ts
1262 > if (thisArgs) {
1263 callback = callback.bind(thisArgs);
1264 }
1265 > event.ts
1266 > const contained = new UniqueContainer(callback);
1267 >
1268 > let removeMonitor: Function | undefined;
1269 > let stack: Stacktrace | undefined;
1270 > if (this._leakageMon && this._size >= Math.ceil(this._leakageMon.threshold * 0.2)) {
1271 // check and record this emitter for potential leakage
1272 contained.stack = Stacktrace.create();
1273 removeMonitor = this._leakageMon.check(contained.stack, this._size + 1);
1274 }
1275 > event.ts
1276 > if (_enableDisposeWithListenerWarning) {
1277 contained.stack = stack ?? Stacktrace.create();
1278 }
1279 > event.ts
1280 > if (!this._listeners) {
1281 > this._options?.onWillAddFirstListener?.(this);
1282 > this._listeners = contained;
1283 > this._options?.onDidAddFirstListener?.(this);
1284 > } else if (this._listeners instanceof UniqueContainer) {
1285 this._deliveryQueue ??= new EventDeliveryQueuePrivate();
1286 this._listeners = [this._listeners, contained];
1288 this._listeners.push(contained);
1289 }
1290 > this._options?.onDidAddListener?.(this); event.ts
1291 >
1292 > this._size++;
1293 >
1294 >
1295 > const result = toDisposable(() => {
1296 > removeMonitor?.(); event.ts
1297 > this._removeListener(contained);
1298 > }); event.ts
1299 > addToDisposables(result, disposables);
1300 >
1301 > return result;
1302 > };
1303 > event.ts
1304 > return this._event;
1305 > }
1306 > event.ts
1307 > private _removeListener(listener: ListenerContainer<T>) {
1308 > this._options?.onWillRemoveListener?.(this); event.ts
1309 >
1310 > if (!this._listeners) {
1311 > return; // expected if a listener gets disposed event.ts
1312 > }
1313
1314 if (this._size === 1) {
1348 listeners.length = n;
1349 }
1350 > } event.ts
1351 > event.ts
1352 > private _deliver(listener: undefined | UniqueContainer<(value: T) => void>, value: T) {
1353 > if (!listener) { event.ts
1354 return;
1355 }
1356 > event.ts
1357 > const errorHandler = this._options?.onListenerError || onUnexpectedError;
1358 > if (!errorHandler) {
1359 listener.value(value);
1360 return;
1361 }
1362 > event.ts
1363 > try {
1364 > listener.value(value);
1365 > } catch (e) {
1366 errorHandler(e);
1367 }
1368 > } event.ts
1369 > event.ts
1370 > /** Delivers items in the queue. Assumes the queue is ready to go. */
1371 > private _deliverQueue(dq: EventDeliveryQueuePrivate) {
1372 const listeners = dq.current!._listeners! as (ListenerContainer<T> | undefined)[];
1373 while (dq.i < dq.end) {
1377 dq.reset();
1378 }
1379 > event.ts
1380 > /**
1381 > * To be kept private to fire an event to
1382 > * subscribers
1383 > */
1384 > fire(event: T): void {
1385 > if (this._deliveryQueue?.current) { event.ts
1386 this._deliverQueue(this._deliveryQueue);
1387 this._perfMon?.stop(); // last fire() will have starting perfmon, stop it before starting the next dispatch
1388 }
1389 > event.ts
1390 > this._perfMon?.start(this._size);
1391 >
1392 > if (!this._listeners) {
1393 > // no-op event.ts
1394 > } else if (this._listeners instanceof UniqueContainer) { event.ts
1395 > this._deliver(this._listeners, event); event.ts
1396 > } else { event.ts
1397 const dq = this._deliveryQueue!;
1398 dq.enqueue(this, event, this._listeners.length);
1399 this._deliverQueue(dq);
1400 }
1401 > event.ts
1402 > this._perfMon?.stop();
1403 > }
1404 > event.ts
1405 > hasListeners(): boolean {
1406 return this._size > 0;
1407 }
1408 > } event.ts
1409 >
1410 > export interface EventDeliveryQueue {
1411 > _isEventDeliveryQueue: true;
1412 > }
1413 >
1414 > export const createEventDeliveryQueue = (): EventDeliveryQueue => new EventDeliveryQueuePrivate();
1415 >
1416 class EventDeliveryQueuePrivate implements EventDeliveryQueue {
1417 declare _isEventDeliveryQueue: true;
1426 */
1427 public end = 0;
1428 > event.ts
1429 > /**
1430 > * Emitter currently being dispatched on. Emitter._listeners is always an array.
1431 > */
1432 > public current?: Emitter<any>;
1433 > /**
1434 > * Currently emitting value. Defined whenever `current` is.
1435 > */
1436 > public value?: unknown;
1437 >
1438 > public enqueue<T>(emitter: Emitter<T>, value: T, end: number) {
1439 this.i = 0;
1440 this.end = end;
1442 this.value = value;
1443 }
1444 > event.ts
1445 > public reset() {
1446 this.i = this.end; // force any current emission loop to stop, mainly for during dispose
1447 this.current = undefined;
1448 this.value = undefined;
1449 }
1450 > } event.ts
1451 >
1452 > export interface IWaitUntil {
1453 > token: CancellationToken;
1454 > waitUntil(thenable: Promise<unknown>): void;
1455 > }
1456 >
1457 > export type IWaitUntilData<T> = Omit<Omit<T, 'waitUntil'>, 'token'>;
1458 >
1459 > export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
1460 >
1461 > private _asyncDeliveryQueue?: LinkedList<[(ev: T) => void, IWaitUntilData<T>]>;
1462 >
1463 > async fireAsync(data: IWaitUntilData<T>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> {
1464 if (!this._listeners) {
1465 return;
1512 }
1513 }
1514 > } event.ts
1515 >
1516 >
1517 > export class PauseableEmitter<T> extends Emitter<T> {
1518 >
1519 > private _isPaused = 0;
1520 > protected _eventQueue = new LinkedList<T>();
1521 > private _mergeFn?: (input: T[]) => T;
1522 >
1523 > public get isPaused(): boolean {
1524 > return this._isPaused !== 0;
1525 > }
1526 >
1527 > constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
1528 super(options);
1529 this._mergeFn = options?.merge;
1530 }
1531 > event.ts
1532 > pause(): void {
1533 this._isPaused++;
1534 }
1535 > event.ts
1536 > resume(): void {
1537 if (this._isPaused !== 0 && --this._isPaused === 0) {
1538 if (this._mergeFn) {
1554 }
1555 }
1556 > event.ts
1557 > override fire(event: T): void {
1558 if (this._size) {
1559 if (this._isPaused !== 0) {
1564 }
1565 }
1566 > } event.ts
1567 >
1568 > export class DebounceEmitter<T> extends PauseableEmitter<T> {
1569 >
1570 > private readonly _delay: number;
1571 > private _handle: Timeout | undefined;
1572 >
1573 > constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number }) {
1574 super(options);
1575 this._delay = options.delay ?? 100;
1576 }
1577 > event.ts
1578 > override fire(event: T): void {
1579 if (!this._handle) {
1580 this.pause();
1586 super.fire(event);
1587 }
1588 > } event.ts
1589 >
1590 > /**
1591 > * An emitter which queue all events and then process them at the
1592 > * end of the event loop.
1593 > */
1594 > export class MicrotaskEmitter<T> extends Emitter<T> {
1595 > private _queuedEvents: T[] = [];
1596 > private _mergeFn?: (input: T[]) => T;
1597 >
1598 > constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
1599 super(options);
1600 this._mergeFn = options?.merge;
1601 }
1602 > override fire(event: T): void { event.ts
1603
1604 if (!this.hasListeners()) {
1618 }
1619 }
1620 > } event.ts
1621 >
1622 > /**
1623 > * An event emitter that multiplexes many events into a single event.
1624 > *
1625 > * @example Listen to the `onData` event of all `Thing`s, dynamically adding and removing `Thing`s
1626 > * to the multiplexer as needed.
1627 > *
1628 > * ```typescript
1629 > * const anythingDataMultiplexer = new EventMultiplexer<{ data: string }>();
1630 > *
1631 > * const thingListeners = DisposableMap<Thing, IDisposable>();
1632 > *
1633 > * thingService.onDidAddThing(thing => {
1634 > * thingListeners.set(thing, anythingDataMultiplexer.add(thing.onData);
1635 > * });
1636 > * thingService.onDidRemoveThing(thing => {
1637 > * thingListeners.deleteAndDispose(thing);
1638 > * });
1639 > *
1640 > * anythingDataMultiplexer.event(e => {
1641 > * console.log('Something fired data ' + e.data)
1642 > * });
1643 > * ```
1644 > */
1645 > export class EventMultiplexer<T> implements IDisposable {
1646 >
1647 > private readonly emitter: Emitter<T>;
1648 > private hasListeners = false;
1649 > private events: { event: Event<T>; listener: IDisposable | null }[] = [];
1650 >
1651 > constructor() {
1652 this.emitter = new Emitter<T>({
1653 onWillAddFirstListener: () => this.onFirstListenerAdd(),
1655 });
1656 }
1657 > event.ts
1658 > get event(): Event<T> {
1659 return this.emitter.event;
1660 }
1661 > event.ts
1662 > add(event: Event<T>): IDisposable {
1663 const e = { event: event, listener: null };
1664 this.events.push(e);
1679 return toDisposable(createSingleCallFunction(dispose));
1680 }
1681 > event.ts
1682 > private onFirstListenerAdd(): void {
1683 this.hasListeners = true;
1684 this.events.forEach(e => this.hook(e));
1685 }
1686 > event.ts
1687 > private onLastListenerRemove(): void {
1688 this.hasListeners = false;
1689 this.events.forEach(e => this.unhook(e));
1690 }
1691 > event.ts
1692 > private hook(e: { event: Event<T>; listener: IDisposable | null }): void {
1693 e.listener = e.event(r => this.emitter.fire(r));
1694 }
1695 > event.ts
1696 > private unhook(e: { event: Event<T>; listener: IDisposable | null }): void {
1697 e.listener?.dispose();
1698 e.listener = null;
1699 }
1700 > event.ts
1701 > dispose(): void {
1702 this.emitter.dispose();
1703
1707 this.events = [];
1708 }
1709 > } event.ts
1710 >
1711 > export interface IDynamicListEventMultiplexer<TEventType> extends IDisposable {
1712 > readonly event: Event<TEventType>;
1713 > }
1714 > export class DynamicListEventMultiplexer<TItem, TEventType> implements IDynamicListEventMultiplexer<TEventType> {
1715 > private readonly _store = new DisposableStore();
1716 >
1717 > readonly event: Event<TEventType>;
1718 >
1719 > constructor(
1720 items: TItem[],
1721 onAddItem: Event<TItem>,
1747 this.event = multiplexer.event;
1748 }
1749 > event.ts
1750 > dispose() {
1751 this._store.dispose();
1752 }
1753 > } event.ts
1754 >
1755 > /**
1756 > * The EventBufferer is useful in situations in which you want
1757 > * to delay firing your events during some code.
1758 > * You can wrap that code and be sure that the event will not
1759 > * be fired during that wrap.
1760 > *
1761 > * ```
1762 > * const emitter: Emitter;
1763 > * const delayer = new EventDelayer();
1764 > * const delayedEvent = delayer.wrapEvent(emitter.event);
1765 > *
1766 > * delayedEvent(console.log);
1767 > *
1768 > * delayer.bufferEvents(() => {
1769 > * emitter.fire(); // event will not be fired yet
1770 > * });
1771 > *
1772 > * // event will only be fired at this point
1773 > * ```
1774 > */
1775 > export class EventBufferer {
1776
1777 private data: { buffers: Function[] }[] = [];
1778 > event.ts
1779 > wrapEvent<T>(event: Event<T>): Event<T>;
1780 > wrapEvent<T>(event: Event<T>, reduce: (last: T | undefined, event: T) => T): Event<T>;
1781 > wrapEvent<T, O>(event: Event<T>, reduce: (last: O | undefined, event: T) => O, initial: O): Event<O>;
1782 > wrapEvent<T, O>(event: Event<T>, reduce?: (last: T | O | undefined, event: T) => T | O, initial?: O): Event<O | T> {
1783 return (listener, thisArgs?, disposables?) => {
1784 return event(i => {
1832 };
1833 }
1834 > event.ts
1835 > bufferEvents<R = void>(fn: () => R): R {
1836 const data = { buffers: new Array<Function>() };
1837 this.data.push(data);
1841 return r;
1842 }
1843 > } event.ts
1844 >
1845 > /**
1846 > * A Relay is an event forwarder which functions as a replugabble event pipe.
1847 > * Once created, you can connect an input event to it and it will simply forward
1848 > * events from that input event through its own `event` property. The `input`
1849 > * can be changed at any point in time.
1850 > */
1851 > export class Relay<T> implements IDisposable {
1852
1853 private listening = false;
1867
1868 readonly event: Event<T> = this.emitter.event;
1869 > event.ts
1870 > set input(event: Event<T>) {
1871 this.inputEvent = event;
1872
1876 }
1877 }
1878 > event.ts
1879 > dispose() {
1880 this.inputEventListener.dispose();
1881 this.emitter.dispose();
1882 }
1883 > } event.ts
1884 >
1885 > export interface IValueWithChangeEvent<T> {
1886 > readonly onDidChange: Event<void>;
1887 > get value(): T;
1888 > }
1889 >
1890 > export class ValueWithChangeEvent<T> implements IValueWithChangeEvent<T> {
1891 > public static const<T>(value: T): IValueWithChangeEvent<T> {
1892 > return new ConstValueWithChangeEvent(value);
1893 > }
1894 >
1895 > private readonly _onDidChange = new Emitter<void>();
1896 > readonly onDidChange: Event<void> = this._onDidChange.event;
1897 >
1898 > constructor(private _value: T) { }
1899 >
1900 > get value(): T {
1901 return this._value;
1902 }
1903 > event.ts
1904 > set value(value: T) {
1905 if (value !== this._value) {
1906 this._value = value;
1908 }
1909 }
1910 > } event.ts
1911 >
1912 > class ConstValueWithChangeEvent<T> implements IValueWithChangeEvent<T> {
1913 > public readonly onDidChange: Event<void> = Event.None;
1914 >
1915 > constructor(readonly value: T) { }
1916 > }
1917 >
1918 > /**
1919 > * @param handleItem Is called for each item in the set (but only the first time the item is seen in the set).
1920 > * The returned disposable is disposed if the item is no longer in the set.
1921 > */
1922 > export function trackSetChanges<T>(getData: () => ReadonlySet<T>, onDidChangeData: Event<unknown>, handleItem: (d: T) => IDisposable): IDisposable {
1923 const map = new DisposableMap<T, IDisposable>();
1924 let oldData = new Set(getData());
1942 return store;
1943 }
1944 > event.ts
1945 >
1946 > function addToDisposables(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) { event.ts
1947 > if (disposables instanceof DisposableStore) {
1948 disposables.add(result);
1949 > } else if (Array.isArray(disposables)) { event.ts
1950 disposables.push(result);
1951 }
1952 > } event.ts
1953 > event.ts
1954 function disposeAndRemove(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) {
1955 if (disposables instanceof DisposableStore) {
src/vs/platform/agentHost/common/state/sessionState.ts 929 covered LOC · 82 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionState.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Immutable state types for the sessions process protocol.
7 > // See protocol.md for the full design rationale.
8 > //
9 > // Most types are imported from the auto-generated protocol layer
10 > // (synced from the agent-host-protocol repo). This file adds VS Code-specific
11 > // helpers and re-exports.
12 >
13 > import { decodeBase64, encodeBase64, VSBuffer } from '../../../../base/common/buffer.js';
14 > import { hasKey, type Mutable } from '../../../../base/common/types.js';
15 > import { URI as ResourceURI } from '../../../../base/common/uri.js';
16 > import type { IProductService } from '../../../product/common/productService.js';
17 > import { readToolCallMeta } from '../meta/agentToolCallMeta.js';
18 > import {
19 > ResponsePartKind,
20 > SessionStatus,
21 > ToolCallStatus,
22 > SessionLifecycle,
23 > TerminalState,
24 > ToolResultContentType,
25 > ToolResultFileEditContent,
26 > ChatOriginKind,
27 > ChatInteractivity,
28 > type ActiveTurn,
29 > type ChangesetState,
30 > type ChatState,
31 > type ChatSummary,
32 > type PendingMessage,
33 > type Turn,
34 > type AnnotationsState,
35 > type URI as ProtocolURI,
36 > type RootState,
37 > type SessionState,
38 > type SessionSummary,
39 > type TextRange,
40 > type ToolCallCancelledState,
41 > type ToolCallCompletedState,
42 > type ToolCallResult,
43 > type ToolCallState,
44 > type ToolResultContent,
45 > type ToolResultSubagentContent,
46 > type ToolResultTextContent,
47 > type UsageInfo,
48 > type Message,
49 > } from './protocol/state.js';
50 >
51 > // Re-export everything from the protocol state module
52 > export {
53 > ChangesetOperationScope, ChangesetOperationStatus, ChangesetStatus, CustomizationLoadStatus,
54 > CustomizationType, MessageAttachmentKind, MessageKind,
55 > PendingMessageKind,
56 > PolicyState,
57 > ResponsePartKind,
58 > ChatInputAnswerState as SessionInputAnswerState,
59 > ChatInputAnswerValueKind as SessionInputAnswerValueKind,
60 > ChatInputQuestionKind as SessionInputQuestionKind,
61 > ChatInputResponseKind as SessionInputResponseKind,
62 > ChatInteractivity,
63 > ChatOriginKind,
64 > SessionLifecycle,
65 > SessionStatus, ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallContributorKind, ToolCallRiskAssessmentKind, ToolCallRiskAssessmentStatus, ToolCallStatus,
66 > ToolResultContentType,
67 > TurnState, type ActiveTurn, type AgentCustomization, type AgentCapabilities, type AgentInfo, type AgentSelection, type Annotation, type AnnotationEntry, type AnnotationsState, type AnnotationsSummary, type Changeset, type ChangesetFile,
68 > type ChangesetOperation, type ChangesetState, type ChatState, type ChatSummary, type ChatOrigin, type ChildCustomization, type ClientPluginCustomization, type ConfigPropertySchema,
69 > type ConfigSchema,
70 > type ContentRef, type Customization, type CustomizationDegradedState,
71 > type CustomizationErrorState, type CustomizationLoadedState, type CustomizationLoadingState, type CustomizationLoadState, type DirectoryCustomization, type ErrorInfo, type HookCustomization, type FileEdit as ISessionFileDiff, type ToolResultEmbeddedResourceContent as IToolResultBinaryContent, type MarkdownResponsePart, type McpServerCustomization, type MessageAttachment,
72 > type MessageResourceAttachment, type MessageEmbeddedResourceAttachment, type MessageAnnotationsAttachment, type ModelSelection, type PendingMessage, type PluginCustomization, type ProjectInfo, type PromptCustomization, type ReasoningResponsePart,
73 > type ResponsePart,
74 > type RootState, type RuleCustomization, type SessionActiveClient,
75 > type SessionConfigState, type ChatInputAnswer as SessionInputAnswer,
76 > type ChatInputOption as SessionInputOption, type ChatInputQuestion as SessionInputQuestion, type ChatInputRequest as SessionInputRequest, type SessionModelInfo,
77 > type SessionState,
78 > type SessionSummary, type SkillCustomization, type Snapshot, type StringOrMarkdown, type TerminalState, type TextRange,
79 > type ToolAnnotations,
80 > type ToolCallCancelledState,
81 > type ToolCallCompletedState,
82 > type ToolCallPendingConfirmationState,
83 > type ToolCallPendingResultConfirmationState,
84 > type ToolCallResponsePart,
85 > type ToolCallResult,
86 > type ToolCallRiskAssessment,
87 > type ToolCallRiskAssessmentCompleteState,
88 > type ToolCallRiskAssessmentLoadingState,
89 > type ToolCallRunningState,
90 > type ToolCallState,
91 > type ToolCallStreamingState,
92 > type ToolCallContributor,
93 > type ToolDefinition, type ToolResultContent,
94 > type ToolResultFileEditContent,
95 > type TerminalCommandResult,
96 > type ToolResultSubagentContent,
97 > type ToolResultTerminalContent,
98 > type ToolResultTextContent,
99 > type Turn, type URI, type UsageInfo,
100 > type Message
101 > } from './protocol/state.js';
102 >
103 > /**
104 > * Well-known keys that may appear on {@link UsageInfo._meta}.
105 > * Clients MAY read these to provide enhanced UI (e.g. credit cost display).
106 > */
107 > export interface UsageInfoMeta {
108 > /** Per-turn credit cost reported by the backend. */
109 > cost?: number;
110 > /** The concrete model selected by Copilot Auto and the routing explanation. */
111 > autoModeResolved?: IAutoModeResolvedInfo;
112 > /** Copilot-specific usage breakdown, including nano-AIU totals. */
113 > copilotUsage?: {
114 > totalNanoAiu?: number;
115 > [key: string]: unknown;
116 > };
117 > /**
118 > * Per-category account quota snapshots reported by the backend on the
119 > * model-call usage event, keyed by quota type (e.g. `chat`,
120 > * `premium_interactions`). Clients MAY use these to keep the account quota
121 > * UI current without a separate quota fetch.
122 > */
123 > quotaSnapshots?: {
124 > [quotaType: string]: {
125 > readonly isUnlimitedEntitlement?: boolean;
126 > readonly entitlementRequests?: number;
127 > readonly usedRequests?: number;
128 > readonly remainingPercentage?: number;
129 > readonly overage?: number;
130 > readonly overageAllowedWithExhaustedQuota?: boolean;
131 > /** ISO 8601 date when the quota resets, if applicable. */
132 > readonly resetDate?: string;
133 > } | undefined;
134 > };
135 > /**
136 > * Per-source context-window attribution breakdown reported by the SDK's
137 > * `session.rpc.metadata.getContextAttribution()`. Populated asynchronously
138 > * after each usage event and piped to the context-usage widget as
139 > * `promptTokenDetails`.
140 > */
141 > contextAttribution?: IContextAttributionData;
142 > [key: string]: unknown;
143 > }
144 >
145 > export interface IAutoModeResolvedInfo {
146 > readonly chosenModel: string;
147 > readonly reasoningBucket?: 'low' | 'medium' | 'high';
148 > readonly categoryScores?: Readonly<Record<string, number | undefined>>;
149 > readonly predictedLabel?: string;
150 > readonly confidence?: number;
151 > readonly candidateModels?: readonly string[];
152 > }
153 >
154 > /**
155 > * Mirrors the SDK's `SessionContextAttribution` shape — a flat list of
156 > * per-source entries describing what occupies the session's context window.
157 > */
158 > export interface IContextAttributionData {
159 > readonly totalTokens: number;
160 > readonly entries: readonly IContextAttributionEntry[];
161 > readonly compactions: { readonly count: number };
162 > }
163 >
164 > export interface IContextAttributionEntry {
165 > readonly kind: string;
166 > readonly id: string;
167 > readonly label: string;
168 > readonly tokens: number;
169 > readonly parentId?: string;
170 > readonly attributes?: Readonly<Record<string, string | undefined>>;
171 > }
172 >
173 > type AccountQuotaSnapshot = NonNullable<NonNullable<UsageInfoMeta['quotaSnapshots']>[string]>;
174 >
175 function readAccountQuotaSnapshot(value: unknown): AccountQuotaSnapshot | undefined {
176 if (!value || typeof value !== 'object' || Array.isArray(value)) {
188 return snapshot;
189 }
191 > /**
192 > * Reads the well-known {@link UsageInfoMeta} keys from a usage report's open
193 > * `_meta` bag, ignoring unrelated provider-specific keys and validating each
194 > * field's type. Always read {@link UsageInfo._meta} through this helper rather
195 > * than casting the bag to {@link UsageInfoMeta}, so a malformed or partial bag
196 > * degrades to absent fields instead of producing values of the wrong runtime
197 > * type. Returns an empty object when the bag is absent.
198 > */
199 > export function readUsageInfoMeta(usage: UsageInfo | undefined): UsageInfoMeta {
200 const meta = usage?._meta;
201 if (!meta) {
227 return result;
228 }
230 function readAutoModeResolvedInfo(value: unknown): IAutoModeResolvedInfo | undefined {
231 if (!value || typeof value !== 'object' || Array.isArray(value)) {
258 return result;
259 }
261 function readContextAttribution(value: unknown): IContextAttributionData | undefined {
262 if (!value || typeof value !== 'object' || Array.isArray(value)) {
295 return { totalTokens: raw['totalTokens'] as number, entries, compactions };
296 }
298 function filterStringAttributes(raw: Record<string, unknown>): Record<string, string | undefined> {
299 const result: Record<string, string | undefined> = {};
305 return result;
306 }
308 > export {
309 > ChangesetOperationTargetKind, type ChangesetOperationFollowUp, type ChangesetOperationTarget
310 > } from './protocol/commands.js';
311 >
312 > // Canonical chat-input type names (the protocol renamed the former
313 > // `SessionInput*` types to `ChatInput*` when input requests moved onto the
314 > // chat channel). Re-exported here so consumers can import them from the glue
315 > // layer alongside the legacy `SessionInput*` aliases above.
316 > export {
317 > ChatInputAnswerState,
318 > ChatInputAnswerValueKind,
319 > ChatInputQuestionKind,
320 > ChatInputResponseKind,
321 > type ChatInputAnswer,
322 > type ChatInputOption,
323 > type ChatInputQuestion,
324 > type ChatInputRequest,
325 > type InputRequestResponsePart,
326 > } from './protocol/state.js';
327 >
328 > // ---- File edit kind ---------------------------------------------------------
329 >
330 > /**
331 > * The kind of file edit operation. Derived from the presence/absence of
332 > * `before`/`after` in {@link ToolResultFileEditContent}.
333 > */
334 > export const enum FileEditKind {
335 > /** Content edit (same file URI, different content). */
336 > Edit = 'edit',
337 > /** File creation (no before state). */
338 > Create = 'create',
339 > /** File deletion (no after state). */
340 > Delete = 'delete',
341 > /** File rename/move (different before and after URIs). */
342 > Rename = 'rename',
343 > }
344 >
345 > // ---- Well-known URIs --------------------------------------------------------
346 >
347 > /** URI for the root state subscription. */
348 > export const ROOT_STATE_URI = 'ahp-root://';
349 >
350 > /** Scheme used by {@link ROOT_STATE_URI}. */
351 > export const AHP_ROOT_SCHEME = 'ahp-root';
352 >
353 > /** Scheme used by resource-watch channel URIs (`ahp-resource-watch:/<encoded>`). */
354 > export const AHP_RESOURCE_WATCH_SCHEME = 'ahp-resource-watch';
355 >
356 > /**
357 > * Encode a resource-watch descriptor into its canonical channel URI. The
358 > * descriptor is serialised into the URI path so the receiver can recover
359 > * the watch parameters without any server-side bookkeeping — subscribe is
360 > * the only point where state is materialised (an `IFileService` watcher
361 > * is attached on the first subscriber and held through a grace window
362 > * after the last drops).
363 > */
364 > export function buildResourceWatchChannelUri(descriptor: {
365 readonly root: string;
366 readonly recursive?: boolean;
380 return `${AHP_RESOURCE_WATCH_SCHEME}://r/${json}`;
381 }
383 > /**
384 > * Inverse of {@link buildResourceWatchChannelUri}. Returns `undefined` if
385 > * `uri` is not a well-formed `ahp-resource-watch:` URI — callers should
386 > * surface that as a not-found error to the client.
387 > */
388 > export function parseResourceWatchChannelUri(uri: string): {
389 root: string;
390 recursive: boolean;
421 }
422 }
424 > /** Returns `true` when `uri` identifies a resource-watch channel. */
425 > export function isAhpResourceWatchChannel(uri: string): boolean {
426 try {
427 return ResourceURI.parse(uri).scheme === AHP_RESOURCE_WATCH_SCHEME;
430 }
431 }
433 > /**
434 > * Returns `true` when `uri` identifies the root channel, regardless of
435 > * whether the caller passes the canonical wire form (`'ahp-root://'`) or a
436 > * variant that has been round-tripped through the workbench {@link URI} class
437 > * (which normalizes the authority-less form to `'ahp-root:'`). Always prefer
438 > * this helper over a direct `=== ROOT_STATE_URI` comparison so the two
439 > * spellings stay interchangeable.
440 > */
441 > export function isAhpRootChannel(uri: string): boolean {
442 if (uri === ROOT_STATE_URI) {
443 return true;
449 }
450 }
452 > /**
453 > * Mints a session-unique opaque id for a customization, derived from its
454 > * source URI and (when present) its `range` within the source. Plugins MAY
455 > * declare multiple children (e.g. MCP servers, hooks) inside the same
456 > * manifest file; including the range disambiguates them without an extra
457 > * mapping table.
458 > *
459 > * The range is appended as a reserved `#range=` query-style suffix; any
460 > * existing `#` in the URI is percent-encoded first so a source URI that
461 > * already contains a fragment cannot collide with a ranged id.
462 > */
463 > export function customizationId(uri: string, range?: TextRange): string {
464 if (!range) {
465 return uri;
468 return `${safeUri}#range=${range.start.line}:${range.start.character}-${range.end.line}:${range.end.character}`;
469 }
471 > // ---- VS Code-specific derived types -----------------------------------------
472 >
473 > /**
474 > * A tool call in a terminal state, stored in completed turns.
475 > */
476 > export type ICompletedToolCall = ToolCallCompletedState | ToolCallCancelledState;
477 >
478 > /**
479 > * Derived status type for the tool call lifecycle.
480 > */
481 > export type ToolCallStatusString = ToolCallState['status'];
482 >
483 > // ---- Tool output helper -----------------------------------------------------
484 >
485 > /**
486 > * Extracts a plain-text tool output string from a tool call result's `content`
487 > * array. Joins all text-type content parts into a single string.
488 > *
489 > * Returns `undefined` if there are no text content parts.
490 > */
491 > export function getToolOutputText(result: ToolCallResult): string | undefined {
492 if (!result.content || result.content.length === 0) {
493 return undefined;
504 return textParts.map(p => p.text).join('\n');
505 }
507 > /**
508 > * Extracts file edit content entries from a tool call result's `content` array.
509 > * Returns an empty array if there are no file edit content parts.
510 > */
511 > export function getToolFileEdits(result: ToolCallResult): ToolResultFileEditContent[] {
512 if (!result.content || result.content.length === 0) {
513 return [];
521 return edits;
522 }
524 > /**
525 > * Extracts the first subagent content entry from a tool call's `content` array.
526 > * Works with both completed tool call results and running tool call states.
527 > * Returns `undefined` if there are no subagent content parts.
528 > */
529 > export function getToolSubagentContent(result: { content?: readonly ToolResultContent[] }): ToolResultSubagentContent | undefined {
530 if (!result.content || result.content.length === 0) {
531 return undefined;
538 return undefined;
539 }
541 > // ---- Subagent URI helpers ---------------------------------------------------
542 >
543 > const SUBAGENT_URI_SEGMENT = 'subagent';
544 > const SUBAGENT_URI_MARKER = `/${SUBAGENT_URI_SEGMENT}/`;
545 > const SUBAGENT_URI_PATH_REGEX = /^(?<parentPath>.+)\/subagent\/(?<toolCallId>.+)$/;
546 >
547 function asResourceUri(uri: ProtocolURI | ResourceURI): ResourceURI {
548 return typeof uri === 'string' ? ResourceURI.parse(uri) : uri;
549 }
551 function getSubagentBasePath(parentSession: ProtocolURI | ResourceURI): { parent: ResourceURI; path: string } {
552 const parent = asResourceUri(parentSession);
554 return { parent, path: `${parentPath}${SUBAGENT_URI_MARKER}` };
555 }
557 > /**
558 > * Builds a subagent session URI from a parent session URI and tool call ID.
559 > * Convention: `{parentSessionUri}/subagent/{toolCallId}`
560 > */
561 > export function buildSubagentSessionUri(parentSession: ProtocolURI | ResourceURI, toolCallId: string): string {
562 const { parent, path } = getSubagentBasePath(parentSession);
563 return parent.with({ path: `${path}${toolCallId}` }).toString();
564 }
566 > /**
567 > * Parses a subagent session URI into its parent session URI and tool call ID.
568 > * Returns `undefined` if the URI does not follow the subagent convention.
569 > */
570 > export function parseSubagentSessionUri(uri: ProtocolURI | ResourceURI): { parentSession: ResourceURI; toolCallId: string } | undefined {
571 const resource = asResourceUri(uri);
572 const match = SUBAGENT_URI_PATH_REGEX.exec(resource.path);
579 };
580 }
582 > /**
583 > * Returns whether a session URI represents a subagent session.
584 > */
585 > export function isSubagentSession(uri: ProtocolURI | ResourceURI): boolean {
586 return parseSubagentSessionUri(uri) !== undefined;
587 }
589 > /**
590 > * Builds the string prefix used by the state manager for cached subagent sessions.
591 > */
592 > export function buildSubagentSessionUriPrefix(parentSession: ProtocolURI | ResourceURI): string {
593 const { parent, path } = getSubagentBasePath(parentSession);
594 return parent.with({ path }).toString();
595 }
597 > // ---- Factory helpers --------------------------------------------------------
598 >
599 > export function createRootState(): RootState {
600 > return { sessionState.ts
601 > agents: [],
602 > activeSessions: 0,
603 > };
604 > }
606 > /**
607 > * Creates the initial flat {@link SessionState} for a session from its
608 > * root-channel {@link SessionSummary} catalog entry. Session metadata
609 > * ({@link SessionMetadata}) — and the shared `_meta` bag — are inlined directly
610 > * onto the state.
611 > */
612 > export function createSessionState(summary: SessionSummary): SessionState {
613 > const state: SessionState = { sessionState.ts
614 > provider: summary.provider,
615 > title: summary.title,
616 > status: summary.status,
617 > lifecycle: SessionLifecycle.Creating,
618 > activeClients: [],
619 > chats: [],
620 > defaultChat: undefined,
621 > };
622 > if (summary.activity !== undefined) { state.activity = summary.activity; }
623 > if (summary.project !== undefined) { state.project = summary.project; }
624 > if (summary.workingDirectories !== undefined) { state.workingDirectories = summary.workingDirectories; }
625 > if (summary.annotations !== undefined) { state.annotations = summary.annotations; }
626 > if (summary._meta !== undefined) { state._meta = summary._meta; }
627 > return state;
628 > }
630 > /**
631 > * Creates an empty {@link ChatState} for a chat. The summary fields are
632 > * denormalized onto the chat state per the protocol contract; callers pass
633 > * the chat's catalog summary and this seeds an empty conversation.
634 > */
635 > export function createChatState(summary: ChatSummary): ChatState {
636 > return { sessionState.ts
637 > resource: summary.resource,
638 > title: summary.title,
639 > status: summary.status,
640 > activity: summary.activity,
641 > modifiedAt: summary.modifiedAt,
642 > origin: summary.origin,
643 > interactivity: summary.interactivity,
644 > workingDirectories: summary.workingDirectories,
645 > primaryWorkingDirectory: summary.primaryWorkingDirectory,
646 > turns: [],
647 > activeTurn: undefined,
648 > };
649 > }
651 > /**
652 > * Derives the default-chat {@link ChatSummary} for a session from its
653 > * {@link SessionSummary}. The default chat inherits the session's title,
654 > * status, activity and working directory, and is marked as a
655 > * {@link ChatOriginKind.User | user-originated} chat. Both the session and
656 > * chat `modifiedAt` are ISO-8601 strings, so it is carried over directly.
657 > */
658 > export function createDefaultChatSummary(session: SessionSummary, chatUri: ProtocolURI): ChatSummary {
659 > const summary: ChatSummary = { sessionState.ts
660 > resource: chatUri,
661 > title: session.title,
662 > status: session.status,
663 > modifiedAt: session.modifiedAt,
664 > origin: { kind: ChatOriginKind.User },
665 > };
666 > if (session.activity !== undefined) { summary.activity = session.activity; }
667 > // `workingDirectories` is deliberately NOT copied: per the protocol it is a
668 > // per-chat SUBSET override and, when absent, the chat inherits the session's
669 > // full set of working directories (see `mergeSessionWithDefaultChat`).
670 > // Seeding it here would denormalize the session default onto every chat as a
671 > // fake override, which then goes stale when the session's working
672 > // directories are resolved later (e.g. a worktree resolved at
673 > // materialization). `primaryWorkingDirectory` is per-chat and fixed at chat
674 > // creation (the session has no primary), so it is likewise not seeded here.
675 > return summary;
676 > }
678 > /** Activity bits (0-4) of {@link SessionStatus}; the high bits carry orthogonal flags (IsRead / IsArchived). */
679 > const STATUS_ACTIVITY_MASK = (1 << 5) - 1;
680 >
681 > /** Whether the active turn has a `PendingConfirmation` tool call auto-approved by the session's bypass setting. */
682 function hasAutoApprovedPendingConfirmation(state: ChatState): boolean {
683 return !!state.activeTurn?.responseParts.some(part =>
687 );
688 }
690 > /** Whether the chat is genuinely blocked on user input (an open input request, an auth-required tool, or a non-auto-approved confirmation gate). */
691 function chatAwaitsUserInput(state: ChatState): boolean {
692 return !!state.activeTurn?.responseParts.some(part => {
708 });
709 }
711 > /**
712 > * Projects a chat's status for session-summary aggregation, demoting an
713 > * `InputNeeded` back to `InProgress` only when it is caused solely by an
714 > * auto-approved confirmation — otherwise a session with bypass approvals flashes
715 > * "input needed" in the sessions list while an auto-approved tool runs.
716 > */
717 > function chatSummaryStatus(state: ChatState): SessionStatus { sessionState.ts
718 > const status = state.status;
719 > if ((status & SessionStatus.InputNeeded) !== SessionStatus.InputNeeded) {
720 > return status; sessionState.ts
721 > }
722 // Only demote when we can positively attribute the InputNeeded to an
723 // auto-approved confirmation with no genuine blocker present; otherwise (e.g.
724 // a restored summary whose activeTurn is not loaded) preserve the status.
725 > if (hasAutoApprovedPendingConfirmation(state) && !chatAwaitsUserInput(state)) { sessionState.ts
726 return (status & ~STATUS_ACTIVITY_MASK) | SessionStatus.InProgress;
727 }
728 return status;
729 }
731 > /**
732 > * Derives a {@link ChatSummary} from a fully-populated {@link ChatState} by
733 > * projecting out the denormalized summary fields. Used to keep the parent
734 > * session's `chats` catalog in sync with a chat's denormalized state.
735 > */
736 > export function chatSummaryFromState(state: ChatState): ChatSummary {
737 > const summary: ChatSummary = { sessionState.ts
738 > resource: state.resource,
739 > title: state.title,
740 > status: chatSummaryStatus(state),
741 > modifiedAt: state.modifiedAt,
742 > };
743 > if (state.activity !== undefined) { summary.activity = state.activity; }
744 > if (state.origin !== undefined) { summary.origin = state.origin; }
745 > if (state.interactivity !== undefined) { summary.interactivity = state.interactivity; }
746 > if (state.workingDirectories !== undefined) { summary.workingDirectories = state.workingDirectories; }
747 > if (state.primaryWorkingDirectory !== undefined) { summary.primaryWorkingDirectory = state.primaryWorkingDirectory; }
748 > return summary;
749 > }
751 > /**
752 > * The effective interactivity of a chat given its session's archived state.
753 > *
754 > * `interactivity` is the general read-only mechanism (e.g. subagent worker
755 > * chats are `ReadOnly`). An archived session is read-only too, so its
756 > * interactive chats are downgraded to `ReadOnly`. `Hidden` chats stay hidden —
757 > * archiving only downgrades `Full` chats. Absent interactivity defaults to
758 > * `Full` for backward compatibility.
759 > *
760 > * The host uses this to enforce read-only turns off a single signal
761 > * ({@link isChatReadOnly}) rather than special-casing archived; the same rule
762 > * is mirrored client-side to hide the composer.
763 > */
764 > export function effectiveChatInteractivity(interactivity: ChatInteractivity | undefined, sessionArchived: boolean): ChatInteractivity {
765 if (interactivity === ChatInteractivity.Hidden) {
766 return ChatInteractivity.Hidden;
771 return interactivity ?? ChatInteractivity.Full;
772 }
774 > /**
775 > * Whether a chat rejects user-dispatched turns, given its own interactivity and
776 > * its session's archived state. `true` for `ReadOnly` chats (including archived
777 > * sessions' interactive chats). See {@link effectiveChatInteractivity}.
778 > */
779 > export function isChatReadOnly(interactivity: ChatInteractivity | undefined, sessionArchived: boolean): boolean {
780 return effectiveChatInteractivity(interactivity, sessionArchived) === ChatInteractivity.ReadOnly;
781 }
783 > export function createActiveTurn(id: string, message: Message, startedAt: string): ActiveTurn {
784 return {
785 id,
790 };
791 }
793 > export const enum StateComponents {
794 > Root,
795 > Session,
796 > Chat,
797 > Terminal,
798 > Changeset,
799 > Annotations,
800 > }
801 >
802 > export type ComponentToState = {
803 > [StateComponents.Root]: RootState;
804 > [StateComponents.Session]: SessionState;
805 > [StateComponents.Chat]: ChatState;
806 > [StateComponents.Terminal]: TerminalState;
807 > [StateComponents.Changeset]: ChangesetState;
808 > [StateComponents.Annotations]: AnnotationsState;
809 > };
810 >
811 > // ---- Default chat URI helpers ----------------------------------------------
812 >
813 > /** Scheme used by chat channel URIs (`ahp-chat://...`). */
814 > export const AHP_CHAT_SCHEME = 'ahp-chat';
815 >
816 > /** Chat id of the default chat that every session owns. */
817 > export const DEFAULT_CHAT_ID = 'default';
818 >
819 > /**
820 > * Derives the deterministic channel URI for a chat within a session. Every chat
821 > * — the default chat and any additional peer chats — encodes its owning session
822 > * URI into the path so producers and consumers can recover the session without a
823 > * lookup table (see {@link parseChatUri}). The chat id is carried in the URI
824 > * authority.
825 > *
826 > * `ahp-chat://<chatId>/<base64(sessionUri)>`
827 > */
828 > export function buildChatUri(sessionUri: ProtocolURI | ResourceURI, chatId: string): string {
829 > const session = typeof sessionUri === 'string' ? sessionUri : sessionUri.toString(); sessionState.ts
830 > const encoded = encodeBase64(VSBuffer.fromString(session), false, true);
831 > return `${AHP_CHAT_SCHEME}://${chatId}/${encoded}`;
832 > }
834 > /**
835 > * Derives the deterministic default-chat channel URI for a session. While the
836 > * protocol allows a session to contain many chats, every session always owns a
837 > * default chat whose URI is derived from the owning session URI so producers and
838 > * consumers can compute it without a lookup table.
839 > *
840 > * The session URI is encoded into the path so {@link parseChatUri} can recover
841 > * it.
842 > */
843 > export function buildDefaultChatUri(sessionUri: ProtocolURI | ResourceURI): string {
844 > return buildChatUri(sessionUri, DEFAULT_CHAT_ID); sessionState.ts
845 > }
847 > const SUBAGENT_CHAT_ID = 'subagent';
848 >
849 > export function isSubagentChatUri(uri: ProtocolURI | ResourceURI): boolean {
850 const parsed = typeof uri === 'string' ? ResourceURI.parse(uri) : uri;
851 return parsed.scheme === AHP_CHAT_SCHEME && parsed.authority === SUBAGENT_CHAT_ID;
852 }
854 > export function buildSubagentChatUri(sessionUri: ProtocolURI | ResourceURI, toolCallId: string): string {
855 const session = typeof sessionUri === 'string' ? sessionUri : sessionUri.toString();
856 const encoded = encodeBase64(VSBuffer.fromString(session), false, true);
857 return `${AHP_CHAT_SCHEME}://${SUBAGENT_CHAT_ID}/${encoded}/${encodeURIComponent(toolCallId)}`;
858 }
860 > /**
861 > * Inverse of {@link buildChatUri}: recovers the owning session URI and chat id
862 > * from any chat channel URI. Returns `undefined` when `uri` is not a well-formed
863 > * chat URI.
864 > */
865 > export function parseChatUri(uri: ProtocolURI | ResourceURI): { session: string; chatId: string } | undefined {
866 > let parsed: ResourceURI; sessionState.ts
867 > try {
868 > parsed = typeof uri === 'string' ? ResourceURI.parse(uri) : uri;
869 > } catch {
870 return undefined;
871 }
872 > if (parsed.scheme !== AHP_CHAT_SCHEME || !parsed.authority) { sessionState.ts
873 return undefined;
874 }
875 > const encoded = parsed.path.replace(/^\//, ''); sessionState.ts
876 > if (!encoded) {
877 return undefined;
878 }
879 > try { sessionState.ts
880 > if (parsed.authority === SUBAGENT_CHAT_ID) {
881 const [sessionPart, ...toolCallIdParts] = encoded.split('/');
882 const toolCallId = toolCallIdParts.join('/');
886 return { session: decodeBase64(sessionPart).toString(), chatId: `${SUBAGENT_CHAT_ID}/${decodeURIComponent(toolCallId)}` };
887 }
888 > return { session: decodeBase64(encoded).toString(), chatId: parsed.authority }; sessionState.ts
889 > } catch {
890 return undefined;
891 }
892 > } sessionState.ts
894 > /**
895 > * Inverse of {@link buildDefaultChatUri}: recovers the owning session URI from a
896 > * chat channel URI. Returns `undefined` when `uri` is not a well-formed chat URI.
897 > * Accepts any chat URI (default or additional) so callers that only need the
898 > * parent session can use it uniformly.
899 > */
900 > export function parseDefaultChatUri(uri: ProtocolURI | ResourceURI): string | undefined {
901 > return parseChatUri(uri)?.session; sessionState.ts
902 > }
904 > export function parseRequiredSessionUriFromChatUri(uri: ProtocolURI | ResourceURI): string {
905 > const session = parseDefaultChatUri(uri); sessionState.ts
906 > if (session === undefined) {
907 throw new Error(`Malformed AHP chat URI: ${typeof uri === 'string' ? uri : uri.toString()}`);
908 }
909 > return session; sessionState.ts
910 > }
912 > /** Returns `true` when `uri` is the default chat of its session. */
913 > export function isDefaultChatUri(uri: ProtocolURI | ResourceURI): boolean {
914 return parseChatUri(uri)?.chatId === DEFAULT_CHAT_ID;
915 }
917 > /**
918 > * Resolves a feature-level `(session, chat)` pair to the single chat URI used by
919 > * the agent session/chat surface. A session always owns a DEFAULT chat addressed
920 > * by the session URI itself; additional (peer) chats are addressed by their own
921 > * chat channel URIs. This is the one place default-chat resolution lives so
922 > * agents never re-derive "is this the default chat?".
923 > */
924 > export function resolveChatUri(session: ResourceURI, chat: ResourceURI): ResourceURI {
925 return isDefaultChatUri(chat) ? session : chat;
926 }
928 > /** Returns `true` when `uri` identifies a chat channel. */
929 > export function isAhpChatChannel(uri: string): boolean {
930 > try { sessionState.ts
931 > return ResourceURI.parse(uri).scheme === AHP_CHAT_SCHEME;
932 > } catch {
933 return false;
934 }
935 > } sessionState.ts
937 > // ---- Session + default-chat composite --------------------------------------
938 >
939 > /**
940 > * A single chat's effective session context: the shared {@link SessionState}
941 > * (working directories, active clients, config, customizations/MCP scope, …)
942 > * resolved for one chat and merged with that chat's conversation contents.
943 > *
944 > * The protocol moved turns and pending state off the session and onto a
945 > * per-chat channel, and lets a chat override the session's working directories
946 > * with a subset (e.g. {@link ChatState.workingDirectories}) and carry its own
947 > * read-only {@link ChatState.primaryWorkingDirectory | primary} (fixed at chat
948 > * creation — the session has no primary). This composite recombines the session
949 > * with one of its chats — default or peer — so consumers read the chat's
950 > * effective context and conversation through one object without walking back to
951 > * the session to re-derive shared state. The {@link ISessionWithDefaultChat.workingDirectories}
952 > * carry the chat's *effective* working directories (its own subset override when
953 > * present, else the session's full set); {@link ISessionWithDefaultChat.primaryWorkingDirectory}
954 > * is the chat's own primary.
955 > */
956 > export interface ISessionWithDefaultChat extends SessionState {
957 > /** The chat's read-only primary working directory (fixed at chat creation). */
958 > primaryWorkingDirectory?: ProtocolURI;
959 > /** Completed turns of this chat. */
960 > turns: Turn[];
961 > /** Currently in-progress turn of this chat. */
962 > activeTurn?: ActiveTurn;
963 > /** Steering message pending on this chat. */
964 > steeringMessage?: PendingMessage;
965 > /** Queued messages pending on this chat. */
966 > queuedMessages?: PendingMessage[];
967 > /** Draft input of this chat. */
968 > draft?: Message;
969 > }
970 >
971 > /**
972 > * Projects a {@link SessionState} and one of its {@link ChatState | chats}
973 > * (default or peer) into that chat's {@link ISessionWithDefaultChat | effective
974 > * session context}. Per-chat overrides (the working-directories subset and the
975 > * chat's own primary) are layered over the session defaults, and the
976 > * conversation fields are taken from the chat. When the chat state is absent
977 > * (e.g. not yet hydrated) the conversation fields default to empty and the
978 > * session defaults apply.
979 > */
980 > export function mergeSessionWithDefaultChat(session: SessionState, chat: ChatState | undefined): ISessionWithDefaultChat {
981 return {
982 ...session,
990 };
991 }
993 > /**
994 > * Resolves the active turn of a session's default chat, if any.
995 > */
996 > export function getActiveTurn(chat: ChatState | undefined): ActiveTurn | undefined {
997 return chat?.activeTurn;
998 }
1000 > /**
1001 > * Resolves the default chat's catalog summary from a session, if present.
1002 > */
1003 > export function getDefaultChat(session: SessionState): ChatSummary | undefined {
1004 if (session.defaultChat !== undefined) {
1005 const match = session.chats.find(c => c.resource === session.defaultChat);
1010 return session.chats[0];
1011 }
1013 > // ---- SessionMeta accessors -------------------------------------------------
1014 >
1015 > /**
1016 > * VS Code-side alias for the protocol's open `_meta` property bag on
1017 > * {@link SessionState}. Keys SHOULD be namespaced (e.g. `git`, `vscode.foo`)
1018 > * to avoid collisions; values MUST be JSON-serializable.
1019 > */
1020 > export type SessionMeta = Record<string, unknown>;
1021 >
1022 > /**
1023 > * VS Code-side alias for the protocol's open `_meta` property bag on
1024 > * {@link SessionSummary}. Keys SHOULD be namespaced (e.g. `git`, `vscode.foo`)
1025 > * to avoid collisions; values MUST be JSON-serializable.
1026 > */
1027 > export type SessionSummaryMeta = Record<string, unknown>;
1028 >
1029 > /**
1030 > * Reserved key under {@link SessionMeta} for the well-known git-state
1031 > * payload. Value at this key, when present, MUST be shaped like
1032 > * {@link ISessionGitState}. This is a VS Code-specific convention layered
1033 > * on top of the protocol's generic `_meta` bag — the protocol itself does
1034 > * not know about git state.
1035 > */
1036 > export const SESSION_META_GIT_KEY = 'git';
1037 >
1038 > /**
1039 > * Reserved key under {@link SessionMeta} for the well-known GitHub-state
1040 > * payload. Value at this key, when present, MUST be shaped like
1041 > * {@link ISessionGitHubState}. This is a VS Code-specific convention layered
1042 > * on top of the protocol's generic `_meta` bag — the protocol itself does
1043 > * not know about GitHub state.
1044 > */
1045 > export const SESSION_META_GITHUB_KEY = 'github';
1046 >
1047 > export const SESSION_META_PROMPT_CACHE_KEY = 'vscode.promptCache';
1048 >
1049 > /** Latest known prompt-cache state for the model active in an agent session. */
1050 > export interface ISessionPromptCacheState {
1051 > readonly modelId: string;
1052 > readonly cacheExpiresAt: string;
1053 > }
1054 >
1055 > /** Reads the latest known prompt-cache state from session metadata. */
1056 > export function readSessionPromptCacheState(meta: SessionMeta | undefined): ISessionPromptCacheState | undefined {
1057 const value = meta?.[SESSION_META_PROMPT_CACHE_KEY];
1058 if (!value || typeof value !== 'object' || Array.isArray(value)) {
1064 : undefined;
1065 }
1067 > /** Returns session metadata with the prompt-cache slot updated or removed. */
1068 > export function withSessionPromptCacheState(meta: SessionMeta | undefined, promptCache: ISessionPromptCacheState | undefined): SessionMeta | undefined {
1069 const next: SessionMeta = { ...meta };
1070 if (promptCache) {
1075 return Object.keys(next).length > 0 ? next : undefined;
1076 }
1078 > /**
1079 > * Git state of a session's working directory, carried under
1080 > * {@link SessionMeta} at {@link SESSION_META_GIT_KEY}. Used by clients to
1081 > * drive source-control affordances (e.g. PR/merge buttons in the Agents
1082 > * app).
1083 > *
1084 > * All fields are optional — agents that do not track a particular field
1085 > * should omit it rather than send a placeholder, so clients can distinguish
1086 > * "unknown" from "known to be zero".
1087 > */
1088 > export interface ISessionGitState {
1089 > /** Whether the working directory has a `github.com` git remote. */
1090 > readonly hasGitHubRemote?: boolean;
1091 > /** Current branch name. */
1092 > readonly branchName?: string;
1093 > /** Base branch the work targets (e.g. `main`). */
1094 > readonly baseBranchName?: string;
1095 > /** Upstream tracking branch (e.g. `origin/feature`). */
1096 > readonly upstreamBranchName?: string;
1097 > /** Number of commits the upstream branch has ahead of the local branch. */
1098 > readonly incomingChanges?: number;
1099 > /** Number of commits the local branch has ahead of the upstream branch. */
1100 > readonly outgoingChanges?: number;
1101 > /** Number of files with uncommitted changes. */
1102 > readonly uncommittedChanges?: number;
1103 > /** GitHub repository owner parsed from the working copy's GitHub remote (preferring `origin`, falling back to the first GitHub remote). */
1104 > readonly githubOwner?: string;
1105 > /** GitHub repository name parsed from the working copy's GitHub remote (preferring `origin`, falling back to the first GitHub remote). */
1106 > readonly githubRepo?: string;
1107 > }
1108 >
1109 > /**
1110 > * GitHub state of a session, carried under {@link SessionMeta} at
1111 > * {@link SESSION_META_GITHUB_KEY}. Used by clients to drive GitHub-specific
1112 > * affordances (e.g. PR/merge buttons in the Agents app).
1113 > *
1114 > * All fields are optional — agents that do not track a particular field
1115 > * should omit it rather than send a placeholder, so clients can distinguish
1116 > * "unknown" from "known to be zero".
1117 > */
1118 > export interface ISessionGitHubState {
1119 > /** The owner of the GitHub repository. */
1120 > readonly owner?: string;
1121 > /** The name of the GitHub repository. */
1122 > readonly repo?: string;
1123 > /** The URL of the GitHub pull request. */
1124 > readonly pullRequestUrl?: string;
1125 > }
1126 >
1127 > /**
1128 > * Reads the well-known git-state payload from {@link SessionMeta}, if
1129 > * present. Returns `undefined` when the meta bag is absent or the value at
1130 > * the git key is not a plain object (e.g. an array or a primitive).
1131 > * Individual fields with wrong types are silently dropped so partial state
1132 > * still propagates.
1133 > *
1134 > * Unlike the other typed readers, this takes the raw {@link SessionMeta} value
1135 > * rather than its parent {@link SessionState}: the sessions provider stores and
1136 > * reads a detached meta snapshot without retaining the owning state.
1137 > */
1138 > export function readSessionGitState(meta: SessionMeta | undefined): ISessionGitState | undefined {
1139 const value = meta?.[SESSION_META_GIT_KEY];
1140 if (!value || typeof value !== 'object' || Array.isArray(value)) {
1164 return result;
1165 }
1167 > /**
1168 > * Returns a new {@link SessionMeta} with the git-state payload set to
1169 > * `gitState`, or with the git slot removed if `gitState` is `undefined`.
1170 > * Returns `undefined` if the result would be empty.
1171 > */
1172 > export function withSessionGitState(meta: SessionMeta | undefined, gitState: ISessionGitState | undefined): SessionMeta | undefined {
1173 const next: { [key: string]: unknown } = { ...meta };
1174 if (gitState !== undefined) {
1179 return Object.keys(next).length > 0 ? next : undefined;
1180 }
1182 > /**
1183 > * Reads the well-known GitHub state payload from {@link SessionSummaryMeta}, if
1184 > * present. Returns `undefined` when the meta bag is absent or the value at the
1185 > * GitHub key is not a plain object (e.g. an array or a primitive).
1186 > * Individual fields with wrong types are silently dropped so partial state
1187 > * still propagates.
1188 > *
1189 > * Unlike the other typed readers, this takes the raw {@link SessionSummaryMeta}
1190 > * value rather than its parent {@link SessionState}: the sessions provider stores and
1191 > * reads a detached meta snapshot without retaining the owning state.
1192 > */
1193 > export function readSessionGitHubState(meta: SessionSummaryMeta | undefined): ISessionGitHubState | undefined {
1194 const value = meta?.[SESSION_META_GITHUB_KEY];
1195 if (!value || typeof value !== 'object' || Array.isArray(value)) {
1208 return result;
1209 }
1211 > /**
1212 > * Returns a new {@link SessionSummaryMeta} with the GitHub-state payload set to
1213 > * `gitHubState`, or with the GitHub slot removed if `gitHubState` is `undefined`.
1214 > * Returns `undefined` if the result would be empty.
1215 > */
1216 > export function withSessionGitHubState(meta: SessionSummaryMeta | undefined, gitHubState: ISessionGitHubState | undefined): SessionSummaryMeta | undefined {
1217 const next: { [key: string]: unknown } = { ...meta };
1218 if (gitHubState !== undefined) {
1223 return Object.keys(next).length > 0 ? next : undefined;
1224 }
1226 > /**
1227 > * Reserved key under {@link SessionSummaryMeta} recording how deeply a session
1228 > * was spawned via the `create_session` host tool (0 for a top-level, user-created
1229 > * session). Used to bound recursive session creation. VS Code-specific convention
1230 > * layered on top of the protocol's generic `_meta` bag.
1231 > */
1232 > export const SESSION_META_SPAWN_DEPTH_KEY = 'agentHost/sessionSpawnDepth';
1233 >
1234 > /**
1235 > * Reads the `create_session` spawn depth from a {@link SessionSummaryMeta} bag,
1236 > * returning `0` when the key is absent or not a finite number.
1237 > */
1238 > export function readSessionSpawnDepth(meta: SessionSummaryMeta | undefined): number {
1239 const value = meta?.[SESSION_META_SPAWN_DEPTH_KEY];
1240 return typeof value === 'number' && Number.isFinite(value) ? value : 0;
1241 }
1243 > /**
1244 > * Returns a new {@link SessionSummaryMeta} with the `create_session` spawn depth
1245 > * set to `depth`, preserving any other keys in the bag.
1246 > */
1247 > export function withSessionSpawnDepth(meta: SessionSummaryMeta | undefined, depth: number): SessionSummaryMeta {
1248 return { ...meta, [SESSION_META_SPAWN_DEPTH_KEY]: depth };
1249 }
1251 > /**
1252 > * Reserved key under {@link SessionSummaryMeta} marking a session as
1253 > * workspace-less: a session with no workspace/folder binding (surfaced in the
1254 > * UI as a "Quick Chat"). Carried on the summary bag (not the full state) so
1255 > * clients can group/style such sessions in session lists without subscribing to
1256 > * full session state. VS Code-specific convention layered on the protocol's
1257 > * generic `_meta` bag.
1258 > */
1259 > export const SESSION_META_WORKSPACELESS_KEY = 'workspaceless';
1260 >
1261 > /**
1262 > * Session-database metadata key recording whether a session is workspace-less (a
1263 > * workspace-less chat). Owned by the AH service: `AgentService` writes it centrally at
1264 > * create/materialize and overlays it onto every agent's summary `_meta` in
1265 > * `listSessions`; agents only read it (e.g. to pick the workspace-less system prompt
1266 > * on resume) and never persist it themselves.
1267 > */
1268 > export const AH_META_WORKSPACELESS_DB_KEY = 'agentHost.workspaceless';
1269 >
1270 > /**
1271 > * Session-database metadata key recording whether a session is archived. Written by
1272 > * the AH orchestrator (`AgentSideEffects` on `SessionIsArchivedChanged`) and read by
1273 > * both the orchestrator (`AgentService` restore/list) and agents (e.g. `CopilotAgent`
1274 > * decides whether to recreate a missing worktree vs. resume read-only for history).
1275 > * {@link AH_META_IS_DONE_DB_KEY} is the legacy name kept for sessions persisted before
1276 > * the rename; readers fall back to it when {@link AH_META_IS_ARCHIVED_DB_KEY} is absent.
1277 > */
1278 > export const AH_META_IS_ARCHIVED_DB_KEY = 'isArchived';
1279 >
1280 > /** Legacy metadata key for the archived flag; see {@link AH_META_IS_ARCHIVED_DB_KEY}. */
1281 > export const AH_META_IS_DONE_DB_KEY = 'isDone';
1282 >
1283 > /**
1284 > * Reads the workspace-less marker from {@link SessionSummaryMeta}. Returns
1285 > * `true` only when the well-known key is present and set to boolean `true`.
1286 > */
1287 > export function readSessionWorkspaceless(meta: SessionSummaryMeta | undefined): boolean {
1288 return meta?.[SESSION_META_WORKSPACELESS_KEY] === true;
1289 }
1291 > /**
1292 > * Returns a new {@link SessionSummaryMeta} with the workspace-less marker set,
1293 > * or with the slot removed when `workspaceless` is `false`. Returns `undefined`
1294 > * if the result would be empty.
1295 > */
1296 > export function withSessionWorkspaceless(meta: SessionSummaryMeta | undefined, workspaceless: boolean): SessionSummaryMeta | undefined {
1297 const next: { [key: string]: unknown } = { ...meta };
1298 if (workspaceless) {
1303 return Object.keys(next).length > 0 ? next : undefined;
1304 }
1306 > // ---- RootState _meta accessors ---------------------------------------------
1307 >
1308 > /**
1309 > * VS Code-side alias for the protocol's open `_meta` property bag on
1310 > * {@link RootState}. Keys SHOULD be namespaced to avoid collisions; values MUST
1311 > * be JSON-serializable.
1312 > */
1313 > export type RootMeta = Record<string, unknown>;
1314 >
1315 > /**
1316 > * Reserved key under {@link RootMeta} for the well-known host-build payload.
1317 > * Value at this key, when present, MUST be shaped like {@link IHostBuildInfo}.
1318 > * This is a VS Code-specific convention layered on top of the protocol's
1319 > * generic `_meta` bag — the protocol itself does not know about build info.
1320 > */
1321 > export const ROOT_META_HOST_BUILD_KEY = 'hostBuild';
1322 >
1323 > /**
1324 > * Build information about the program hosting the agent host (the VS Code CLI),
1325 > * carried under {@link RootMeta} at {@link ROOT_META_HOST_BUILD_KEY}. Lets a
1326 > * client see which build is hosting it — useful when inspecting the output of a
1327 > * remote agent host.
1328 > *
1329 > * All fields except {@link version} are optional — a build that does not track
1330 > * a particular field should omit it.
1331 > */
1332 > export interface IHostBuildInfo {
1333 > /** Product version (e.g. `1.96.0`). */
1334 > readonly version: string;
1335 > /** Commit SHA of the build, if known. */
1336 > readonly commit?: string;
1337 > /** Build date (ISO 8601), if known. */
1338 > readonly date?: string;
1339 > /** Release quality (e.g. `stable`, `insider`), if known. */
1340 > readonly quality?: string;
1341 > }
1342 >
1343 > /**
1344 > * Derives {@link IHostBuildInfo} from the host's {@link IProductService}.
1345 > */
1346 > export function hostBuildInfoFromProduct(productService: IProductService): IHostBuildInfo {
1347 return {
1348 version: productService.version,
1352 };
1353 }
1355 > /**
1356 > * Reads the well-known host-build payload from {@link RootMeta}, if present.
1357 > * Returns `undefined` when the meta bag is absent or the value at the host-build
1358 > * key is not a plain object with a string `version`. Optional fields with wrong
1359 > * types are silently dropped.
1360 > */
1361 > export function readHostBuildInfo(state: RootState | undefined): IHostBuildInfo | undefined {
1362 const meta = state?._meta;
1363 const value = meta?.[ROOT_META_HOST_BUILD_KEY];
1377 return result;
1378 }
1380 > /**
1381 > * Returns a new {@link RootMeta} with the host-build payload set to
1382 > * `buildInfo`, or with the slot removed if `buildInfo` is `undefined`. Returns
1383 > * `undefined` if the result would be empty.
1384 > */
1385 > export function withHostBuildInfo(meta: RootMeta | undefined, buildInfo: IHostBuildInfo | undefined): RootMeta | undefined {
1386 > const next: { [key: string]: unknown } = { ...meta }; sessionState.ts
1387 > if (buildInfo !== undefined) {
1388 next[ROOT_META_HOST_BUILD_KEY] = buildInfo;
1389 > } else { sessionState.ts
1390 > delete next[ROOT_META_HOST_BUILD_KEY]; sessionState.ts
1391 > }
1392 > return Object.keys(next).length > 0 ? next : undefined; sessionState.ts
1393 > }
1395 > /**
1396 > * Formats {@link IHostBuildInfo} as a short single-line human-readable string,
1397 > * e.g. `1.96.0 (commit abc1234, 2024-01-02T03:04:05Z, insider)`.
1398 > */
1399 > export function formatHostBuildInfo(info: IHostBuildInfo): string {
1400 const details: string[] = [];
1401 if (info.commit) { details.push(`commit ${info.commit}`); }
src/vs/platform/agentHost/node/agentHostStateManager.ts 923 covered LOC · 113 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentHostStateManager.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { RunOnceScheduler } from '../../../base/common/async.js';
7 > import { Emitter, Event } from '../../../base/common/event.js';
8 > import { Disposable } from '../../../base/common/lifecycle.js';
9 > import { equals } from '../../../base/common/objects.js';
10 > import { ILogService } from '../../log/common/log.js';
11 > import { createDecorator } from '../../instantiation/common/instantiation.js';
12 > import { TelemetryLevel } from '../../telemetry/common/telemetry.js';
13 > import { ActionType, ActionEnvelope, ActionOrigin, INotification, IRootConfigChangedAction, SessionAction, ChatAction, RootAction, StateAction, TerminalAction, ChangesetAction, ClientChangesetAction, AnnotationsAction, ClientAnnotationsAction, isRootAction, isSessionAction, isChatAction, isChangesetAction, isAnnotationsAction, type AuthRequiredParams, type ProgressParams } from '../common/state/sessionActions.js';
14 > import type { IStateSnapshot } from '../common/state/sessionProtocol.js';
15 > import { rootReducer, sessionReducer, chatReducer, changesetReducer, annotationsReducer } from '../common/state/sessionReducers.js';
16 > import { createRootState, createSessionState, createChatState, createDefaultChatSummary, chatSummaryFromState, buildDefaultChatUri, parseDefaultChatUri, parseRequiredSessionUriFromChatUri, isAhpChatChannel, isDefaultChatUri, mergeSessionWithDefaultChat, isAhpRootChannel, SessionLifecycle, withHostBuildInfo, type Changeset, type ChangesetState, type AnnotationsState, type ChatState, type ChatSummary, type Customization, type ISessionWithDefaultChat, type Message, type RootState, type SessionConfigState, type SessionMeta, type SessionState, type SessionSummary, type Turn, type URI, ROOT_STATE_URI, ChangesetStatus, IHostBuildInfo, SessionStatus } from '../common/state/sessionState.js';
17 > import { AgentHostTelemetryLevelConfigKey, IPermissionsValue, platformRootSchema, telemetryLevelToAgentHostConfigValue } from '../common/agentHostSchema.js';
18 > import { SessionConfigKey } from '../common/sessionConfigKeys.js';
19 > import { parseChangesetUri } from '../common/changesetUri.js';
20 > import { buildAnnotationsUri, isAnnotationsUri } from '../common/annotationsUri.js';
21 > import { AgentHostChangesetStateCache, type IAgentHostChangesetStateRetentionOptions } from './agentHostChangesetStateCache.js';
22 > import { ChangesSummary, ChatInteractivity, type ChatOrigin } from '../common/state/protocol/state.js';
23 > import { arrayEquals, structuralEquals } from '../../../base/common/equals.js';
24 > import { preserveProviderBackedRootConfigValues } from '../common/agentCustomizationSettings.js';
25 >
26 > export interface IAgentHostStateManagerOptions {
27 > readonly changesetStateRetention?: IAgentHostChangesetStateRetentionOptions;
28 > /**
29 > * Build information about the program hosting the agent host. When
30 > * provided, it is published on {@link RootState._meta} so clients can see
31 > * which build is hosting them.
32 > */
33 > readonly hostBuildInfo?: IHostBuildInfo;
34 > }
35 >
36 > /**
37 > * Authoritative per-session record held by the state manager. Bundles the flat
38 > * {@link SessionState} with the {@link SessionSummary} catalog-only fields that
39 > * do not live on the state. The session URI (catalog `resource`) is the map
40 > * key, and the catalog `_meta` is the same object as {@link SessionState._meta},
41 > * so the only extra fields the record carries are the timestamps and the
42 > * aggregate change counts.
43 > */
44 > interface ISessionEntry {
45 > state: SessionState;
46 > /** Creation timestamp (ISO 8601). Catalog-only; immutable after creation. */
47 > readonly createdAt: string;
48 > /** Last modification timestamp (ISO 8601). Catalog-only; derived from chat aggregation. */
49 > modifiedAt: string;
50 > /** Aggregate file-change counts for the session-wide changeset. Catalog-only. */
51 > changes?: ChangesSummary;
52 > }
53 >
54 > /**
55 > * Encapsulates the root-channel summary-notification bookkeeping for the
56 > * {@link AgentHostStateManager}: the last {@link SessionSummary} announced to
57 > * clients per session (the diff baseline) and the set of sessions whose summary
58 > * changed since the last debounced flush. The snapshot map and the dirty set
59 > * are always mutated in lockstep, so keeping them together — rather than as two
60 > * loose fields on the manager — keeps the diffing state cohesive.
61 > *
62 > * The current summary for a session is sourced via the injected `getSummary`
63 > * callback; diff-based `root/sessionSummaryChanged` notifications are emitted
64 > * through `emit`.
65 > */
66 > class SessionSummaryNotifier extends Disposable {
67 >
68 > /** Last summary announced to clients (via sessionAdded or sessionSummaryChanged). */
69 > private readonly _lastNotified = new Map<string, SessionSummary>();
70 >
71 > /** Sessions whose summary changed since the last flush. */
72 > private readonly _dirty = new Set<string>();
73 >
74 > private readonly _scheduler = this._register(new RunOnceScheduler(() => this._flushAll(), 100));
75 >
76 > constructor(
77 > private readonly _getSummary: (session: string) => SessionSummary | undefined, agentHostStateManager.ts
78 > private readonly _emit: (session: string, changes: Partial<SessionSummary>) => void,
79 > ) {
80 > super();
81 > }
83 > /** Records `summary` as the last value announced to clients for `session`. */
84 > announce(session: string, summary: SessionSummary): void {
85 > this._lastNotified.set(session, summary); agentHostStateManager.ts
86 > }
88 > /** Whether `session` has already been announced to clients. */
89 > isAnnounced(session: string): boolean {
90 return this._lastNotified.has(session);
91 }
93 > /** Marks `session` dirty and schedules a debounced flush. */
94 > markDirty(session: string): void {
95 > this._dirty.add(session); agentHostStateManager.ts
96 > this._scheduler.schedule();
97 > }
99 > /** Whether `session` has a pending (unflushed) summary change. */
100 > isDirty(session: string): boolean {
101 return this._dirty.has(session);
102 }
104 > /** Drops the pending dirty flag for `session` without flushing it. */
105 > clearDirty(session: string): void {
106 this._dirty.delete(session);
107 }
109 > /** Drops all notification bookkeeping for `session`. */
110 > remove(session: string): void {
111 this._lastNotified.delete(session);
112 this._dirty.delete(session);
113 }
115 > private _flushAll(): void {
116 for (const session of this._dirty) {
117 this.flush(session);
119 this._dirty.clear();
120 }
122 > /**
123 > * Emits a `root/sessionSummaryChanged` notification for `session` if its
124 > * current summary differs from the last announced one, then advances the
125 > * snapshot. Does NOT clear the dirty flag — callers own that bookkeeping.
126 > */
127 > flush(session: string): void {
128 const current = this._getSummary(session);
129 const lastNotified = this._lastNotified.get(session);
148 }
149 }
151 >
152 > /**
153 > * Server-side state manager for the sessions process protocol.
154 > *
155 > * Maintains the authoritative state tree (root + per-session), applies actions
156 > * through pure reducers, assigns monotonic sequence numbers, and emits
157 > * {@link ActionEnvelope}s for subscribed clients.
158 > */
159 > export const IAgentHostStateManager = createDecorator<AgentHostStateManager>('agentHostStateManager');
160 >
161 > export class AgentHostStateManager extends Disposable {
162 > declare readonly _serviceBrand: undefined;
163 >
164 > private _serverSeq = 0;
165 >
166 > private _rootState: RootState;
167 >
168 > /**
169 > * Authoritative per-session state, keyed by session URI string. Each entry
170 > * bundles the flat {@link SessionState} with the catalog-only fields that
171 > * are not part of the state (`createdAt`, `modifiedAt`, `changes`). The
172 > * root-channel {@link SessionSummary} catalog view is derived on demand from
173 > * an entry via {@link getSessionSummary} (its `_meta` is the same object as
174 > * {@link SessionState._meta}); the host streams catalog deltas via
175 > * `root/sessionSummaryChanged`.
176 > */
177 > private readonly _sessionStates = new Map<string, ISessionEntry>();
178 >
179 > /**
180 > * Authoritative per-chat conversation state, keyed by chat channel URI.
181 > * The protocol moved turns/activeTurn/pending state off the session and
182 > * onto a per-chat channel. VS Code currently models every session as
183 > * having exactly one chat — its default chat — whose URI is derived
184 > * deterministically from the session URI via {@link buildDefaultChatUri}.
185 > */
186 > private readonly _chatStates = new Map<string, ChatState>();
187 >
188 > /**
189 > * Opaque, agent-owned `providerData` blobs keyed by peer-chat channel URI.
190 > *
191 > * Each entry is the verbatim token the owning agent produced for a peer
192 > * chat (see {@link IAgentCreateChatResult.providerData}). The orchestrator
193 > * persists it with the session and hands it back to the agent on restore so
194 > * the agent can re-materialize its SDK conversation; the StateManager itself
195 > * **never parses, validates, or mutates it** — it stores and returns the
196 > * string as-is. The map is kept separate from the protocol-visible
197 > * {@link ChatState}/{@link ChatSummary} catalog so the private blob is not
198 > * streamed to clients. The default chat carries no `providerData`, so it
199 > * never appears here.
200 > */
201 > private readonly _chatProviderData = new Map<string, string>();
202 >
203 > /** Expanded changeset states, separated from protocol sequencing so cache policy stays local. */
204 > private readonly _changesets: AgentHostChangesetStateCache;
205 >
206 > /**
207 > * Per-channel annotation states for the `<session>/annotations` channel.
208 > * Unlike changesets (server-owned), annotation actions are
209 > * client-dispatchable and lazily create their state on first write.
210 > */
211 > private readonly _annotations = new Map<string, AnnotationsState>();
212 >
213 > /**
214 > * Active turns per session, keyed by session URI string with the value
215 > * being the set of that session's chat channel URIs that currently have an
216 > * active turn. A session is "active" while at least one of its chats is
217 > * streaming — this stays correct for multi-chat sessions whose chats can run
218 > * concurrent turns (e.g. agent-team / sub-agent workers), where the previous
219 > * single-flag-per-session model would clear too early. Active state is
220 > * derived from `state.activeTurn` (the source of truth maintained by the
221 > * session reducer) — never from raw action turn-ids — so that mismatched or
222 > * out-of-order turn lifecycle actions can't desync it from reality. The
223 > * session count (`size`) drives `RootActiveSessionsChanged` and
224 > * `hasActiveSessions`, which together gate `--enable-remote-auto-shutdown`.
225 > */
226 > private readonly _sessionsWithActiveTurn = new Map<string, Set<string>>();
227 >
228 > /**
229 > * Root-channel summary notification bookkeeping: the diff baseline (last
230 > * announced summary per session) and the dirty set, debounced into
231 > * `root/sessionSummaryChanged` notifications. Assigned in the constructor
232 > * since it closes over {@link _toSummary} and {@link _onDidEmitNotification}.
233 > */
234 > private readonly _summaryNotifier: SessionSummaryNotifier;
235 >
236 > private readonly _onDidEmitEnvelope = this._register(new Emitter<ActionEnvelope>());
237 > readonly onDidEmitEnvelope: Event<ActionEnvelope> = this._onDidEmitEnvelope.event;
238 >
239 > private readonly _onDidEmitNotification = this._register(new Emitter<INotification>());
240 > readonly onDidEmitNotification: Event<INotification> = this._onDidEmitNotification.event;
241 > private readonly _onDidChangeSessionActiveTurn = this._register(new Emitter<{ session: string; active: boolean }>());
242 > readonly onDidChangeSessionActiveTurn: Event<{ session: string; active: boolean }> = this._onDidChangeSessionActiveTurn.event;
243 >
244 > constructor(
245 > @ILogService private readonly _logService: ILogService, agentHostStateManager.ts
246 > options: IAgentHostStateManagerOptions = {},
247 > ) {
248 > super();
249 > this._changesets = new AgentHostChangesetStateCache(options.changesetStateRetention);
250 > this._rootState = createRootState();
251 > // Seed the host-level configuration schema + default values so that
252 > // RootConfigChanged actions can merge into it, and clients see the
253 > // schema immediately upon subscribing to `agenthost:/root`. See
254 > // `platformRootSchema` for the set of platform-owned properties.
255 > this._rootState = {
256 > ...this._rootState,
257 > config: {
258 > schema: platformRootSchema.toProtocol(),
259 > values: platformRootSchema.validateOrDefault({}, {
260 > [SessionConfigKey.Permissions]: { allow: [], deny: [] } satisfies IPermissionsValue,
261 > [AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(TelemetryLevel.USAGE),
262 > }),
263 > },
264 > _meta: withHostBuildInfo(this._rootState._meta, options.hostBuildInfo),
265 > };
266 > this._summaryNotifier = this._register(new SessionSummaryNotifier(
267 > session => {
268 const entry = this._sessionStates.get(session);
269 return entry ? this._toSummary(session, entry) : undefined;
270 },
271 > (session, changes) => this._onDidEmitNotification.fire({ agentHostStateManager.ts
272 type: 'root/sessionSummaryChanged',
273 channel: ROOT_STATE_URI,
275 changes,
276 }),
278 > }
279 > private readonly _log = (msg: string) => this._logService.warn(`[AgentHostStateManager] ${msg}`); agentHostStateManager.ts
280 >
281 > get hasActiveSessions(): boolean {
282 return this._sessionsWithActiveTurn.size > 0;
283 }
285 > /**
286 > * Whether the given session currently has an active turn — i.e. a request is
287 > * in progress on any of its chats. Stays `true` while at least one chat is
288 > * streaming, so it remains correct for multi-chat sessions running
289 > * concurrent turns.
290 > */
291 > hasActiveTurn(sessionKey: string): boolean {
292 return this._sessionsWithActiveTurn.has(sessionKey);
293 }
295 > // ---- State accessors ----------------------------------------------------
296 >
297 > get rootState(): RootState {
298 > return this._rootState; agentHostStateManager.ts
299 > }
301 > getSessionState(sessionOrChat: URI): ISessionWithDefaultChat | undefined {
302 // Accept either a session URI or one of its chat channel URIs. When a
303 // chat URI is given the conversation contents are taken from that chat,
315 return mergeSessionWithDefaultChat(entry.state, this._chatStates.get(chatUri));
316 }
318 > /**
319 > * Returns the root-channel {@link SessionSummary} catalog entry for a
320 > * session, or `undefined` when the session is unknown. The summary is
321 > * derived on demand from the session's {@link ISessionEntry}: its metadata
322 > * fields and `_meta` come straight off the live {@link SessionState}, while
323 > * the catalog-only `resource` / `createdAt` / `modifiedAt` / `changes` come
324 > * from the entry.
325 > */
326 > getSessionSummary(session: URI): SessionSummary | undefined {
327 const entry = this._sessionStates.get(session);
328 return entry ? this._toSummary(session, entry) : undefined;
329 }
331 > /**
332 > * Projects an {@link ISessionEntry} into its root-channel
333 > * {@link SessionSummary}. The summary's `_meta` is the same object as
334 > * {@link SessionState._meta} — the host treats the two as identical.
335 > */
336 > private _toSummary(session: string, entry: ISessionEntry): SessionSummary {
337 const { state } = entry;
338 const summary: SessionSummary = {
352 return summary;
353 }
355 > /**
356 > * Whether the {@link SessionSummary}-relevant fields of two session states
357 > * are field-equal. Used to decide whether a session action mutated anything
358 > * the root-channel catalog cares about.
359 > */
360 > private _summaryFieldsEqual(a: SessionState, b: SessionState): boolean {
361 > return a.title === b.title agentHostStateManager.ts
362 > && a.status === b.status agentHostStateManager.ts
363 > && a.activity === b.activity
364 > && a.project === b.project
365 > && a.workingDirectories === b.workingDirectories
366 > && a.annotations === b.annotations
367 > && a._meta === b._meta;
370 > /**
371 > * Returns the authoritative {@link ChatState} for a session's default
372 > * chat, or `undefined` when the session is unknown. Use this when the
373 > * caller specifically needs conversation contents (turns, activeTurn,
374 > * pending/input state) rather than the session summary.
375 > */
376 > getDefaultChatState(session: URI): ChatState | undefined {
377 return this._chatStates.get(buildDefaultChatUri(session));
378 }
380 > /** Returns the authoritative {@link ChatState} for a chat channel URI. */
381 > getChatState(chat: URI): ChatState | undefined {
382 return this._chatStates.get(chat);
383 }
385 > /**
386 > * Returns the opaque, agent-owned `providerData` blob previously recorded
387 > * for a peer chat via {@link addChat} or {@link restoreChat}, or `undefined`
388 > * when none was stored (e.g. the default chat, or a peer chat the agent had
389 > * nothing resumable to persist for). The value is returned verbatim — the
390 > * StateManager never interprets it; callers persist it with the session and
391 > * hand it back to the owning agent on restore.
392 > */
393 > getChatProviderData(chat: URI): string | undefined {
394 return this._chatProviderData.get(chat);
395 }
397 > /**
398 > * Seeds the conversation contents (turns) of a session's default chat.
399 > * Used by the fork flow, which materializes a new session pre-populated
400 > * with a slice of the source session's turns.
401 > */
402 > seedDefaultChatTurns(session: URI, turns: Turn[]): void {
403 const chatState = this._chatStates.get(buildDefaultChatUri(session));
404 if (chatState) {
406 }
407 }
409 > get serverSeq(): number {
410 return this._serverSeq;
411 }
413 > getSessionUris(): string[] {
414 return [...this._sessionStates.keys()];
415 }
417 > /**
418 > * Summaries eligible to be overlaid onto a provider's `listSessions`
419 > * snapshot when that snapshot is missing them. A session qualifies if it
420 > * has materialized (lifecycle !== {@link SessionLifecycle.Creating}) — this
421 > * covers the transient-drop case where a provider briefly omits a
422 > * just-materialized session — or if it is still provisional but has had any
423 > * turn activity (an in-flight turn, or a completed turn whose materialize
424 > * event has not landed yet; the first turn can start before materialization
425 > * completes). Idle provisional sessions (created but not yet materialized
426 > * and with no turn activity, e.g. the new-session composer's eagerly-created
427 > * session before its first message) are excluded so they don't leak into
428 > * the session list (#321269).
429 > */
430 > getOverlaySessionSummaries(): SessionSummary[] {
431 const summaries: SessionSummary[] = [];
432 for (const [key, entry] of this._sessionStates) {
441 return summaries;
442 }
444 > /**
445 > * Returns all session URIs whose keys start with the given prefix.
446 > * Used to discover subagent sessions for a given parent.
447 > */
448 > getSessionUrisWithPrefix(prefix: string): string[] {
449 const result: string[] = [];
450 for (const key of this._sessionStates.keys()) {
455 return result;
456 }
458 > // ---- Snapshots ----------------------------------------------------------
459 >
460 > /**
461 > * Returns a state snapshot for a given resource URI.
462 > * The `fromSeq` in the snapshot is the current serverSeq at snapshot time;
463 > * the client should process subsequent envelopes with serverSeq > fromSeq.
464 > */
465 > getSnapshot(resource: URI): IStateSnapshot | undefined {
466 if (isAhpRootChannel(resource)) {
467 return {
519 };
520 }
522 > /** Read-only accessor for callers that only need to inspect a changeset (not subscribe). */
523 > getChangesetState(changeset: URI): ChangesetState | undefined {
524 return this._changesets.get(changeset);
525 }
527 > /** Reconsiders changeset state retention after subscribers or computes release their pins. */
528 > onChangesetLivenessChanged(): void {
529 this._changesets.trimEvictableEntries();
530 }
532 > // ---- Session lifecycle --------------------------------------------------
533 >
534 > /**
535 > * Creates a new session in state with `lifecycle: 'creating'`.
536 > * Returns the initial session state.
537 > *
538 > * By default a {@link NotificationType.SessionAdded} notification is
539 > * emitted so clients see the new session immediately. Pass
540 > * `options.emitNotification: false` to defer the notification — a typical
541 > * use is for **provisional** sessions that exist on the server but should
542 > * not appear in client session lists until they have been persisted by
543 > * the agent (e.g. on the first message that materializes an SDK session
544 > * and writes its on-disk metadata). Call {@link markSessionPersisted}
545 > * afterwards to fire the deferred notification.
546 > */
547 > createSession(summary: SessionSummary, options?: { readonly emitNotification?: boolean }): SessionState {
548 > const key = summary.resource; agentHostStateManager.ts
549 > const existing = this._sessionStates.get(key);
550 > if (existing) {
551 this._logService.warn(`[AgentHostStateManager] Session already exists: ${key}`);
552 return existing.state;
553 }
555 > const state = createSessionState(summary);
556 > this._sessionStates.set(key, this._newEntry(state, summary));
557 > this._ensureDefaultChat(key, summary);
558 >
559 > this._logService.trace(`[AgentHostStateManager] Created session: ${key}`);
560 >
561 > if (options?.emitNotification !== false) {
562 > // Announcing the summary to the notifier is what makes agentHostStateManager.ts
563 > // its later flush emit incremental updates and what makes
564 > // `markSessionPersisted` a no-op. Provisional sessions
565 > // intentionally skip both until they are persisted.
566 > this._summaryNotifier.announce(key, summary);
567 > this._onDidEmitNotification.fire({
568 > type: 'root/sessionAdded',
569 > channel: ROOT_STATE_URI,
570 > summary,
571 > });
572 > }
574 > return state;
575 > }
577 > /** Builds the authoritative {@link ISessionEntry} for a freshly seeded state. */
578 > private _newEntry(state: SessionState, summary: SessionSummary): ISessionEntry {
579 > return { state, createdAt: summary.createdAt, modifiedAt: summary.modifiedAt, changes: summary.changes }; agentHostStateManager.ts
580 > }
582 > /**
583 > * Fire a {@link NotificationType.SessionAdded} notification for a session
584 > * whose creation was deferred via `createSession({ emitNotification: false })`.
585 > *
586 > * Propagates the materialization-resolved catalog fields (`project`,
587 > * `workingDirectory`, `modifiedAt`, `changes`) from the supplied summary
588 > * onto the session entry so subscribers see them. The reducer-owned metadata
589 > * (`title`, `status`, `activity`) is intentionally NOT copied back — the live
590 > * state is authoritative for those. No-ops for sessions that were already
591 > * announced (idempotent).
592 > */
593 > markSessionPersisted(session: URI, summary: SessionSummary): void {
594 const key = session.toString();
595 const entry = this._sessionStates.get(key);
621 });
622 }
624 > /**
625 > * Restores a session from a previous server lifetime into the state manager
626 > * with pre-populated turns. The session is created in `ready` lifecycle
627 > * state since it already exists on the backend.
628 > *
629 > * Unlike {@link createSession}, this does NOT emit a `sessionAdded`
630 > * notification because the session is already known to clients via
631 > * `listSessions`.
632 > */
633 > restoreSession(summary: SessionSummary, turns: Turn[], options?: { readonly draft?: Message; readonly defaultChatTitle?: string }): SessionState {
634 const key = summary.resource;
635 const existing = this._sessionStates.get(key);
651 return state;
652 }
654 > /**
655 > * Creates the default {@link ChatState} for a session and records it as
656 > * the session's single chat. VS Code models every session as having
657 > * exactly one chat — its default chat — whose URI is derived
658 > * deterministically from the session URI. The chat is seeded with any
659 > * pre-populated `turns` (used by {@link restoreSession}).
660 > *
661 > * The session's `chats` catalog and `defaultChat` pointer are updated
662 > * in place rather than via dispatched actions: there are no subscribers
663 > * at creation/restore time, so the snapshot a client later receives on
664 > * subscribe already reflects the default chat.
665 > */
666 > private _ensureDefaultChat(sessionKey: string, summary: SessionSummary, turns?: Turn[], draft?: Message, defaultChatTitle?: string): void {
667 > const chatUri = buildDefaultChatUri(sessionKey); agentHostStateManager.ts
668 > // Empty title means "inherit the session title"; a persisted independent
669 > // rename (`defaultChatTitle`) is seeded back here so it survives restore.
670 > const chatSummary: ChatSummary = { ...createDefaultChatSummary(summary, chatUri), title: defaultChatTitle ?? '' };
671 > this._chatStates.set(chatUri, { ...createChatState(chatSummary), turns: turns ?? [], draft });
672 > const entry = this._sessionStates.get(sessionKey);
673 > if (entry) {
674 > // Update the session's chat catalog in place so the object
675 > // identity returned by `createSession`/`restoreSession` stays
676 > // live in the map. Callers (e.g. `AgentService.createSession`)
677 > // mutate the returned state directly (`state.config = …`), so
678 > // replacing the map entry with a fresh clone here would strand
679 > // those mutations on a detached object.
680 > entry.state.chats = [chatSummary];
681 > entry.state.defaultChat = chatUri;
682 > }
683 > }
685 > /**
686 > * Adds an additional (non-default) chat to an existing session. Creates
687 > * the chat's authoritative {@link ChatState}, registers it in the session's
688 > * catalog via a dispatched {@link ActionType.SessionChatAdded} action (so
689 > * live subscribers refresh), and returns the new chat's summary.
690 > *
691 > * The chat inherits the session's model/agent/working-directory scope. It
692 > * is a no-op (returning the existing summary) when a chat with the same URI
693 > * already exists.
694 > *
695 > * When `options.providerData` is supplied it is recorded verbatim as the
696 > * peer chat's opaque, agent-owned restore blob (see
697 > * {@link getChatProviderData}); the StateManager never parses it. The
698 > * default chat never carries `providerData`.
699 > */
700 > addChat(session: URI, chatUri: URI, options?: { readonly title?: string; readonly turns?: Turn[]; readonly origin?: ChatOrigin; readonly providerData?: string; readonly interactivity?: ChatInteractivity }): ChatSummary | undefined {
701 const entry = this._sessionStates.get(session);
702 if (!entry) {
735 return chatSummary;
736 }
738 > /**
739 > * Re-registers an additional (non-default) peer chat when a session is
740 > * restored from persistent storage, seeding its {@link ChatState} with the
741 > * supplied turns. Unlike {@link addChat} this does not snapshot the session
742 > * title onto the default chat (the default chat's persisted title is
743 > * restored independently) and it seeds history. The catalog entry is added
744 > * in place so the object identity returned by {@link restoreSession} stays
745 > * live; no {@link ActionType.SessionChatAdded} is dispatched because restore
746 > * runs before clients subscribe.
747 > *
748 > * When `options.providerData` is supplied it is recorded verbatim as the
749 > * peer chat's opaque, agent-owned restore blob (see
750 > * {@link getChatProviderData}); the StateManager never parses it.
751 > */
752 > restoreChat(session: URI, chatUri: URI, options: { readonly title?: string; readonly turns: Turn[]; readonly draft?: Message; readonly providerData?: string; readonly origin?: ChatOrigin }): void {
753 const entry = this._sessionStates.get(session);
754 if (!entry) {
772 sessionState.chats = [...sessionState.chats, chatSummary];
773 }
775 > /**
776 > * Removes an additional chat from a session. Deletes its
777 > * {@link ChatState}, dispatches {@link ActionType.SessionChatRemoved}, and
778 > * — if the removed chat was the default — repoints `defaultChat` to the
779 > * first remaining chat. The default chat itself cannot be removed in
780 > * isolation; it lives and dies with its session.
781 > */
782 > removeChat(session: URI, chatUri: URI): void {
783 const entry = this._sessionStates.get(session);
784 if (!entry || !entry.state.chats.some(c => c.resource === chatUri)) {
801 this.dispatchServerAction(session, { type: ActionType.SessionChatRemoved, chat: chatUri });
802 }
804 > /**
805 > * Renames a single chat within a session independently of the session
806 > * title. Updates the chat's authoritative {@link ChatState} title (so
807 > * later `chatSummaryFromState` projections stay consistent) and dispatches
808 > * a {@link ActionType.SessionChatUpdated} so the session's catalog entry and
809 > * live subscribers reflect the new title. Works for the default chat too —
810 > * giving it a non-empty title that no longer inherits the session title.
811 > */
812 > updateChatTitle(session: URI, chatUri: URI, title: string): void {
813 const chatState = this._chatStates.get(chatUri);
814 if (chatState) {
817 this.dispatchServerAction(session, { type: ActionType.SessionChatUpdated, chat: chatUri, changes: { title } });
818 }
820 > /**
821 > * Removes a session from in-memory state without emitting a
822 > * {@link NotificationType.SessionRemoved} notification.
823 > * Use {@link deleteSession} when the session is being permanently deleted
824 > * and clients need to be notified of its removal.
825 > *
826 > * Any pending summary change is flushed synchronously before the session is
827 > * torn down, so clients receive the final status (e.g. Idle after a turn
828 > * completes) even when the session is evicted before the scheduler fires.
829 > * A {@link NotificationType.SessionSummaryChanged} notification may therefore
830 > * be emitted as a side-effect of this call.
831 > *
832 > * Per-session changesets are intentionally NOT torn down here: this method
833 > * is also used as an idle-eviction (LRU) hook (see
834 > * `AgentService._maybeEvictIdleSession`) and the session list view keeps a
835 > * changeset subscription open per visible row to render the diff chip.
836 > * Tearing down on eviction would clear the chip on the list while the row
837 > * is still on screen. Permanent-delete paths (`deleteSession`,
838 > * `removeSubagentSessions`) call `disposeSessionChangesets` explicitly
839 > * before invoking `removeSession`.
840 > */
841 > removeSession(session: URI): void {
842 const entry = this._sessionStates.get(session);
843 if (!entry) {
874 this._logService.trace(`[AgentHostStateManager] Removed session: ${session}`);
875 }
877 > /**
878 > * Permanently deletes a session from state and emits a
879 > * {@link NotificationType.SessionRemoved} notification so that clients
880 > * know the session is no longer accessible.
881 > *
882 > * Sessions whose creation was deferred via
883 > * `createSession({ emitNotification: false })` and never persisted via
884 > * {@link markSessionPersisted} are removed silently — no client knows
885 > * about them, so a `SessionRemoved` would be noise (or worse, would
886 > * cause clients to drop a session URI they had eagerly subscribed to).
887 > */
888 > deleteSession(session: URI): void {
889 const wasAnnounced = this._summaryNotifier.isAnnounced(session);
890 // Drop any pending summary diff: the forthcoming SessionRemoved notification
908 }
909 }
911 > // ---- Session meta -------------------------------------------------------
912 >
913 > /**
914 > * Replaces `state._meta` on a session by dispatching a
915 > * {@link ActionType.SessionMetaChanged} action so the change flows
916 > * through the action envelope (and thus to all live subscribers).
917 > *
918 > * The full `_meta` object is replaced (not merged) so callers stay in
919 > * control of the convention for their own keys; use the `withSessionXxx`
920 > * helpers in `sessionState.ts` to combine slots.
921 > */
922 > setSessionMeta(session: URI, meta: SessionMeta | undefined): void {
923 this.dispatchServerAction(session, { type: ActionType.SessionMetaChanged, _meta: meta });
924 }
926 > /**
927 > * Seeds or replaces a session's resolved {@link SessionConfigState} on the
928 > * live session state. Unlike mid-session {@link ActionType.SessionConfigChanged}
929 > * updates (which merge values onto an existing config), this establishes
930 > * the initial config and is therefore an in-place mutation of the
931 > * authoritative state object so the value is present in the first snapshot
932 > * a subscriber receives. Use this from create/restore flows where the
933 > * config is resolved asynchronously after the session state already exists
934 > * in the map — reading back through {@link getSessionState} would return a
935 > * detached composite copy and stranding the mutation there.
936 > */
937 > setSessionConfig(session: URI, config: SessionConfigState | undefined): void {
938 const entry = this._sessionStates.get(session);
939 if (!entry) {
943 entry.state.config = config;
944 }
946 > /**
947 > * Seeds or replaces the session's effective customizations directly on the
948 > * authoritative in-memory state. Used by create/restore flows to ensure the
949 > * first snapshot already contains customizations.
950 > */
951 > setSessionCustomizations(session: URI, customizations: readonly Customization[] | undefined): void {
952 const entry = this._sessionStates.get(session);
953 if (!entry) {
957 entry.state.customizations = customizations ? [...customizations] : undefined;
958 }
960 > // ---- Changeset registry -------------------------------------------------
961 >
962 > /**
963 > * Registers a server-side changeset so that subscribers can attach to its
964 > * URI. The changeset is created with the supplied initial status (default
965 > * {@link ChangesetStatus.Computing}); subsequent file/operation/status
966 > * mutations flow through {@link dispatchChangesetAction} on the
967 > * canonical `<sessionUri>/changeset/<changesetId>` URI.
968 > *
969 > * Idempotent: a second call with the same URI is a no-op so producers
970 > * can safely re-register on session resume without double-creating
971 > * state.
972 > *
973 > * Callers construct `changesetUri` via {@link buildSessionChangesetUri}
974 > * for the session-wide entry, or {@link buildChangesetUri} for any
975 > * other catalogue entry.
976 > *
977 > * Returns the supplied changeset URI for caller convenience.
978 > */
979 > registerChangeset(changesetUri: URI, initialStatus: ChangesetStatus = ChangesetStatus.Computing): URI {
980 this._changesets.register(changesetUri, initialStatus);
981 return changesetUri;
982 }
984 > /**
985 > * Updates the aggregate `changes` for a session.
986 > *
987 > * There is no dedicated action for this field: the value is purely
988 > * informational (chip rendering on the session list), so the write
989 > * piggybacks on the existing `sessionSummaryChanged` notification
990 > * path. We update the session entry, mark the session dirty, and let
991 > * the summary notifier's flush pick the new value up via its
992 > * `current.changes !== lastNotified.changes` diff.
993 > */
994 > setSessionSummaryChanges(session: URI, changes: ChangesSummary | undefined): void {
995 const entry = this._sessionStates.get(session);
996 if (!entry) {
1006 this._summaryNotifier.markDirty(session);
1007 }
1009 > /**
1010 > * Replaces the catalogue entries on `state.changesets` for `session` by
1011 > * dispatching a {@link ActionType.SessionChangesetsChanged} action.
1012 > * Subscribers see the mutation in the standard session action stream —
1013 > * the catalogue lives on session state and is not its own subscribable
1014 > * resource. Aggregate `changes` counts (additions / deletions /
1015 > * files) are propagated separately via {@link setSessionSummaryChanges}.
1016 > *
1017 > * Producers call this after each compute pass to keep the list of
1018 > * available changesets (with their `changeKind`) in sync so observers
1019 > * can render the correct entries without subscribing to each one.
1020 > */
1021 > setSessionChangesets(session: URI, changesets: readonly Changeset[] | undefined): void {
1022 const entry = this._sessionStates.get(session);
1023 if (!entry) {
1042 });
1043 }
1045 > /**
1046 > * Tear down a changeset. Dispatches {@link ActionType.ChangesetCleared}
1047 > * so subscribers see an empty file list, then deletes the local state
1048 > * so a fresh `getChangesetState` returns `undefined` and forces the
1049 > * producer to re-create the changeset on next subscribe.
1050 > *
1051 > * Per the spec, the server SHOULD also unsubscribe its clients after
1052 > * dispatching this action; for VS Code-internal clients that happens
1053 > * via the `notify/sessionRemoved` notification, which the workbench-side
1054 > * provider correlates to release any held subscriptions.
1055 > *
1056 > * Safe to call for a URI that was never registered: producers typically
1057 > * iterate over a candidate set on session disposal and emit dispose
1058 > * actions defensively.
1059 > */
1060 > disposeChangeset(changeset: URI): void {
1061 if (!this._changesets.has(changeset)) {
1062 return;
1067 this._changesets.delete(changeset);
1068 }
1070 > /**
1071 > * Disposes every changeset whose URI is nested under `session` (i.e.
1072 > * matches `<session>/changeset/...`). Used to cascade cleanup when a
1073 > * session itself is removed.
1074 > */
1075 > disposeSessionChangesets(session: URI): void {
1076 // Collect first because `disposeChangeset` mutates the underlying
1077 // map via its envelope handler.
1087 }
1088 }
1090 > /**
1091 > * Drops the annotation state nested under `session` (i.e. the
1092 > * `<session>/annotations` channel). Used to cascade cleanup when a
1093 > * session itself is removed. Subscriptions are released via the
1094 > * forthcoming `sessionRemoved` notification.
1095 > */
1096 > disposeSessionAnnotations(session: URI): void {
1097 this._annotations.delete(buildAnnotationsUri(session));
1098 }
1100 > // ---- Turn tracking ------------------------------------------------------
1101 >
1102 > /**
1103 > * Registers a mapping from turnId to session URI so that incoming
1104 > * provider events (which carry only session URI) can be associated
1105 > * with the correct active turn.
1106 > */
1107 > getActiveTurnId(sessionOrChat: URI): string | undefined {
1108 const chatUri = isAhpChatChannel(sessionOrChat) ? sessionOrChat : buildDefaultChatUri(sessionOrChat);
1109 return this._chatStates.get(chatUri)?.activeTurn?.id;
1110 }
1112 > // ---- Action dispatch ----------------------------------------------------
1113 >
1114 > /**
1115 > * Dispatch a server-originated action (from the agent backend).
1116 > * The action is applied to state via the reducer and emitted as an
1117 > * envelope with no origin (server-produced).
1118 > *
1119 > * `channel` identifies the channel the action targets — `ROOT_STATE_URI`
1120 > * for root actions, a session URI for session actions, a terminal URI
1121 > * for terminal actions, an expanded changeset URI for changeset actions.
1122 > */
1123 > dispatchServerAction(channel: URI, action: StateAction): void {
1124 > this._applyAndEmit(channel, action, undefined); agentHostStateManager.ts
1125 > }
1127 > /**
1128 > * Dispatch a client-originated action (write-ahead from a renderer).
1129 > * The action is applied to state and emitted with the client's origin
1130 > * so the originating client can reconcile.
1131 > */
1132 > dispatchClientAction(channel: URI, action: SessionAction | ChatAction | TerminalAction | ClientChangesetAction | ClientAnnotationsAction | IRootConfigChangedAction, origin: ActionOrigin): unknown {
1133 return this._applyAndEmit(channel, action, origin);
1134 }
1136 > /**
1137 > * Reject a client-originated action without applying it to state. Emits an
1138 > * {@link ActionEnvelope} that carries the original {@link ActionOrigin} and a
1139 > * {@link ActionEnvelope.rejectionReason | rejectionReason} so the originating
1140 > * client can reconcile (roll back) its optimistic write-ahead action through
1141 > * the normal path instead of leaving it pending until reconnect. The reducer
1142 > * is deliberately NOT run, so no synchronized state changes.
1143 > */
1144 > rejectClientAction(channel: URI, action: StateAction, origin: ActionOrigin, reason: string): void {
1145 const envelope: ActionEnvelope = {
1146 channel,
1153 this._onDidEmitEnvelope.fire(envelope);
1154 }
1156 > // ---- Internal -----------------------------------------------------------
1157 >
1158 > private _applyAndEmit(channel: URI, action: StateAction, origin: ActionOrigin | undefined): unknown {
1159 > let resultingState: unknown = undefined; agentHostStateManager.ts
1160 > if (action.type === ActionType.RootConfigChanged && action.replace) {
1161 action = {
1162 ...action,
1164 };
1165 }
1166 > // Apply to state agentHostStateManager.ts
1167 > if (isRootAction(action)) {
1168 > // `RootConfigChanged` can be a true no-op: the reducer merges/replaces agentHostStateManager.ts
1169 > // values even when the patch matches the current state, and re-emitting
1170 > // it would cause clients observing rootState.onDidChange to react and
1171 > // potentially re-dispatch in a loop. Check the action's own patch
1172 > // against current values before running the reducer so we avoid
1173 > // allocating a new state object at all.
1174 > if (action.type === ActionType.RootConfigChanged && this._rootState.config) {
1175 const current = this._rootState.config.values;
1176 const patch = action.config;
1182 }
1183 }
1184 > this._rootState = rootReducer(this._rootState, action as RootAction, this._log); agentHostStateManager.ts
1185 > resultingState = this._rootState;
1186 > }
1188 > if (isSessionAction(action)) {
1189 > const sessionAction = action as SessionAction; agentHostStateManager.ts
1190 > const key = channel;
1191 > const entry = this._sessionStates.get(key);
1192 > if (entry) {
1193 > const newState = sessionReducer(entry.state, sessionAction, this._log); agentHostStateManager.ts
1194 > const summaryChanged = !this._summaryFieldsEqual(entry.state, newState);
1195 > entry.state = newState;
1196 >
1197 > // When the reducer touched a summary-relevant field, notify
1198 > // root-channel clients of the derived-summary delta.
1199 > if (summaryChanged) {
1200 this._summaryNotifier.markDirty(key);
1201 }
1203 > resultingState = newState;
1204 > } else if (!isAhpChatChannel(key)) { agentHostStateManager.ts
1205 this._logService.warn(`[AgentHostStateManager] Action for unknown session: ${key}, type=${action.type}`);
1206 }
1209 > if (isChatAction(action)) {
1210 > if (!isAhpChatChannel(channel)) { agentHostStateManager.ts
1211 throw new Error(`[AgentHostStateManager] Chat action dispatched to non-chat channel: ${channel}, type=${action.type}`);
1212 }
1214 > const chatAction = action as ChatAction;
1215 > const sessionKey = parseRequiredSessionUriFromChatUri(channel);
1216 > const chat = this._chatStates.get(channel);
1217 > if (chat && sessionKey !== undefined) {
1218 > const newChat = chatReducer(chat, chatAction, this._log);
1219 > this._chatStates.set(channel, newChat);
1220 > this._onChatStateChanged(sessionKey, channel, chat, newChat);
1221 > resultingState = newChat;
1222 > } else {
1223 this._logService.warn(`[AgentHostStateManager] Action for unknown chat: ${channel}, type=${action.type}`);
1224 }
1227 > if (isChangesetAction(action)) {
1228 const changesetAction = action as ChangesetAction;
1229 const key = channel;
1243 resultingState = newState;
1244 }
1246 > if (isAnnotationsAction(action)) {
1247 const annotationsAction = action as AnnotationsAction;
1248 const key = channel;
1256 resultingState = newState;
1257 }
1259 > // Emit envelope
1260 > const envelope: ActionEnvelope = {
1261 > channel,
1262 > action,
1263 > serverSeq: ++this._serverSeq,
1264 > origin,
1265 > };
1266 >
1267 > this._logService.trace(`[AgentHostStateManager] Emitting envelope: seq=${envelope.serverSeq}, channel=${envelope.channel}, type=${action.type}${origin ? `, origin=${origin.clientId}:${origin.clientSeq}` : ''}`);
1268 > this._onDidEmitEnvelope.fire(envelope);
1269 >
1270 > return resultingState;
1271 > }
1273 > /**
1274 > * Removes a single chat from its session's active-turn set, firing the
1275 > * session-level active flip ({@link onDidChangeSessionActiveTurn} +
1276 > * {@link ActionType.RootActiveSessionsChanged}) when this clears the
1277 > * session's last active chat. Safe to call for chats that aren't currently
1278 > * tracked as active — it is a no-op in that case. Used both when a turn
1279 > * ends and when a chat is removed mid-turn, so the session can't be
1280 > * stranded as permanently "active".
1281 > */
1282 > private _removeChatActiveTurn(sessionKey: string, chatUri: string): void {
1283 const activeChats = this._sessionsWithActiveTurn.get(sessionKey);
1284 if (!activeChats || !activeChats.delete(chatUri)) {
1292 }
1293 }
1295 > /**
1296 > * Bridges a default-chat state transition back onto its owning session.
1297 > *
1298 > * The protocol moved turn lifecycle (and therefore the derived
1299 > * activity status) onto the chat channel. To preserve VS Code's
1300 > * single-chat behaviour we:
1301 > * - track active-turn transitions (driving `RootActiveSessionsChanged`
1302 > * and `hasActiveSessions`, which gate `--enable-remote-auto-shutdown`),
1303 > * keyed by the owning session URI;
1304 > * - mirror the chat's denormalized `status`/`activity`/`modifiedAt`
1305 > * onto the session summary so the session list reflects progress;
1306 > * - forward the chat's own `status` to the session `chats` catalog (via a
1307 > * {@link ActionType.SessionChatUpdated}) so per-chat tabs reflect that
1308 > * chat's progress, not just the aggregated session summary; and
1309 > * - keep the session's `chats` catalog entry in sync.
1310 > */
1311 > private _onChatStateChanged(sessionKey: string, chatUri: string, prev: ChatState, next: ChatState): void {
1312 > // Active turn tracking — derive from the reducer's view of state, agentHostStateManager.ts
1313 > // never from raw action turn-ids, so out-of-order lifecycle actions
1314 > // can't desync the count from reality. Track active turns per chat so a
1315 > // session stays active until ALL of its concurrent chat turns finish;
1316 > // only notify when the session's overall active state actually flips.
1317 > const hadActive = !!prev.activeTurn;
1318 > const hasActive = !!next.activeTurn;
1319 > if (hadActive !== hasActive) {
1320 > if (hasActive) { agentHostStateManager.ts
1321 > let activeChats = this._sessionsWithActiveTurn.get(sessionKey);
1322 > const wasSessionActive = !!activeChats?.size;
1323 > if (!activeChats) {
1324 > activeChats = new Set<string>();
1325 > this._sessionsWithActiveTurn.set(sessionKey, activeChats);
1326 > }
1327 > activeChats.add(chatUri);
1328 > if (!wasSessionActive) {
1329 > this._onDidChangeSessionActiveTurn.fire({ session: sessionKey, active: true });
1330 > this.dispatchServerAction(ROOT_STATE_URI, { type: ActionType.RootActiveSessionsChanged, activeSessions: this._sessionsWithActiveTurn.size });
1331 > }
1332 > } else {
1333 this._removeChatActiveTurn(sessionKey, chatUri);
1334 }
1337 > const entry = this._sessionStates.get(sessionKey);
1338 > if (!entry) {
1339 return;
1340 }
1341 > const sessionState = entry.state; agentHostStateManager.ts
1342 >
1343 > // Mirror denormalized chat summary fields onto the session, aggregating
1344 > // across the whole chat catalog per the SessionSummary rules.
1345 > const nextEntry = chatSummaryFromState(next);
1346 > const prevEntry = sessionState.chats.find(c => c.resource === chatUri);
1347 > const chats = sessionState.chats.map(c => c.resource === chatUri ? nextEntry : c);
1348 >
1349 > // Forward the chat's own status to the session catalog so full
1350 > // SessionState subscribers (the per-chat tabs) reflect this chat's
1351 > // progress — not just the aggregated session summary. Status changes
1352 > // at most a couple of times per turn, so this won't flood the channel.
1353 > if (prevEntry?.status !== nextEntry.status) {
1354 > this.dispatchServerAction(sessionKey, { agentHostStateManager.ts
1355 > type: ActionType.SessionChatUpdated,
1356 > chat: chatUri,
1357 > changes: { status: nextEntry.status, activity: nextEntry.activity },
1358 > });
1359 > }
1361 > const aggregate = this._aggregateChatSummaries(chats, sessionState.defaultChat);
1362 > const newStatus = aggregate.status !== undefined ? this._mergeSessionStatus(sessionState.status, aggregate.status) : sessionState.status;
1363 > const statusChanged = newStatus !== sessionState.status;
1364 > const activityChanged = aggregate.activity !== sessionState.activity;
1365 > entry.state = {
1366 > ...sessionState,
1367 > chats,
1368 > ...(statusChanged ? { status: newStatus } : undefined),
1369 > ...(activityChanged ? { activity: aggregate.activity } : undefined),
1370 > };
1371 >
1372 > // Roll the aggregated `modifiedAt` into the catalog-only timestamp.
1373 > const newModifiedAt = aggregate.modifiedAt !== undefined ? new Date(aggregate.modifiedAt).toISOString() : undefined;
1374 > const modifiedAtChanged = newModifiedAt !== undefined && newModifiedAt !== entry.modifiedAt;
1375 > if (modifiedAtChanged) {
1376 > entry.modifiedAt = newModifiedAt; agentHostStateManager.ts
1377 > }
1379 > if (statusChanged || activityChanged || modifiedAtChanged) {
1380 > this._summaryNotifier.markDirty(sessionKey); agentHostStateManager.ts
1381 > }
1384 > /**
1385 > * Aggregates a session's chat catalog into the derived session-summary
1386 > * fields per the protocol rules: activity bits come from the default chat
1387 > * (else the most recently modified chat) with `InputNeeded`/`Error`/
1388 > * `InProgress` promoted whenever any chat raises them; the `activity` string
1389 > * follows the chat driving the resulting status; `modifiedAt` is the max
1390 > * across chats. Promotion precedence is `InputNeeded` > `Error` >
1391 > * `InProgress`, so a running peer (sub) chat surfaces as `InProgress` on the
1392 > * session even when the default chat is idle.
1393 > */
1394 > private _aggregateChatSummaries(chats: readonly ChatSummary[], defaultChat: URI | undefined): { status?: SessionStatus; activity?: string; modifiedAt?: number } {
1395 > if (chats.length === 0) { agentHostStateManager.ts
1396 return {};
1397 }
1398 > const activityMask = ~(SessionStatus.IsRead | SessionStatus.IsArchived); agentHostStateManager.ts
1399 > const base = (defaultChat !== undefined ? chats.find(c => c.resource === defaultChat) : undefined)
1400 ?? chats.reduce((a, b) => Date.parse(b.modifiedAt) > Date.parse(a.modifiedAt) ? b : a);
1401 > let status = base.status & activityMask; agentHostStateManager.ts
1402 > let driver = base;
1403 > const errorChat = chats.find(c => (c.status & SessionStatus.Error) === SessionStatus.Error);
1404 > const inputChat = chats.find(c => (c.status & SessionStatus.InputNeeded) === SessionStatus.InputNeeded);
1405 > // `InputNeeded` is a superset of the `InProgress` bit, so exclude
1406 > // input-needed chats here to find one that is purely streaming.
1407 > const inProgressChat = chats.find(c => (c.status & SessionStatus.InputNeeded) === SessionStatus.InProgress);
1408 > if (inputChat) {
1409 status = SessionStatus.InputNeeded;
1410 driver = inputChat;
1411 > } else if (errorChat) { agentHostStateManager.ts
1412 status = SessionStatus.Error;
1413 driver = errorChat;
1414 > } else if (inProgressChat) { agentHostStateManager.ts
1415 > status = SessionStatus.InProgress; agentHostStateManager.ts
1416 > driver = inProgressChat;
1417 > }
1418 > const modifiedAt = chats.reduce((max, c) => Math.max(max, Date.parse(c.modifiedAt)), 0); agentHostStateManager.ts
1419 > return { status, activity: driver.activity, modifiedAt };
1420 > }
1422 > /**
1423 > * Combines the chat's activity status bits with the session summary's
1424 > * own metadata flags (IsRead / IsArchived) which live in the high bits
1425 > * of {@link SessionStatus} and are owned by the session, not the chat.
1426 > */
1427 > private _mergeSessionStatus(sessionStatus: SessionStatus, chatStatus: SessionStatus): SessionStatus {
1428 > const metaFlags = sessionStatus & (SessionStatus.IsRead | SessionStatus.IsArchived); agentHostStateManager.ts
1429 > const activityBits = chatStatus & ~(SessionStatus.IsRead | SessionStatus.IsArchived);
1430 > return activityBits | metaFlags;
1431 > }
1433 > /**
1434 > * Emit a generic progress notification on the root channel, correlated to
1435 > * the originating request by {@link ProgressParams.progressToken}. Routed to
1436 > * clients through the same {@link onDidEmitNotification} path as session
1437 > * notifications, so both the local (IPC proxy) and remote (WebSocket
1438 > * {@link ProtocolServerHandler}) renderers receive it without any
1439 > * transport-specific special casing. Progress for host-level work (e.g. a
1440 > * shared SDK download) rides the root channel rather than a per-session one.
1441 > */
1442 > emitProgress(progress: Omit<ProgressParams, 'channel'>): void {
1443 this._onDidEmitNotification.fire({
1444 type: 'root/progress',
1447 });
1448 }
1450 > /**
1451 > * Emit an `auth/required` notification on the root channel, asking the
1452 > * client to obtain a fresh token and push it via `authenticate`. Rides the
1453 > * same {@link onDidEmitNotification} path as {@link emitProgress}, so both
1454 > * local (IPC proxy) and remote (WebSocket) renderers receive it. Used for
1455 > * host-level auth requirements (e.g. an agent whose transport flip makes a
1456 > * credential newly required) rather than a per-session one.
1457 > */
1458 > emitAuthRequired(params: Omit<AuthRequiredParams, 'channel'>): void {
1459 this._onDidEmitNotification.fire({
1460 type: 'auth/required',
src/vs/platform/contextkey/common/contextkey.ts 832 covered LOC · 218 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- contextkey.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from '../../../base/common/charCode.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { isChrome, isEdge, isFirefox, isLinux, isMacintosh, isSafari, isWeb, isWindows } from '../../../base/common/platform.js';
9 > import { isFalsyOrWhitespace } from '../../../base/common/strings.js';
10 > import { Scanner, LexingError, Token, TokenType } from './scanner.js';
11 > import { createDecorator } from '../../instantiation/common/instantiation.js';
12 > import { localize } from '../../../nls.js';
13 > import { IDisposable } from '../../../base/common/lifecycle.js';
14 > import { illegalArgument } from '../../../base/common/errors.js';
15 >
16 > const CONSTANT_VALUES = new Map<string, boolean>();
17 > CONSTANT_VALUES.set('false', false);
18 > CONSTANT_VALUES.set('true', true);
19 > CONSTANT_VALUES.set('isMac', isMacintosh);
20 > CONSTANT_VALUES.set('isLinux', isLinux);
21 > CONSTANT_VALUES.set('isWindows', isWindows);
22 > CONSTANT_VALUES.set('isWeb', isWeb);
23 > CONSTANT_VALUES.set('isMacNative', isMacintosh && !isWeb);
24 > CONSTANT_VALUES.set('isEdge', isEdge);
25 > CONSTANT_VALUES.set('isFirefox', isFirefox);
26 > CONSTANT_VALUES.set('isChrome', isChrome);
27 > CONSTANT_VALUES.set('isSafari', isSafari);
28 >
29 > /** allow register constant context keys that are known only after startup; requires running `substituteConstants` on the context key - https://github.com/microsoft/vscode/issues/174218#issuecomment-1437972127 */
30 > export function setConstant(key: string, value: boolean) {
31 if (CONSTANT_VALUES.get(key) !== undefined) { throw illegalArgument('contextkey.setConstant(k, v) invoked with already set constant `k`'); }
32
33 CONSTANT_VALUES.set(key, value);
34 }
36 > const hasOwnProperty = Object.prototype.hasOwnProperty;
37 >
38 > export const enum ContextKeyExprType {
39 > False = 0,
40 > True = 1,
41 > Defined = 2,
42 > Not = 3,
43 > Equals = 4,
44 > NotEquals = 5,
45 > And = 6,
46 > Regex = 7,
47 > NotRegex = 8,
48 > Or = 9,
49 > In = 10,
50 > NotIn = 11,
51 > Greater = 12,
52 > GreaterEquals = 13,
53 > Smaller = 14,
54 > SmallerEquals = 15,
55 > }
56 >
57 > export interface IContextKeyExprMapper {
58 > mapDefined(key: string): ContextKeyExpression;
59 > mapNot(key: string): ContextKeyExpression;
60 > mapEquals(key: string, value: any): ContextKeyExpression;
61 > mapNotEquals(key: string, value: any): ContextKeyExpression;
62 > mapGreater(key: string, value: any): ContextKeyExpression;
63 > mapGreaterEquals(key: string, value: any): ContextKeyExpression;
64 > mapSmaller(key: string, value: any): ContextKeyExpression;
65 > mapSmallerEquals(key: string, value: any): ContextKeyExpression;
66 > mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr;
67 > mapIn(key: string, valueKey: string): ContextKeyInExpr;
68 > mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr;
69 > }
70 >
71 > export interface IContextKeyExpression {
72 > cmp(other: ContextKeyExpression): number;
73 > equals(other: ContextKeyExpression): boolean;
74 > substituteConstants(): ContextKeyExpression | undefined;
75 > evaluate(context: IContext): boolean;
76 > serialize(): string;
77 > keys(): string[];
78 > map(mapFnc: IContextKeyExprMapper): ContextKeyExpression;
79 > negate(): ContextKeyExpression;
80 >
81 > }
82 >
83 > export type ContextKeyExpression = (
84 > ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr
85 > | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr
86 > | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr
87 > | ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr
88 > | ContextKeySmallerExpr | ContextKeySmallerEqualsExpr
89 > );
90 >
91 >
92 > /*
93 >
94 > Syntax grammar:
95 >
96 > ```ebnf
97 >
98 > expression ::= or
99 >
100 > or ::= and { '||' and }*
101 >
102 > and ::= term { '&&' term }*
103 >
104 > term ::=
105 > | '!' (KEY | true | false | parenthesized)
106 > | primary
107 >
108 > primary ::=
109 > | 'true'
110 > | 'false'
111 > | parenthesized
112 > | KEY '=~' REGEX
113 > | KEY [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'not' 'in' | 'in') value ]
114 >
115 > parenthesized ::=
116 > | '(' expression ')'
117 >
118 > value ::=
119 > | 'true'
120 > | 'false'
121 > | 'in' // we support `in` as a value because there's an extension that uses it, ie "when": "languageId == in"
122 > | VALUE // matched by the same regex as KEY; consider putting the value in single quotes if it's a string (e.g., with spaces)
123 > | SINGLE_QUOTED_STR
124 > | EMPTY_STR // this allows "when": "foo == " which's used by existing extensions
125 >
126 > ```
127 > */
128 >
129 > export type ParserConfig = {
130 > /**
131 > * with this option enabled, the parser can recover from regex parsing errors, e.g., unescaped slashes: `/src//` is accepted as `/src\//` would be
132 > */
133 > regexParsingWithErrorRecovery: boolean;
134 > };
135 >
136 > const defaultConfig: ParserConfig = {
137 > regexParsingWithErrorRecovery: true
138 > };
139 >
140 > export type ParsingError = {
141 > message: string;
142 > offset: number;
143 > lexeme: string;
144 > additionalInfo?: string;
145 > };
146 >
147 > const errorEmptyString = localize('contextkey.parser.error.emptyString', "Empty context key expression");
148 > const hintEmptyString = localize('contextkey.parser.error.emptyString.hint', "Did you forget to write an expression? You can also put 'false' or 'true' to always evaluate to false or true, respectively.");
149 > const errorNoInAfterNot = localize('contextkey.parser.error.noInAfterNot', "'in' after 'not'.");
150 > const errorClosingParenthesis = localize('contextkey.parser.error.closingParenthesis', "closing parenthesis ')'");
151 > const errorUnexpectedToken = localize('contextkey.parser.error.unexpectedToken', "Unexpected token");
152 > const hintUnexpectedToken = localize('contextkey.parser.error.unexpectedToken.hint', "Did you forget to put && or || before the token?");
153 > const errorUnexpectedEOF = localize('contextkey.parser.error.unexpectedEOF', "Unexpected end of expression");
154 > const hintUnexpectedEOF = localize('contextkey.parser.error.unexpectedEOF.hint', "Did you forget to put a context key?");
155 >
156 > /**
157 > * A parser for context key expressions.
158 > *
159 > * Example:
160 > * ```ts
161 > * const parser = new Parser();
162 > * const expr = parser.parse('foo == "bar" && baz == true');
163 > *
164 > * if (expr === undefined) {
165 > * // there were lexing or parsing errors
166 > * // process lexing errors with `parser.lexingErrors`
167 > * // process parsing errors with `parser.parsingErrors`
168 > * } else {
169 > * // expr is a valid expression
170 > * }
171 > * ```
172 > */
173 > export class Parser {
174 > // Note: this doesn't produce an exact syntax tree but a normalized one
175 > // ContextKeyExpression's that we use as AST nodes do not expose constructors that do not normalize
176 >
177 > private static _parseError = new Error();
178 >
179 > // lifetime note: `_scanner` lives as long as the parser does, i.e., is not reset between calls to `parse`
180 > private readonly _scanner = new Scanner();
181 >
182 > // lifetime note: `_tokens`, `_current`, and `_parsingErrors` must be reset between calls to `parse`
183 > private _tokens: Token[] = [];
184 > private _current = 0; // invariant: 0 <= this._current < this._tokens.length ; any incrementation of this value must first call `_isAtEnd`
185 > private _parsingErrors: ParsingError[] = [];
186 >
187 > get lexingErrors(): Readonly<LexingError[]> {
188 return this._scanner.errors;
189 }
191 > get parsingErrors(): Readonly<ParsingError[]> {
192 return this._parsingErrors;
193 }
195 > constructor(private readonly _config: ParserConfig = defaultConfig) {
196 > }
197 >
198 > /**
199 > * Parse a context key expression.
200 > *
201 > * @param input the expression to parse
202 > * @returns the parsed expression or `undefined` if there's an error - call `lexingErrors` and `parsingErrors` to see the errors
203 > */
204 > parse(input: string): ContextKeyExpression | undefined {
205
206 if (input === '') {
231 }
232 }
234 > private _expr(): ContextKeyExpression | undefined {
235 return this._or();
236 }
238 > private _or(): ContextKeyExpression | undefined {
239 const expr = [this._and()];
240
246 return expr.length === 1 ? expr[0] : ContextKeyExpr.or(...expr);
247 }
249 > private _and(): ContextKeyExpression | undefined {
250 const expr = [this._term()];
251
257 return expr.length === 1 ? expr[0] : ContextKeyExpr.and(...expr);
258 }
260 > private _term(): ContextKeyExpression | undefined {
261 if (this._matchOne(TokenType.Neg)) {
262 const peek = this._peek();
283 return this._primary();
284 }
286 > private _primary(): ContextKeyExpression | undefined {
287
288 const peek = this._peek();
498 }
499 }
501 > private _value(): string {
502 const token = this._peek();
503 switch (token.type) {
521 }
522 }
524 > private _flagsGYRe = /g|y/g;
525 > private _removeFlagsGY(flags: string): string {
526 return flags.replaceAll(this._flagsGYRe, '');
527 }
529 > // careful: this can throw if current token is the initial one (ie index = 0)
530 > private _previous() {
531 return this._tokens[this._current - 1];
532 }
534 > private _matchOne(token: TokenType) {
535 if (this._check(token)) {
536 this._advance();
540 return false;
541 }
543 > private _advance() {
544 if (!this._isAtEnd()) {
545 this._current++;
547 return this._previous();
548 }
550 > private _consume(type: TokenType, message: string) {
551 if (this._check(type)) {
552 return this._advance();
555 throw this._errExpectedButGot(message, this._peek());
556 }
558 > private _errExpectedButGot(expected: string, got: Token, additionalInfo?: string) {
559 const message = localize('contextkey.parser.error.expectedButGot', "Expected: {0}\nReceived: '{1}'.", expected, Scanner.getLexeme(got));
560 const offset = got.offset;
563 return Parser._parseError;
564 }
566 > private _check(type: TokenType) {
567 return this._peek().type === type;
568 }
570 > private _peek() {
571 return this._tokens[this._current];
572 }
574 > private _isAtEnd() {
575 return this._peek().type === TokenType.EOF;
576 }
577 > } contextkey.ts
578 >
579 > export abstract class ContextKeyExpr {
580 >
581 > public static false(): ContextKeyExpression {
582 return ContextKeyFalseExpr.INSTANCE;
583 }
584 > public static true(): ContextKeyExpression { contextkey.ts
585 return ContextKeyTrueExpr.INSTANCE;
586 }
587 > public static has(key: string): ContextKeyExpression { contextkey.ts
588 return ContextKeyDefinedExpr.create(key);
589 }
590 > public static equals(key: string, value: any): ContextKeyExpression { contextkey.ts
591 return ContextKeyEqualsExpr.create(key, value);
592 }
593 > public static notEquals(key: string, value: any): ContextKeyExpression { contextkey.ts
594 return ContextKeyNotEqualsExpr.create(key, value);
595 }
596 > public static regex(key: string, value: RegExp): ContextKeyExpression { contextkey.ts
597 return ContextKeyRegexExpr.create(key, value);
598 }
599 > public static in(key: string, value: string): ContextKeyExpression { contextkey.ts
600 return ContextKeyInExpr.create(key, value);
601 }
602 > public static notIn(key: string, value: string): ContextKeyExpression { contextkey.ts
603 return ContextKeyNotInExpr.create(key, value);
604 }
605 > public static not(key: string): ContextKeyExpression { contextkey.ts
606 return ContextKeyNotExpr.create(key);
607 }
608 > public static and(...expr: Array<ContextKeyExpression | undefined | null>): ContextKeyExpression | undefined { contextkey.ts
609 return ContextKeyAndExpr.create(expr, null, true);
610 }
611 > public static or(...expr: Array<ContextKeyExpression | undefined | null>): ContextKeyExpression | undefined { contextkey.ts
612 return ContextKeyOrExpr.create(expr, null, true);
613 }
614 > public static greater(key: string, value: number): ContextKeyExpression { contextkey.ts
615 return ContextKeyGreaterExpr.create(key, value);
616 }
617 > public static greaterEquals(key: string, value: number): ContextKeyExpression { contextkey.ts
618 return ContextKeyGreaterEqualsExpr.create(key, value);
619 }
620 > public static smaller(key: string, value: number): ContextKeyExpression { contextkey.ts
621 return ContextKeySmallerExpr.create(key, value);
622 }
623 > public static smallerEquals(key: string, value: number): ContextKeyExpression { contextkey.ts
624 return ContextKeySmallerEqualsExpr.create(key, value);
625 }
627 > private static _parser = new Parser({ regexParsingWithErrorRecovery: false });
628 > public static deserialize(serialized: string | null | undefined): ContextKeyExpression | undefined {
629 if (serialized === undefined || serialized === null) { // an empty string needs to be handled by the parser to get a corresponding parsing error reported
630 return undefined;
634 return expr;
635 }
637 > }
638 >
639 >
640 > export function validateWhenClauses(whenClauses: string[]): any {
641
642 const parser = new Parser({ regexParsingWithErrorRecovery: false }); // we run with no recovery to guide users to use correct regexes
664 });
665 }
667 > export function expressionsAreEqualWithConstantSubstitution(a: ContextKeyExpression | null | undefined, b: ContextKeyExpression | null | undefined): boolean {
668 const aExpr = a ? a.substituteConstants() : undefined;
669 const bExpr = b ? b.substituteConstants() : undefined;
676 return aExpr.equals(bExpr);
677 }
679 function cmp(a: ContextKeyExpression, b: ContextKeyExpression): number {
680 return a.cmp(b);
681 }
683 > export class ContextKeyFalseExpr implements IContextKeyExpression {
684 > public static INSTANCE = new ContextKeyFalseExpr();
685 >
686 > public readonly type = ContextKeyExprType.False;
687 >
688 > protected constructor() {
689 > }
690 >
691 > public cmp(other: ContextKeyExpression): number {
692 return this.type - other.type;
693 }
695 > public equals(other: ContextKeyExpression): boolean {
696 return (other.type === this.type);
697 }
699 > public substituteConstants(): ContextKeyExpression | undefined {
700 return this;
701 }
703 > public evaluate(context: IContext): boolean {
704 return false;
705 }
707 > public serialize(): string {
708 return 'false';
709 }
711 > public keys(): string[] {
712 return [];
713 }
715 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
716 return this;
717 }
719 > public negate(): ContextKeyExpression {
720 return ContextKeyTrueExpr.INSTANCE;
721 }
722 > } contextkey.ts
723 >
724 > export class ContextKeyTrueExpr implements IContextKeyExpression {
725 > public static INSTANCE = new ContextKeyTrueExpr();
726 >
727 > public readonly type = ContextKeyExprType.True;
728 >
729 > protected constructor() {
730 > }
731 >
732 > public cmp(other: ContextKeyExpression): number {
733 return this.type - other.type;
734 }
736 > public equals(other: ContextKeyExpression): boolean {
737 return (other.type === this.type);
738 }
740 > public substituteConstants(): ContextKeyExpression | undefined {
741 return this;
742 }
744 > public evaluate(context: IContext): boolean {
745 return true;
746 }
748 > public serialize(): string {
749 return 'true';
750 }
752 > public keys(): string[] {
753 return [];
754 }
756 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
757 return this;
758 }
760 > public negate(): ContextKeyExpression {
761 return ContextKeyFalseExpr.INSTANCE;
762 }
763 > } contextkey.ts
764 >
765 > export class ContextKeyDefinedExpr implements IContextKeyExpression {
766 > public static create(key: string, negated: ContextKeyExpression | null = null): ContextKeyExpression {
767 > const constantValue = CONSTANT_VALUES.get(key);
768 > if (typeof constantValue === 'boolean') {
769 > return constantValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE; contextkey.ts
770 > }
771 > return new ContextKeyDefinedExpr(key, negated); contextkey.ts
772 > }
773 >
774 > public readonly type = ContextKeyExprType.Defined;
775 >
776 > protected constructor(
777 > readonly key: string, contextkey.ts
778 > private negated: ContextKeyExpression | null
779 > ) {
780 > }
782 > public cmp(other: ContextKeyExpression): number {
783 if (other.type !== this.type) {
784 return this.type - other.type;
786 return cmp1(this.key, other.key);
787 }
789 > public equals(other: ContextKeyExpression): boolean {
790 if (other.type === this.type) {
791 return (this.key === other.key);
793 return false;
794 }
796 > public substituteConstants(): ContextKeyExpression | undefined {
797 const constantValue = CONSTANT_VALUES.get(this.key);
798 if (typeof constantValue === 'boolean') {
801 return this;
802 }
804 > public evaluate(context: IContext): boolean {
805 return (!!context.getValue(this.key));
806 }
808 > public serialize(): string {
809 return this.key;
810 }
812 > public keys(): string[] {
813 return [this.key];
814 }
816 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
817 return mapFnc.mapDefined(this.key);
818 }
820 > public negate(): ContextKeyExpression {
821 if (!this.negated) {
822 this.negated = ContextKeyNotExpr.create(this.key, this);
824 return this.negated;
825 }
826 > } contextkey.ts
827 >
828 > export class ContextKeyEqualsExpr implements IContextKeyExpression {
829 >
830 > public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
831 > if (typeof value === 'boolean') {
832 > return (value ? ContextKeyDefinedExpr.create(key, negated) : ContextKeyNotExpr.create(key, negated)); contextkey.ts
833 > }
834 > const constantValue = CONSTANT_VALUES.get(key); contextkey.ts
835 > if (typeof constantValue === 'boolean') {
836 > const trueValue = constantValue ? 'true' : 'false'; contextkey.ts
837 > return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE);
838 > }
839 > return new ContextKeyEqualsExpr(key, value, negated); contextkey.ts
840 > } contextkey.ts
841 >
842 > public readonly type = ContextKeyExprType.Equals;
843 >
844 > private constructor(
845 private readonly key: string,
846 private readonly value: any,
848 ) {
849 }
851 > public cmp(other: ContextKeyExpression): number {
852 if (other.type !== this.type) {
853 return this.type - other.type;
855 return cmp2(this.key, this.value, other.key, other.value);
856 }
858 > public equals(other: ContextKeyExpression): boolean {
859 if (other.type === this.type) {
860 return (this.key === other.key && this.value === other.value);
862 return false;
863 }
865 > public substituteConstants(): ContextKeyExpression | undefined {
866 const constantValue = CONSTANT_VALUES.get(this.key);
867 if (typeof constantValue === 'boolean') {
871 return this;
872 }
874 > public evaluate(context: IContext): boolean {
875 // Intentional ==
876 // eslint-disable-next-line eqeqeq
877 return (context.getValue(this.key) == this.value);
878 }
880 > public serialize(): string {
881 return `${this.key} == '${this.value}'`;
882 }
884 > public keys(): string[] {
885 return [this.key];
886 }
888 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
889 return mapFnc.mapEquals(this.key, this.value);
890 }
892 > public negate(): ContextKeyExpression {
893 if (!this.negated) {
894 this.negated = ContextKeyNotEqualsExpr.create(this.key, this.value, this);
896 return this.negated;
897 }
898 > } contextkey.ts
899 >
900 > export class ContextKeyInExpr implements IContextKeyExpression {
901 >
902 > public static create(key: string, valueKey: string): ContextKeyInExpr {
903 > return new ContextKeyInExpr(key, valueKey);
904 > }
905 >
906 > public readonly type = ContextKeyExprType.In;
907 > private negated: ContextKeyExpression | null = null;
908 >
909 > private constructor(
910 private readonly key: string,
911 private readonly valueKey: string,
912 ) {
913 }
915 > public cmp(other: ContextKeyExpression): number {
916 if (other.type !== this.type) {
917 return this.type - other.type;
919 return cmp2(this.key, this.valueKey, other.key, other.valueKey);
920 }
922 > public equals(other: ContextKeyExpression): boolean {
923 if (other.type === this.type) {
924 return (this.key === other.key && this.valueKey === other.valueKey);
926 return false;
927 }
929 > public substituteConstants(): ContextKeyExpression | undefined {
930 return this;
931 }
933 > public evaluate(context: IContext): boolean {
934 const source = context.getValue(this.valueKey);
935
964 return false;
965 }
967 > public serialize(): string {
968 return `${this.key} in '${this.valueKey}'`;
969 }
971 > public keys(): string[] {
972 return [this.key, this.valueKey];
973 }
975 > public map(mapFnc: IContextKeyExprMapper): ContextKeyInExpr {
976 return mapFnc.mapIn(this.key, this.valueKey);
977 }
979 > public negate(): ContextKeyExpression {
980 if (!this.negated) {
981 this.negated = ContextKeyNotInExpr.create(this.key, this.valueKey);
983 return this.negated;
984 }
985 > } contextkey.ts
986 >
987 > export class ContextKeyNotInExpr implements IContextKeyExpression {
988 >
989 > public static create(key: string, valueKey: string): ContextKeyNotInExpr {
990 > return new ContextKeyNotInExpr(key, valueKey);
991 > }
992 >
993 > public readonly type = ContextKeyExprType.NotIn;
994 >
995 > private readonly _negated: ContextKeyInExpr;
996 >
997 > private constructor(
998 private readonly key: string,
999 private readonly valueKey: string,
1001 this._negated = ContextKeyInExpr.create(key, valueKey);
1002 }
1003 > contextkey.ts
1004 > public cmp(other: ContextKeyExpression): number {
1005 if (other.type !== this.type) {
1006 return this.type - other.type;
1008 return this._negated.cmp(other._negated);
1009 }
1010 > contextkey.ts
1011 > public equals(other: ContextKeyExpression): boolean {
1012 if (other.type === this.type) {
1013 return this._negated.equals(other._negated);
1015 return false;
1016 }
1017 > contextkey.ts
1018 > public substituteConstants(): ContextKeyExpression | undefined {
1019 return this;
1020 }
1021 > contextkey.ts
1022 > public evaluate(context: IContext): boolean {
1023 return !this._negated.evaluate(context);
1024 }
1025 > contextkey.ts
1026 > public serialize(): string {
1027 return `${this.key} not in '${this.valueKey}'`;
1028 }
1029 > contextkey.ts
1030 > public keys(): string[] {
1031 return this._negated.keys();
1032 }
1033 > contextkey.ts
1034 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1035 return mapFnc.mapNotIn(this.key, this.valueKey);
1036 }
1037 > contextkey.ts
1038 > public negate(): ContextKeyExpression {
1039 return this._negated;
1040 }
1041 > } contextkey.ts
1042 >
1043 > export class ContextKeyNotEqualsExpr implements IContextKeyExpression {
1044 >
1045 > public static create(key: string, value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1046 > if (typeof value === 'boolean') {
1047 > if (value) { contextkey.ts
1048 > return ContextKeyNotExpr.create(key, negated); contextkey.ts
1049 > }
1050 > return ContextKeyDefinedExpr.create(key, negated); contextkey.ts
1051 > }
1052 > const constantValue = CONSTANT_VALUES.get(key); contextkey.ts
1053 > if (typeof constantValue === 'boolean') {
1054 > const falseValue = constantValue ? 'true' : 'false'; contextkey.ts
1055 > return (value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE);
1056 > }
1057 > return new ContextKeyNotEqualsExpr(key, value, negated); contextkey.ts
1058 > } contextkey.ts
1059 >
1060 > public readonly type = ContextKeyExprType.NotEquals;
1061 >
1062 > private constructor(
1063 private readonly key: string,
1064 private readonly value: any,
1066 ) {
1067 }
1068 > contextkey.ts
1069 > public cmp(other: ContextKeyExpression): number {
1070 if (other.type !== this.type) {
1071 return this.type - other.type;
1073 return cmp2(this.key, this.value, other.key, other.value);
1074 }
1075 > contextkey.ts
1076 > public equals(other: ContextKeyExpression): boolean {
1077 if (other.type === this.type) {
1078 return (this.key === other.key && this.value === other.value);
1080 return false;
1081 }
1082 > contextkey.ts
1083 > public substituteConstants(): ContextKeyExpression | undefined {
1084 const constantValue = CONSTANT_VALUES.get(this.key);
1085 if (typeof constantValue === 'boolean') {
1089 return this;
1090 }
1091 > contextkey.ts
1092 > public evaluate(context: IContext): boolean {
1093 // Intentional !=
1094 // eslint-disable-next-line eqeqeq
1095 return (context.getValue(this.key) != this.value);
1096 }
1097 > contextkey.ts
1098 > public serialize(): string {
1099 return `${this.key} != '${this.value}'`;
1100 }
1101 > contextkey.ts
1102 > public keys(): string[] {
1103 return [this.key];
1104 }
1105 > contextkey.ts
1106 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1107 return mapFnc.mapNotEquals(this.key, this.value);
1108 }
1109 > contextkey.ts
1110 > public negate(): ContextKeyExpression {
1111 if (!this.negated) {
1112 this.negated = ContextKeyEqualsExpr.create(this.key, this.value, this);
1114 return this.negated;
1115 }
1116 > } contextkey.ts
1117 >
1118 > export class ContextKeyNotExpr implements IContextKeyExpression {
1119 >
1120 > public static create(key: string, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1121 > const constantValue = CONSTANT_VALUES.get(key);
1122 > if (typeof constantValue === 'boolean') {
1123 > return (constantValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE); contextkey.ts
1124 > }
1125 > return new ContextKeyNotExpr(key, negated); contextkey.ts
1126 > }
1127 >
1128 > public readonly type = ContextKeyExprType.Not;
1129 >
1130 > private constructor(
1131 private readonly key: string,
1132 private negated: ContextKeyExpression | null
1133 ) {
1134 }
1135 > contextkey.ts
1136 > public cmp(other: ContextKeyExpression): number {
1137 if (other.type !== this.type) {
1138 return this.type - other.type;
1140 return cmp1(this.key, other.key);
1141 }
1142 > contextkey.ts
1143 > public equals(other: ContextKeyExpression): boolean {
1144 if (other.type === this.type) {
1145 return (this.key === other.key);
1147 return false;
1148 }
1149 > contextkey.ts
1150 > public substituteConstants(): ContextKeyExpression | undefined {
1151 const constantValue = CONSTANT_VALUES.get(this.key);
1152 if (typeof constantValue === 'boolean') {
1155 return this;
1156 }
1157 > contextkey.ts
1158 > public evaluate(context: IContext): boolean {
1159 return (!context.getValue(this.key));
1160 }
1161 > contextkey.ts
1162 > public serialize(): string {
1163 return `!${this.key}`;
1164 }
1165 > contextkey.ts
1166 > public keys(): string[] {
1167 return [this.key];
1168 }
1169 > contextkey.ts
1170 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1171 return mapFnc.mapNot(this.key);
1172 }
1173 > contextkey.ts
1174 > public negate(): ContextKeyExpression {
1175 if (!this.negated) {
1176 this.negated = ContextKeyDefinedExpr.create(this.key, this);
1178 return this.negated;
1179 }
1180 > } contextkey.ts
1181 >
1182 function withFloatOrStr<T extends ContextKeyExpression>(value: any, callback: (value: number | string) => T): T | ContextKeyFalseExpr {
1183 if (typeof value === 'string') {
1192 return ContextKeyFalseExpr.INSTANCE;
1193 }
1194 > contextkey.ts
1195 > export class ContextKeyGreaterExpr implements IContextKeyExpression {
1196 >
1197 > public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1198 > return withFloatOrStr(_value, (value) => new ContextKeyGreaterExpr(key, value, negated));
1199 > }
1200 >
1201 > public readonly type = ContextKeyExprType.Greater;
1202 >
1203 > private constructor(
1204 private readonly key: string,
1205 private readonly value: number | string,
1206 private negated: ContextKeyExpression | null
1207 ) { }
1208 > contextkey.ts
1209 > public cmp(other: ContextKeyExpression): number {
1210 if (other.type !== this.type) {
1211 return this.type - other.type;
1213 return cmp2(this.key, this.value, other.key, other.value);
1214 }
1215 > contextkey.ts
1216 > public equals(other: ContextKeyExpression): boolean {
1217 if (other.type === this.type) {
1218 return (this.key === other.key && this.value === other.value);
1220 return false;
1221 }
1222 > contextkey.ts
1223 > public substituteConstants(): ContextKeyExpression | undefined {
1224 return this;
1225 }
1226 > contextkey.ts
1227 > public evaluate(context: IContext): boolean {
1228 if (typeof this.value === 'string') {
1229 return false;
1231 return (parseFloat(context.getValue<any>(this.key)) > this.value);
1232 }
1233 > contextkey.ts
1234 > public serialize(): string {
1235 return `${this.key} > ${this.value}`;
1236 }
1237 > contextkey.ts
1238 > public keys(): string[] {
1239 return [this.key];
1240 }
1241 > contextkey.ts
1242 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1243 return mapFnc.mapGreater(this.key, this.value);
1244 }
1245 > contextkey.ts
1246 > public negate(): ContextKeyExpression {
1247 if (!this.negated) {
1248 this.negated = ContextKeySmallerEqualsExpr.create(this.key, this.value, this);
1250 return this.negated;
1251 }
1252 > } contextkey.ts
1253 >
1254 > export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {
1255 >
1256 > public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1257 > return withFloatOrStr(_value, (value) => new ContextKeyGreaterEqualsExpr(key, value, negated));
1258 > }
1259 >
1260 > public readonly type = ContextKeyExprType.GreaterEquals;
1261 >
1262 > private constructor(
1263 private readonly key: string,
1264 private readonly value: number | string,
1265 private negated: ContextKeyExpression | null
1266 ) { }
1267 > contextkey.ts
1268 > public cmp(other: ContextKeyExpression): number {
1269 if (other.type !== this.type) {
1270 return this.type - other.type;
1272 return cmp2(this.key, this.value, other.key, other.value);
1273 }
1274 > contextkey.ts
1275 > public equals(other: ContextKeyExpression): boolean {
1276 if (other.type === this.type) {
1277 return (this.key === other.key && this.value === other.value);
1279 return false;
1280 }
1281 > contextkey.ts
1282 > public substituteConstants(): ContextKeyExpression | undefined {
1283 return this;
1284 }
1285 > contextkey.ts
1286 > public evaluate(context: IContext): boolean {
1287 if (typeof this.value === 'string') {
1288 return false;
1290 return (parseFloat(context.getValue<any>(this.key)) >= this.value);
1291 }
1292 > contextkey.ts
1293 > public serialize(): string {
1294 return `${this.key} >= ${this.value}`;
1295 }
1296 > contextkey.ts
1297 > public keys(): string[] {
1298 return [this.key];
1299 }
1300 > contextkey.ts
1301 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1302 return mapFnc.mapGreaterEquals(this.key, this.value);
1303 }
1304 > contextkey.ts
1305 > public negate(): ContextKeyExpression {
1306 if (!this.negated) {
1307 this.negated = ContextKeySmallerExpr.create(this.key, this.value, this);
1309 return this.negated;
1310 }
1311 > } contextkey.ts
1312 >
1313 > export class ContextKeySmallerExpr implements IContextKeyExpression {
1314 >
1315 > public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1316 > return withFloatOrStr(_value, (value) => new ContextKeySmallerExpr(key, value, negated));
1317 > }
1318 >
1319 > public readonly type = ContextKeyExprType.Smaller;
1320 >
1321 > private constructor(
1322 private readonly key: string,
1323 private readonly value: number | string,
1325 ) {
1326 }
1327 > contextkey.ts
1328 > public cmp(other: ContextKeyExpression): number {
1329 if (other.type !== this.type) {
1330 return this.type - other.type;
1332 return cmp2(this.key, this.value, other.key, other.value);
1333 }
1334 > contextkey.ts
1335 > public equals(other: ContextKeyExpression): boolean {
1336 if (other.type === this.type) {
1337 return (this.key === other.key && this.value === other.value);
1339 return false;
1340 }
1341 > contextkey.ts
1342 > public substituteConstants(): ContextKeyExpression | undefined {
1343 return this;
1344 }
1345 > contextkey.ts
1346 > public evaluate(context: IContext): boolean {
1347 if (typeof this.value === 'string') {
1348 return false;
1350 return (parseFloat(context.getValue<any>(this.key)) < this.value);
1351 }
1352 > contextkey.ts
1353 > public serialize(): string {
1354 return `${this.key} < ${this.value}`;
1355 }
1356 > contextkey.ts
1357 > public keys(): string[] {
1358 return [this.key];
1359 }
1360 > contextkey.ts
1361 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1362 return mapFnc.mapSmaller(this.key, this.value);
1363 }
1364 > contextkey.ts
1365 > public negate(): ContextKeyExpression {
1366 if (!this.negated) {
1367 this.negated = ContextKeyGreaterEqualsExpr.create(this.key, this.value, this);
1369 return this.negated;
1370 }
1371 > } contextkey.ts
1372 >
1373 > export class ContextKeySmallerEqualsExpr implements IContextKeyExpression {
1374 >
1375 > public static create(key: string, _value: any, negated: ContextKeyExpression | null = null): ContextKeyExpression {
1376 > return withFloatOrStr(_value, (value) => new ContextKeySmallerEqualsExpr(key, value, negated));
1377 > }
1378 >
1379 > public readonly type = ContextKeyExprType.SmallerEquals;
1380 >
1381 > private constructor(
1382 private readonly key: string,
1383 private readonly value: number | string,
1385 ) {
1386 }
1387 > contextkey.ts
1388 > public cmp(other: ContextKeyExpression): number {
1389 if (other.type !== this.type) {
1390 return this.type - other.type;
1392 return cmp2(this.key, this.value, other.key, other.value);
1393 }
1394 > contextkey.ts
1395 > public equals(other: ContextKeyExpression): boolean {
1396 if (other.type === this.type) {
1397 return (this.key === other.key && this.value === other.value);
1399 return false;
1400 }
1401 > contextkey.ts
1402 > public substituteConstants(): ContextKeyExpression | undefined {
1403 return this;
1404 }
1405 > contextkey.ts
1406 > public evaluate(context: IContext): boolean {
1407 if (typeof this.value === 'string') {
1408 return false;
1410 return (parseFloat(context.getValue<any>(this.key)) <= this.value);
1411 }
1412 > contextkey.ts
1413 > public serialize(): string {
1414 return `${this.key} <= ${this.value}`;
1415 }
1416 > contextkey.ts
1417 > public keys(): string[] {
1418 return [this.key];
1419 }
1420 > contextkey.ts
1421 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1422 return mapFnc.mapSmallerEquals(this.key, this.value);
1423 }
1424 > contextkey.ts
1425 > public negate(): ContextKeyExpression {
1426 if (!this.negated) {
1427 this.negated = ContextKeyGreaterExpr.create(this.key, this.value, this);
1429 return this.negated;
1430 }
1431 > } contextkey.ts
1432 >
1433 > export class ContextKeyRegexExpr implements IContextKeyExpression {
1434 >
1435 > public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr {
1436 > return new ContextKeyRegexExpr(key, regexp);
1437 > }
1438 >
1439 > public readonly type = ContextKeyExprType.Regex;
1440 > private negated: ContextKeyExpression | null = null;
1441 >
1442 > private constructor(
1443 private readonly key: string,
1444 private readonly regexp: RegExp | null
1446 //
1447 }
1448 > contextkey.ts
1449 > public cmp(other: ContextKeyExpression): number {
1450 if (other.type !== this.type) {
1451 return this.type - other.type;
1467 return 0;
1468 }
1469 > contextkey.ts
1470 > public equals(other: ContextKeyExpression): boolean {
1471 if (other.type === this.type) {
1472 const thisSource = this.regexp ? this.regexp.source : '';
1476 return false;
1477 }
1478 > contextkey.ts
1479 > public substituteConstants(): ContextKeyExpression | undefined {
1480 return this;
1481 }
1482 > contextkey.ts
1483 > public evaluate(context: IContext): boolean {
1484 const value = context.getValue<any>(this.key);
1485 return this.regexp ? this.regexp.test(value) : false;
1486 }
1487 > contextkey.ts
1488 > public serialize(): string {
1489 const value = this.regexp
1490 ? `/${this.regexp.source}/${this.regexp.flags}`
1492 return `${this.key} =~ ${value}`;
1493 }
1494 > contextkey.ts
1495 > public keys(): string[] {
1496 return [this.key];
1497 }
1498 > contextkey.ts
1499 > public map(mapFnc: IContextKeyExprMapper): ContextKeyRegexExpr {
1500 return mapFnc.mapRegex(this.key, this.regexp);
1501 }
1502 > contextkey.ts
1503 > public negate(): ContextKeyExpression {
1504 if (!this.negated) {
1505 this.negated = ContextKeyNotRegexExpr.create(this);
1507 return this.negated;
1508 }
1509 > } contextkey.ts
1510 >
1511 > export class ContextKeyNotRegexExpr implements IContextKeyExpression {
1512 >
1513 > public static create(actual: ContextKeyRegexExpr): ContextKeyExpression {
1514 > return new ContextKeyNotRegexExpr(actual);
1515 > }
1516 >
1517 > public readonly type = ContextKeyExprType.NotRegex;
1518 >
1519 > private constructor(private readonly _actual: ContextKeyRegexExpr) {
1520 //
1521 }
1522 > contextkey.ts
1523 > public cmp(other: ContextKeyExpression): number {
1524 if (other.type !== this.type) {
1525 return this.type - other.type;
1527 return this._actual.cmp(other._actual);
1528 }
1529 > contextkey.ts
1530 > public equals(other: ContextKeyExpression): boolean {
1531 if (other.type === this.type) {
1532 return this._actual.equals(other._actual);
1534 return false;
1535 }
1536 > contextkey.ts
1537 > public substituteConstants(): ContextKeyExpression | undefined {
1538 return this;
1539 }
1540 > contextkey.ts
1541 > public evaluate(context: IContext): boolean {
1542 return !this._actual.evaluate(context);
1543 }
1544 > contextkey.ts
1545 > public serialize(): string {
1546 return `!(${this._actual.serialize()})`;
1547 }
1548 > contextkey.ts
1549 > public keys(): string[] {
1550 return this._actual.keys();
1551 }
1552 > contextkey.ts
1553 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1554 return new ContextKeyNotRegexExpr(this._actual.map(mapFnc));
1555 }
1556 > contextkey.ts
1557 > public negate(): ContextKeyExpression {
1558 return this._actual;
1559 }
1560 > } contextkey.ts
1561 >
1562 > /**
1563 > * @returns the same instance if nothing changed.
1564 > */
1565 function eliminateConstantsInArray(arr: ContextKeyExpression[]): (ContextKeyExpression | undefined)[] {
1566 // Allocate array only if there is a difference
1591 return newArr;
1592 }
1593 > contextkey.ts
1594 > export class ContextKeyAndExpr implements IContextKeyExpression {
1595 >
1596 > public static create(_expr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {
1597 > return ContextKeyAndExpr._normalizeArr(_expr, negated, extraRedundantCheck);
1598 > }
1599 >
1600 > public readonly type = ContextKeyExprType.And;
1601 >
1602 > private constructor(
1603 public readonly expr: ContextKeyExpression[],
1604 private negated: ContextKeyExpression | null
1605 ) {
1606 }
1607 > contextkey.ts
1608 > public cmp(other: ContextKeyExpression): number {
1609 if (other.type !== this.type) {
1610 return this.type - other.type;
1624 return 0;
1625 }
1626 > contextkey.ts
1627 > public equals(other: ContextKeyExpression): boolean {
1628 if (other.type === this.type) {
1629 if (this.expr.length !== other.expr.length) {
1639 return false;
1640 }
1641 > contextkey.ts
1642 > public substituteConstants(): ContextKeyExpression | undefined {
1643 const exprArr = eliminateConstantsInArray(this.expr);
1644 if (exprArr === this.expr) {
1648 return ContextKeyAndExpr.create(exprArr, this.negated, false);
1649 }
1650 > contextkey.ts
1651 > public evaluate(context: IContext): boolean {
1652 for (let i = 0, len = this.expr.length; i < len; i++) {
1653 if (!this.expr[i].evaluate(context)) {
1657 return true;
1658 }
1659 > contextkey.ts
1660 > private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {
1661 const expr: ContextKeyExpression[] = [];
1662 let hasTrue = false;
1762 return new ContextKeyAndExpr(expr, negated);
1763 }
1764 > contextkey.ts
1765 > public serialize(): string {
1766 return this.expr.map(e => e.serialize()).join(' && ');
1767 }
1768 > contextkey.ts
1769 > public keys(): string[] {
1770 const result: string[] = [];
1771 for (const expr of this.expr) {
1774 return result;
1775 }
1776 > contextkey.ts
1777 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1778 return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc)), null);
1779 }
1780 > contextkey.ts
1781 > public negate(): ContextKeyExpression {
1782 if (!this.negated) {
1783 const result: ContextKeyExpression[] = [];
1789 return this.negated;
1790 }
1791 > } contextkey.ts
1792 >
1793 > export class ContextKeyOrExpr implements IContextKeyExpression {
1794 >
1795 > public static create(_expr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {
1796 > return ContextKeyOrExpr._normalizeArr(_expr, negated, extraRedundantCheck);
1797 > }
1798 >
1799 > public readonly type = ContextKeyExprType.Or;
1800 >
1801 > private constructor(
1802 public readonly expr: ContextKeyExpression[],
1803 private negated: ContextKeyExpression | null
1804 ) {
1805 }
1806 > contextkey.ts
1807 > public cmp(other: ContextKeyExpression): number {
1808 if (other.type !== this.type) {
1809 return this.type - other.type;
1823 return 0;
1824 }
1825 > contextkey.ts
1826 > public equals(other: ContextKeyExpression): boolean {
1827 if (other.type === this.type) {
1828 if (this.expr.length !== other.expr.length) {
1838 return false;
1839 }
1840 > contextkey.ts
1841 > public substituteConstants(): ContextKeyExpression | undefined {
1842 const exprArr = eliminateConstantsInArray(this.expr);
1843 if (exprArr === this.expr) {
1847 return ContextKeyOrExpr.create(exprArr, this.negated, false);
1848 }
1849 > contextkey.ts
1850 > public evaluate(context: IContext): boolean {
1851 for (let i = 0, len = this.expr.length; i < len; i++) {
1852 if (this.expr[i].evaluate(context)) {
1856 return false;
1857 }
1858 > contextkey.ts
1859 > private static _normalizeArr(arr: ReadonlyArray<ContextKeyExpression | null | undefined>, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined {
1860 let expr: ContextKeyExpression[] = [];
1861 let hasFalse = false;
1932 return new ContextKeyOrExpr(expr, negated);
1933 }
1934 > contextkey.ts
1935 > public serialize(): string {
1936 return this.expr.map(e => e.serialize()).join(' || ');
1937 }
1938 > contextkey.ts
1939 > public keys(): string[] {
1940 const result: string[] = [];
1941 for (const expr of this.expr) {
1944 return result;
1945 }
1946 > contextkey.ts
1947 > public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
1948 return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc)), null);
1949 }
1950 > contextkey.ts
1951 > public negate(): ContextKeyExpression {
1952 if (!this.negated) {
1953 const result: ContextKeyExpression[] = [];
1976 return this.negated;
1977 }
1978 > } contextkey.ts
1979 >
1980 > export interface ContextKeyInfo {
1981 > readonly key: string;
1982 > readonly type?: string;
1983 > readonly description?: string;
1984 > }
1985 >
1986 > export class RawContextKey<T extends ContextKeyValue> extends ContextKeyDefinedExpr {
1987 >
1988 > private static _info: ContextKeyInfo[] = [];
1989 >
1990 > static all(): IterableIterator<ContextKeyInfo> {
1991 return RawContextKey._info.values();
1992 }
1993 > contextkey.ts
1994 > private readonly _defaultValue: T | undefined;
1995 >
1996 > constructor(key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string; description: string }) {
1997 > super(key, null); contextkey.ts
1998 > this._defaultValue = defaultValue;
1999 >
2000 > // collect all context keys into a central place
2001 > if (typeof metaOrHide === 'object') {
2002 RawContextKey._info.push({ ...metaOrHide, key });
2003 > } else if (metaOrHide !== true) { contextkey.ts
2004 > RawContextKey._info.push({ key, description: metaOrHide, type: defaultValue !== null && defaultValue !== undefined ? typeof defaultValue : undefined }); contextkey.ts
2005 > }
2006 > } contextkey.ts
2007 > contextkey.ts
2008 > public bindTo(target: IContextKeyService): IContextKey<T> {
2009 return target.createKey(this.key, this._defaultValue);
2010 }
2011 > contextkey.ts
2012 > public getValue(target: IContextKeyService): T | undefined {
2013 return target.getContextKeyValue<T>(this.key);
2014 }
2015 > contextkey.ts
2016 > public toNegated(): ContextKeyExpression {
2017 return this.negate();
2018 }
2019 > contextkey.ts
2020 > public isEqualTo(value: any): ContextKeyExpression {
2021 return ContextKeyEqualsExpr.create(this.key, value);
2022 }
2023 > contextkey.ts
2024 > public notEqualsTo(value: any): ContextKeyExpression {
2025 return ContextKeyNotEqualsExpr.create(this.key, value);
2026 }
2027 > contextkey.ts
2028 > public greater(value: any): ContextKeyExpression {
2029 return ContextKeyGreaterExpr.create(this.key, value);
2030 }
2031 > } contextkey.ts
2032 >
2033 > export type ContextKeyValue = null | undefined | boolean | number | string
2034 > | Array<null | undefined | boolean | number | string>
2035 > | Record<string, null | undefined | boolean | number | string>;
2036 >
2037 > export interface IContext {
2038 > getValue<T extends ContextKeyValue = ContextKeyValue>(key: string): T | undefined;
2039 > }
2040 >
2041 > export interface IContextKey<T extends ContextKeyValue = ContextKeyValue> {
2042 > set(value: T): void;
2043 > reset(): void;
2044 > get(): T | undefined;
2045 > }
2046 >
2047 > export interface IContextKeyServiceTarget {
2048 > parentElement: IContextKeyServiceTarget | null;
2049 > setAttribute(attr: string, value: string): void;
2050 > removeAttribute(attr: string): void;
2051 > hasAttribute(attr: string): boolean;
2052 > getAttribute(attr: string): string | null;
2053 > }
2054 >
2055 > export const IContextKeyService = createDecorator<IContextKeyService>('contextKeyService');
2056 >
2057 > export interface IReadableSet<T> {
2058 > has(value: T): boolean;
2059 > }
2060 >
2061 > export interface IContextKeyChangeEvent {
2062 > affectsSome(keys: IReadableSet<string>): boolean;
2063 > allKeysContainedIn(keys: IReadableSet<string>): boolean;
2064 > }
2065 >
2066 > export type IScopedContextKeyService = IContextKeyService & IDisposable;
2067 >
2068 > export interface IContextKeyService {
2069 > readonly _serviceBrand: undefined;
2070 >
2071 > readonly onDidChangeContext: Event<IContextKeyChangeEvent>;
2072 > bufferChangeEvents(callback: Function): void;
2073 >
2074 > createKey<T extends ContextKeyValue>(key: string, defaultValue: T | undefined): IContextKey<T>;
2075 > contextMatchesRules(rules: ContextKeyExpression | undefined): boolean;
2076 > getContextKeyValue<T>(key: string): T | undefined;
2077 >
2078 > createScoped(target: IContextKeyServiceTarget): IScopedContextKeyService;
2079 > createOverlay(overlay: Iterable<[string, any]>): IContextKeyService;
2080 > getContext(target: IContextKeyServiceTarget | null): IContext;
2081 >
2082 > updateParent(parentContextKeyService: IContextKeyService): void;
2083 > }
2084 >
2085 function cmp1(key1: string, key2: string): number {
2086 if (key1 < key2) {
2092 return 0;
2093 }
2094 > contextkey.ts
2095 function cmp2(key1: string, value1: any, key2: string, value2: any): number {
2096 if (key1 < key2) {
2108 return 0;
2109 }
2110 > contextkey.ts
2111 > /**
2112 > * Returns true if it is provable `p` implies `q`.
2113 > */
2114 > export function implies(p: ContextKeyExpression, q: ContextKeyExpression): boolean {
2115
2116 if (p.type === ContextKeyExprType.False || q.type === ContextKeyExprType.True) {
2152 return p.equals(q);
2153 }
2154 > contextkey.ts
2155 > /**
2156 > * Returns true if all elements in `p` are also present in `q`.
2157 > * The two arrays are assumed to be sorted
2158 > */
2159 function allElementsIncluded(p: ContextKeyExpression[], q: ContextKeyExpression[]): boolean {
2160 let pIndex = 0;
2175 return (pIndex === p.length);
2176 }
2177 > contextkey.ts
2178 function getTerminals(node: ContextKeyExpression) {
2179 if (node.type === ContextKeyExprType.Or) {
src/vs/platform/agentHost/common/agentHostSchema.ts 574 covered LOC · 32 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentHostSchema.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { localize } from '../../../nls.js';
7 > import { structuralEquals } from '../../../base/common/equals.js';
8 > import { ConfigurationTarget, type IConfigurationService, type IConfigurationValue } from '../../configuration/common/configuration.js';
9 > import type { IMcpServerConfiguration } from '../../mcp/common/mcpPlatformTypes.js';
10 > import { TelemetryConfiguration, TelemetryLevel } from '../../telemetry/common/telemetry.js';
11 > import { SessionConfigKey } from './sessionConfigKeys.js';
12 > import type { SessionConfigPropertySchema, SessionConfigSchema } from './state/protocol/commands.js';
13 > import { JsonRpcErrorCodes, ProtocolError } from './state/sessionProtocol.js';
14 >
15 > // ---- Schema builder --------------------------------------------------------
16 >
17 > /**
18 > * A schema property with a phantom TypeScript type and a precomputed
19 > * runtime validator.
20 > *
21 > * The `<T>` type parameter is the developer's assertion about the
22 > * property's runtime shape; the validator derived from `protocol`
23 > * (`type`, `enum`, `items`, `properties`, `required`) enforces it at
24 > * runtime.
25 > */
26 > export interface ISchemaProperty<T> {
27 > readonly protocol: SessionConfigPropertySchema;
28 > /**
29 > * Returns `true` iff `value` conforms to {@link protocol}. Narrows
30 > * the type to `T` for callers. The boolean form is preferred for
31 > * control flow; use {@link assertValid} when you want a descriptive
32 > * error for the offending path.
33 > */
34 > validate(value: unknown): value is T;
35 > /**
36 > * Throws a {@link ProtocolError} with `JsonRpcErrorCodes.InvalidParams`
37 > * describing the offending path (e.g. `'permissions.allow[2]'`) when
38 > * `value` does not conform to {@link protocol}. Otherwise returns and
39 > * narrows the type to `T`.
40 > *
41 > * @param path Dotted path prefix to embed in error messages. Defaults
42 > * to empty (the value itself).
43 > */
44 > assertValid(value: unknown, path?: string): asserts value is T;
45 > }
46 >
47 > /**
48 > * Defines a strongly-typed schema property whose runtime validator is
49 > * derived from the supplied JSON-schema descriptor.
50 > */
51 > export function schemaProperty<T>(protocol: SessionConfigPropertySchema): ISchemaProperty<T> {
52 > const assertFn = buildAssert(protocol);
53 > const assertValid = (value: unknown, path: string = ''): asserts value is T => assertFn(value, path);
54 > const validate = (value: unknown): value is T => {
55 try {
56 assertFn(value, '');
60 }
61 };
62 > return { protocol, validate, assertValid }; agentHostSchema.ts
63 > }
64 >
65 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
66 > export type SchemaDefinition = Record<string, ISchemaProperty<any>>;
67 >
68 > export type SchemaValue<P> = P extends ISchemaProperty<infer T> ? T : never;
69 >
70 > export type SchemaValues<D extends SchemaDefinition> = {
71 > [K in keyof D]?: SchemaValue<D[K]>;
72 > };
73 >
74 > /**
75 > * A bundle of named schema properties plus helpers for serializing to the
76 > * protocol shape, validating a values bag at write sites, and validating
77 > * a single key at read sites.
78 > */
79 > export interface ISchema<D extends SchemaDefinition> {
80 > readonly definition: D;
81 > /** Returns the protocol-serializable schema for this bundle. */
82 > toProtocol(): SessionConfigSchema;
83 > /**
84 > * Validates each known key in `values` against its schema and returns
85 > * a new plain record. Throws a {@link ProtocolError} with a path like
86 > * `'permissions.allow[2]'` when any supplied value fails validation.
87 > * Unknown keys are passed through untouched for forward-compatibility.
88 > */
89 > values(values: SchemaValues<D>): Record<string, unknown>;
90 > /**
91 > * Returns `true` iff `value` validates against the schema for `key`.
92 > * Unknown keys return `false`.
93 > */
94 > validate<K extends keyof D & string>(key: K, value: unknown): value is SchemaValue<D[K]>;
95 > /**
96 > * Throws a {@link ProtocolError} describing the offending path when
97 > * `value` does not validate against the schema for `key`, or when
98 > * `key` is not defined in the schema.
99 > */
100 > assertValid<K extends keyof D & string>(key: K, value: unknown): asserts value is SchemaValue<D[K]>;
101 > /**
102 > * Returns a fully-typed values bag by validating each key of the
103 > * schema against `values` and falling back to the default when
104 > * the incoming value is missing or fails validation.
105 > *
106 > * Semantics: for every key declared in the schema `definition`:
107 > * - if `values[key]` validates, it is kept;
108 > * - else if `key` is present in `defaults`, the default is used;
109 > * - else the key is omitted from the result.
110 > *
111 > * This means callers MAY supply defaults for only a subset of the
112 > * schema — keys not present in `defaults` are simply left unset
113 > * when the incoming value is missing or invalid. This is useful
114 > * when some properties (e.g. per-session `permissions`) should be
115 > * inherited from a higher scope rather than materialized on every
116 > * new session.
117 > *
118 > * Intended for sanitizing untrusted input at protocol boundaries
119 > * (e.g. `resolveSessionConfig`). Keys that fail validation are
120 > * silently replaced with their default or dropped; use
121 > * {@link values} or {@link assertValid} when you want a descriptive
122 > * {@link ProtocolError} instead.
123 > */
124 > validateOrDefault<T extends Partial<{ [K in keyof D]: SchemaValue<D[K]> }>>(values: { [K in keyof T]?: unknown } | undefined, defaults: T): T;
125 > }
126 >
127 > export function createSchema<D extends SchemaDefinition>(definition: D): ISchema<D> {
128 > return {
129 > definition,
130 > toProtocol(): SessionConfigSchema {
131 > const properties: Record<string, SessionConfigPropertySchema> = {}; agentHostSchema.ts
132 > for (const key of Object.keys(definition)) {
133 > properties[key] = definition[key].protocol;
134 > }
135 > return { type: 'object', properties };
136 > },
137 > values(values) { agentHostSchema.ts
138 const raw = values as Record<string, unknown>;
139 for (const key of Object.keys(definition)) {
149 return { ...raw };
150 },
151 > validate<K extends keyof D & string>(key: K, value: unknown): value is SchemaValue<D[K]> { agentHostSchema.ts
152 const prop = definition[key];
153 return prop ? prop.validate(value) : false;
154 },
155 > assertValid<K extends keyof D & string>(key: K, value: unknown): asserts value is SchemaValue<D[K]> { agentHostSchema.ts
156 const prop: ISchemaProperty<unknown> | undefined = definition[key];
157 if (!prop) {
163 narrowed.assertValid(value, key);
164 },
165 > validateOrDefault<T extends Partial<{ [K in keyof D]: SchemaValue<D[K]> }>>(values: { [K in keyof T]?: unknown } | undefined, defaults: T): T { agentHostSchema.ts
166 > const result: Record<string, unknown> = {}; agentHostSchema.ts
167 > const raw: { [K in keyof T]?: unknown } = values ?? {};
168 > for (const key of Object.keys(definition)) {
169 > const prop = definition[key];
170 > const candidate = raw[key];
171 > if (candidate !== undefined && prop.validate(candidate)) {
172 result[key] = candidate;
173 > } else if (Object.prototype.hasOwnProperty.call(defaults, key)) { agentHostSchema.ts
174 > result[key] = (defaults as Record<string, unknown>)[key]; agentHostSchema.ts
175 > }
176 > // else: key not in defaults and incoming value missing/invalid agentHostSchema.ts
177 > // → leave unset so higher-scope defaults can fill in.
178 > }
179 > return result as T;
180 > },
182 > }
183 >
184 > // ---- Validator derivation --------------------------------------------------
185 >
186 > /**
187 > * A validator that throws a {@link ProtocolError} annotated with the
188 > * offending path when `value` does not conform, or returns normally
189 > * when it does.
190 > */
191 > type AssertValidator = (value: unknown, path: string) => void;
192 >
193 > function buildAssert(schema: SessionConfigPropertySchema): AssertValidator {
194 > if (schema.type === 'object' && schema.properties) {
195 > const propAsserts: Record<string, AssertValidator> = {};
196 > for (const key of Object.keys(schema.properties)) {
197 > propAsserts[key] = buildAssert(schema.properties[key] as SessionConfigPropertySchema);
198 > }
199 > const required = new Set(schema.required ?? []);
200 > return (value, path) => {
201 if (typeof value !== 'object' || value === null || Array.isArray(value)) {
202 throw invalidParams(path, 'object', value);
214 }
215 };
217 > if (schema.type === 'array' && schema.items) {
218 > const itemAssert = buildAssert(schema.items as SessionConfigPropertySchema);
219 > return (value, path) => {
220 if (!Array.isArray(value)) {
221 throw invalidParams(path, 'array', value);
225 }
226 };
228 > return buildPrimitiveAssert(schema);
229 > }
230 >
231 > function buildPrimitiveAssert(schema: SessionConfigPropertySchema): AssertValidator {
232 > const enumDynamic = schema.enumDynamic === true;
233 > return (value, path) => {
234 switch (schema.type) {
235 case 'string': if (typeof value !== 'string') { throw invalidParams(path, 'string', value); } break;
243 }
244 };
246 >
247 function invalidParams(path: string, expected: string, value: unknown): ProtocolError {
248 return new ProtocolError(JsonRpcErrorCodes.InvalidParams, `Invalid value at '${path || '<root>'}': expected ${expected}, got ${safeStringify(value)}`);
249 }
251 function joinPath(parent: string, key: string): string {
252 return parent ? `${parent}.${key}` : key;
253 }
255 function safeStringify(value: unknown): string {
256 try {
260 }
261 }
263 > // ---- Platform-owned schema -------------------------------------------------
264 >
265 > export type AutoApproveLevel = 'default' | 'assisted' | 'autoApprove';
266 >
267 > export type SessionMode = 'interactive' | 'plan' | 'autopilot';
268 >
269 > export interface IPermissionsValue {
270 > readonly allow: readonly string[];
271 > readonly deny: readonly string[];
272 > }
273 >
274 > const permissionsProperty = schemaProperty<IPermissionsValue>({
275 > type: 'object',
276 > title: localize('agentHost.sessionConfig.permissions', "Permissions"),
277 > description: localize('agentHost.sessionConfig.permissionsDescription', "Per-tool session permissions. Updated automatically when approving a tool \"in this Session\"."),
278 > properties: {
279 > allow: {
280 > type: 'array',
281 > title: localize('agentHost.sessionConfig.permissions.allow', "Allowed tools"),
282 > items: {
283 > type: 'string',
284 > title: localize('agentHost.sessionConfig.permissions.toolName', "Tool name"),
285 > },
286 > },
287 > deny: {
288 > type: 'array',
289 > title: localize('agentHost.sessionConfig.permissions.deny', "Denied tools"),
290 > items: {
291 > type: 'string',
292 > title: localize('agentHost.sessionConfig.permissions.toolName', "Tool name"),
293 > },
294 > },
295 > },
296 > default: { allow: [], deny: [] },
297 > sessionMutable: true,
298 > });
299 >
300 > /**
301 > * Session-config properties owned by the platform itself — i.e. consumed
302 > * by the agent host rather than by any particular agent.
303 > *
304 > * Agents extend this schema by spreading `platformSessionSchema.definition`
305 > * into their own {@link createSchema} call together with any
306 > * provider-specific properties.
307 > */
308 > export const platformSessionSchema = createSchema({
309 > [SessionConfigKey.AutoApprove]: schemaProperty<AutoApproveLevel>({
310 > type: 'string',
311 > title: localize('agentHost.sessionConfig.autoApprove', "Approvals"),
312 > description: localize('agentHost.sessionConfig.autoApproveDescription', "Tool approval behavior for this session"),
313 > enum: ['default', 'assisted', 'autoApprove'],
314 > enumLabels: [
315 > localize('agentHost.sessionConfig.autoApprove.default', "Default approvals"),
316 > localize('agentHost.sessionConfig.autoApprove.assisted', "Assisted permissions"),
317 > localize('agentHost.sessionConfig.autoApprove.bypass', "Allow all"),
318 > ],
319 > enumDescriptions: [
320 > localize('agentHost.sessionConfig.autoApprove.defaultDescription', "Asks when approval settings don't apply"),
321 > localize('agentHost.sessionConfig.autoApprove.assistedDescription', "Evaluates risk before running tools"),
322 > localize('agentHost.sessionConfig.autoApprove.bypassDescription', "Runs tool calls without asking"),
323 > ],
324 > default: 'default',
325 > sessionMutable: true,
326 > }),
327 > [SessionConfigKey.Permissions]: permissionsProperty,
328 > [SessionConfigKey.Mode]: schemaProperty<SessionMode>({
329 > type: 'string',
330 > title: localize('agentHost.sessionConfig.mode', "Agent Mode"),
331 > description: localize('agentHost.sessionConfig.modeDescription', "How the agent should approach this turn"),
332 > enum: ['interactive', 'plan', 'autopilot'],
333 > enumLabels: [
334 > localize('agentHost.sessionConfig.mode.interactive', "Interactive"),
335 > localize('agentHost.sessionConfig.mode.plan', "Plan"),
336 > localize('agentHost.sessionConfig.mode.autopilot', "Autopilot"),
337 > ],
338 > enumDescriptions: [
339 > localize('agentHost.sessionConfig.mode.interactiveDescription', "Step-by-step collaboration"),
340 > localize('agentHost.sessionConfig.mode.planDescription', "Plan first, execute when ready"),
341 > localize('agentHost.sessionConfig.mode.autopilotDescription', "Autonomously iterates from start to finish"),
342 > ],
343 > default: 'interactive',
344 > sessionMutable: true,
345 > }),
346 > });
347 >
348 > /**
349 > * Rewrites a legacy `autoApprove='autopilot'` config value — used before
350 > * Autopilot moved from the `autoApprove` axis onto the orthogonal `mode`
351 > * axis — into the current two-axis shape:
352 > *
353 > * - `autoApprove='autopilot'` + `mode='plan'` → `mode='plan'`, `autoApprove='default'`
354 > * (legacy `plan` took precedence over autopilot when resolving the SDK mode).
355 > * - `autoApprove='autopilot'` + any other mode → `mode='autopilot'`, `autoApprove='default'`.
356 > *
357 > * Returns a shallow copy with the migration applied, or the original
358 > * reference unchanged when no legacy value is present. Safe to call on
359 > * `undefined`.
360 > *
361 > * Without this, a session persisted (or a "remembered" picker value seeded)
362 > * with `autoApprove='autopilot'` would fail the new schema's enum validation
363 > * and silently fall back to `default`, downgrading the session from
364 > * autonomous Autopilot to manual per-tool confirmation.
365 > */
366 > export function migrateLegacyAutopilotConfig<T extends Record<string, unknown> | undefined>(config: T): T {
367 if (!config || config[SessionConfigKey.AutoApprove] !== 'autopilot') {
368 return config;
375 return migrated as T;
376 }
378 > /**
379 > * Root (agent host) config properties owned by the platform itself.
380 > *
381 > * Root config acts as the baseline that applies to every session:
382 > *
383 > * - {@link SessionConfigKey.Permissions} — host-wide allow/deny lists
384 > * unioned with each session's own permissions when evaluating tool
385 > * auto-approval. See `SessionPermissionManager` for the evaluation
386 > * rules.
387 > */
388 > export const AgentHostTelemetryLevelConfigKey = 'telemetryLevel';
389 >
390 > /** Legacy Copilot Chat debug switch that disables `request.repoInfo` collection. */
391 > export const AgentHostDisableRepoInfoTelemetryConfigKey = 'disableRepoInfoTelemetry';
392 >
393 > /** VS Code setting forwarded into {@link AgentHostDisableRepoInfoTelemetryConfigKey}. */
394 > export const DISABLE_REPO_INFO_TELEMETRY_SETTING_ID = 'chat.advanced.debug.disableRepoInfoTelemetry';
395 >
396 > /**
397 > * Root config key forwarded from the renderer when VS Code's
398 > * `chat.sessionSync.enabled` setting changes. Controls the `remote` flag
399 > * passed to the copilot-sdk `CopilotClientOptions`.
400 > */
401 > export const AgentHostSessionSyncEnabledConfigKey = 'sessionSyncEnabled';
402 >
403 > /**
404 > * Root config key forwarded from the renderer carrying the experiment-aware
405 > * value of `chat.agentHost.codexAgent.enabled`. The host registers the Codex
406 > * provider when this is `true`; disabling requires an agent host restart.
407 > */
408 > export const AgentHostCodexEnabledConfigKey = 'codexAgentEnabled';
409 >
410 > /**
411 > * Root config key forwarded from the renderer when VS Code's
412 > * `chat.tools.terminal.enableAutoApprove` setting changes. Controls whether
413 > * agent-host shell permission checks may apply terminal auto-approve rules.
414 > */
415 > export const AgentHostTerminalAutoApproveEnabledConfigKey = 'terminalAutoApproveEnabled';
416 >
417 > /**
418 > * The VS Code setting ID for terminal auto approve enablement. Defined here so
419 > * renderer-side agent-host clients can forward it without importing from
420 > * workbench terminal contributions.
421 > */
422 > export const TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID = 'chat.tools.terminal.enableAutoApprove';
423 >
424 > /**
425 > * Root config key forwarded from the renderer when VS Code's
426 > * `chat.tools.global.autoApprove` setting changes. When `true`, the global
427 > * auto-approve ("approve everything") setting is enabled and the agent host
428 > * treats every tool call as auto-approved — equivalent to a session running
429 > * with Allow all.
430 > */
431 > export const AgentHostGlobalAutoApproveEnabledConfigKey = 'globalAutoApproveEnabled';
432 >
433 > /**
434 > * The VS Code setting ID for global auto approve. Defined here so renderer-side
435 > * agent-host clients can forward it without importing from `workbench/contrib/chat`.
436 > */
437 > export const GLOBAL_AUTO_APPROVE_SETTING_ID = 'chat.tools.global.autoApprove';
438 >
439 > /**
440 > * Root config key forwarded from the renderer when VS Code's `chat.autoReply`
441 > * setting changes. When `true`, the agent host auto-answers `ask_user`
442 > * questions instead of blocking on the user — the user is treated as
443 > * unavailable and the agent is told to use its best judgment, mirroring the
444 > * behavior of `autopilot` mode.
445 > */
446 > export const AgentHostAutoReplyEnabledConfigKey = 'autoReplyEnabled';
447 >
448 > /**
449 > * The VS Code setting ID for auto-reply. Defined here so renderer-side
450 > * agent-host clients can forward it without importing from `workbench/contrib/chat`.
451 > */
452 > export const AUTO_REPLY_SETTING_ID = 'chat.autoReply';
453 >
454 > // Root config key forwarded from the renderer when Copilot Chat's `github.copilot.chat.preferLongContext.enabled` setting changes.
455 > export const AgentHostPreferLongContextEnabledConfigKey = 'preferLongContextEnabled';
456 >
457 > // The Copilot Chat setting ID for preferring long context, forwarded into the agent host root config.
458 > export const PREFER_LONG_CONTEXT_SETTING_ID = 'github.copilot.chat.preferLongContext.enabled';
459 >
460 > /** Root config key forwarded from the renderer for automatic OS system proxy discovery. */
461 > export const AgentHostSystemProxyEnabledConfigKey = 'systemProxyEnabled';
462 >
463 > /**
464 > * Root config key forwarded from the renderer when VS Code's
465 > * `chat.tools.terminal.autoApprove` setting changes. Holds the effective
466 > * terminal auto-approve rule object for agent-host shell permission checks.
467 > */
468 > export const AgentHostTerminalAutoApproveRulesConfigKey = 'terminalAutoApproveRules';
469 >
470 > export interface IAgentHostTerminalAutoApproveRule {
471 > readonly approve: boolean;
472 > readonly matchCommandLine?: boolean;
473 > }
474 >
475 > export type AgentHostTerminalAutoApproveRuleValue = boolean | null | IAgentHostTerminalAutoApproveRule;
476 > export type AgentHostTerminalAutoApproveRules = Record<string, AgentHostTerminalAutoApproveRuleValue>;
477 >
478 > /**
479 > * The VS Code setting IDs for terminal auto approve rules. Defined here so
480 > * renderer-side agent-host clients can forward them without importing from
481 > * workbench terminal contributions.
482 > */
483 > export const TERMINAL_AUTO_APPROVE_SETTING_ID = 'chat.tools.terminal.autoApprove';
484 > export const TERMINAL_IGNORE_DEFAULT_AUTO_APPROVE_RULES_SETTING_ID = 'chat.tools.terminal.ignoreDefaultAutoApproveRules';
485 >
486 > export function getAgentHostTerminalAutoApproveRulesConfig(configurationService: IConfigurationService): AgentHostTerminalAutoApproveRules {
487 const config = configurationService.getValue<AgentHostTerminalAutoApproveRules | undefined>(TERMINAL_AUTO_APPROVE_SETTING_ID);
488 const configInspectValue = configurationService.inspect<Readonly<AgentHostTerminalAutoApproveRules>>(TERMINAL_AUTO_APPROVE_SETTING_ID);
490 return normalizeAgentHostTerminalAutoApproveRulesConfig(config, configInspectValue, ignoreDefaults);
491 }
493 > export function normalizeAgentHostTerminalAutoApproveRulesConfig(config: AgentHostTerminalAutoApproveRules | undefined, configInspectValue: IConfigurationValue<Readonly<AgentHostTerminalAutoApproveRules>>, ignoreDefaults: boolean): AgentHostTerminalAutoApproveRules {
494 if (!config) {
495 return {};
505 return rules;
506 }
508 function isDefaultOnlyAutoApproveRule(key: string, value: AgentHostTerminalAutoApproveRuleValue, configInspectValue: IConfigurationValue<Readonly<AgentHostTerminalAutoApproveRules>>): boolean {
509 const defaultValue = configInspectValue.default?.value;
517 return sourceTarget === ConfigurationTarget.DEFAULT;
518 }
520 function getAutoApproveRuleSourceTarget(key: string, value: AgentHostTerminalAutoApproveRuleValue, configInspectValue: IConfigurationValue<Readonly<AgentHostTerminalAutoApproveRules>>): ConfigurationTarget {
521 if (hasMatchingRule(configInspectValue.workspaceFolderValue, key, value)) {
539 return ConfigurationTarget.DEFAULT;
540 }
542 function hasMatchingRule(config: Readonly<AgentHostTerminalAutoApproveRules> | undefined, key: string, value: AgentHostTerminalAutoApproveRuleValue): boolean {
543 return !!config && Object.prototype.hasOwnProperty.call(config, key) && structuralEquals(config[key], value);
544 }
546 > /**
547 > * Root config key holding agent-host-level MCP server definitions.
548 > *
549 > * The value is a map of server name → {@link IMcpServerConfiguration}
550 > * (the same `servers` shape used by `mcp.json`). These servers are
551 > * exposed to every session created by the host, merged with any
552 > * plugin-provided MCP servers when launching the copilot-sdk client.
553 > */
554 > export const AgentHostMcpServersConfigKey = 'mcpServers';
555 >
556 > /**
557 > * Map of server name → MCP server configuration, as stored in the
558 > * {@link AgentHostMcpServersConfigKey} root config value.
559 > */
560 > export type AgentHostMcpServers = Record<string, IMcpServerConfiguration>;
561 >
562 > /**
563 > * The VS Code setting ID for session sync. Defined here so the platform
564 > * layer (renderer-side forwarding) can reference it without importing from
565 > * `workbench/contrib/chat`.
566 > */
567 > export const SESSION_SYNC_ENABLED_SETTING_ID = 'chat.sessionSync.enabled';
568 >
569 > export function telemetryLevelToAgentHostConfigValue(telemetryLevel: TelemetryLevel): TelemetryConfiguration {
570 > switch (telemetryLevel) { agentHostSchema.ts
571 > case TelemetryLevel.NONE:
572 return TelemetryConfiguration.OFF;
573 > case TelemetryLevel.CRASH: agentHostSchema.ts
574 return TelemetryConfiguration.CRASH;
575 > case TelemetryLevel.ERROR: agentHostSchema.ts
576 return TelemetryConfiguration.ERROR;
577 > case TelemetryLevel.USAGE: agentHostSchema.ts
578 > return TelemetryConfiguration.ON; agentHostSchema.ts
580 > }
582 > export function agentHostConfigValueToTelemetryLevel(value: unknown): TelemetryLevel | undefined {
583 switch (value) {
584 case TelemetryConfiguration.OFF:
594 }
595 }
597 > /**
598 > * Field descriptors for a single MCP server entry, shared by the stdio and
599 > * http shapes. The agent-host config schema has no `oneOf`, so both variants'
600 > * fields are described together; `type` selects which fields apply
601 > * (`stdio` uses `command`/`args`/`env`/`cwd`, `http` uses `url`/`headers`).
602 > */
603 > const mcpServerConfigProperties: Record<string, SessionConfigPropertySchema> = {
604 > type: {
605 > type: 'string',
606 > title: localize('agentHost.config.mcpServers.type.title', "Server Type"),
607 > description: localize('agentHost.config.mcpServers.type.description', "The transport used to reach the server: `stdio` for a local command, `http` for a remote endpoint."),
608 > enum: ['stdio', 'http'],
609 > },
610 > command: {
611 > type: 'string',
612 > title: localize('agentHost.config.mcpServers.command.title', "Command"),
613 > description: localize('agentHost.config.mcpServers.command.description', "For `stdio` servers, the executable to spawn."),
614 > },
615 > args: {
616 > type: 'array',
617 > title: localize('agentHost.config.mcpServers.args.title', "Arguments"),
618 > description: localize('agentHost.config.mcpServers.args.description', "For `stdio` servers, the arguments passed to the command."),
619 > items: { type: 'string', title: localize('agentHost.config.mcpServers.arg.title', "Argument") },
620 > },
621 > env: {
622 > type: 'object',
623 > title: localize('agentHost.config.mcpServers.env.title', "Environment"),
624 > description: localize('agentHost.config.mcpServers.env.description', "For `stdio` servers, environment variables set on the spawned process."),
625 > },
626 > cwd: {
627 > type: 'string',
628 > title: localize('agentHost.config.mcpServers.cwd.title', "Working Directory"),
629 > description: localize('agentHost.config.mcpServers.cwd.description', "For `stdio` servers, the working directory the command runs in."),
630 > },
631 > url: {
632 > type: 'string',
633 > title: localize('agentHost.config.mcpServers.url.title', "URL"),
634 > description: localize('agentHost.config.mcpServers.url.description', "For `http` servers, the endpoint URL of the MCP server."),
635 > },
636 > headers: {
637 > type: 'object',
638 > title: localize('agentHost.config.mcpServers.headers.title', "Headers"),
639 > description: localize('agentHost.config.mcpServers.headers.description', "For `http` servers, HTTP headers sent with every request."),
640 > },
641 > };
642 >
643 > /**
644 > * Documents the value shape of the {@link AgentHostMcpServersConfigKey} map.
645 > *
646 > * The config value is a map of server name → server config. The schema
647 > * language has no `additionalProperties`, so the per-entry shape is attached
648 > * under a placeholder key (`<serverName>`) rather than at the map level —
649 > * this keeps the field descriptions discoverable without the runtime
650 > * validator mistaking a real server named e.g. `command` for the `command`
651 > * field. Real entries (keyed by actual server names) are passed through.
652 > */
653 > const mcpServersValueProperties: Record<string, SessionConfigPropertySchema> = {
654 > '<serverName>': {
655 > type: 'object',
656 > title: localize('agentHost.config.mcpServers.entry.title', "MCP Server"),
657 > description: localize('agentHost.config.mcpServers.entry.description', "A single MCP server entry. The property key is the server name."),
658 > properties: mcpServerConfigProperties,
659 > },
660 > };
661 >
662 > export const platformRootSchema = createSchema({
663 > [SessionConfigKey.Permissions]: permissionsProperty,
664 > [AgentHostDisableRepoInfoTelemetryConfigKey]: schemaProperty<boolean>({
665 > type: 'boolean',
666 > title: localize('agentHost.config.disableRepoInfoTelemetry.title', "Disable Repository Information Telemetry"),
667 > description: localize('agentHost.config.disableRepoInfoTelemetry.description', "Whether repository information telemetry is disabled for Agent Host sessions."),
668 > default: false,
669 > }),
670 > [AgentHostTelemetryLevelConfigKey]: schemaProperty<TelemetryConfiguration>({
671 > type: 'string',
672 > title: localize('agentHost.config.telemetryLevel.title', "Telemetry Level"),
673 > description: localize('agentHost.config.telemetryLevel.description', "Most restrictive telemetry level requested by connected clients."),
674 > enum: [TelemetryConfiguration.ON, TelemetryConfiguration.ERROR, TelemetryConfiguration.CRASH, TelemetryConfiguration.OFF],
675 > default: TelemetryConfiguration.ON,
676 > }),
677 > [AgentHostSessionSyncEnabledConfigKey]: schemaProperty<boolean>({
678 > type: 'boolean',
679 > title: localize('agentHost.config.sessionSyncEnabled.title', "Session Sync"),
680 > description: localize('agentHost.config.sessionSyncEnabled.description', "Whether remote session sync is enabled for the copilot-sdk CLI."),
681 > default: false,
682 > }),
683 > [AgentHostCodexEnabledConfigKey]: schemaProperty<boolean>({
684 > type: 'boolean',
685 > title: localize('agentHost.config.codexAgentEnabled.title', "Codex Agent"),
686 > description: localize('agentHost.config.codexAgentEnabled.description', "Whether the Codex provider is enabled."),
687 > default: false,
688 > }),
689 > [AgentHostTerminalAutoApproveEnabledConfigKey]: schemaProperty<boolean>({
690 > type: 'boolean',
691 > title: localize('agentHost.config.terminalAutoApproveEnabled.title', "Terminal Auto Approve"),
692 > description: localize('agentHost.config.terminalAutoApproveEnabled.description', "Whether terminal auto-approve rules forwarded by the connected client are allowed to apply to agent-host shell permission requests."),
693 > default: true,
694 > }),
695 > [AgentHostGlobalAutoApproveEnabledConfigKey]: schemaProperty<boolean>({
696 > type: 'boolean',
697 > title: localize('agentHost.config.globalAutoApproveEnabled.title', "Global Auto Approve"),
698 > description: localize('agentHost.config.globalAutoApproveEnabled.description', "Whether VS Code's global auto-approve setting is enabled. When `true`, every tool call is auto-approved, equivalent to a session using Allow all."),
699 > default: false,
700 > }),
701 > [AgentHostAutoReplyEnabledConfigKey]: schemaProperty<boolean>({
702 > type: 'boolean',
703 > title: localize('agentHost.config.autoReplyEnabled.title', "Auto Reply"),
704 > description: localize('agentHost.config.autoReplyEnabled.description', "Whether VS Code's auto-reply setting is enabled. When `true`, `ask_user` questions are auto-answered instead of blocking on the user, mirroring autopilot mode."),
705 > default: false,
706 > }),
707 > [AgentHostPreferLongContextEnabledConfigKey]: schemaProperty<boolean>({
708 > type: 'boolean',
709 > title: localize('agentHost.config.preferLongContextEnabled.title', "Prefer Long Context"),
710 > description: localize('agentHost.config.preferLongContextEnabled.description', "Whether Copilot Chat's prefer-long-context setting is enabled. When `true`, models with a free long context window only show the long context option in the picker. When `false` (default), the smaller default context option stays selectable."),
711 > default: false,
712 > }),
713 > [AgentHostSystemProxyEnabledConfigKey]: schemaProperty<boolean>({
714 > type: 'boolean',
715 > title: localize('agentHost.config.systemProxyEnabled.title', "System Proxy Discovery"),
716 > description: localize('agentHost.config.systemProxyEnabled.description', "Whether Copilot sessions automatically discover and use the operating system's proxy configuration."),
717 > default: true,
718 > }),
719 > [AgentHostTerminalAutoApproveRulesConfigKey]: schemaProperty<AgentHostTerminalAutoApproveRules>({
720 > type: 'object',
721 > title: localize('agentHost.config.terminalAutoApproveRules.title', "Terminal Auto Approve Rules"),
722 > description: localize('agentHost.config.terminalAutoApproveRules.description', "Terminal auto-approve rules forwarded by the connected client for agent-host shell permission checks."),
723 > default: {},
724 > }),
725 > [AgentHostMcpServersConfigKey]: schemaProperty<AgentHostMcpServers>({
726 > type: 'object',
727 > title: localize('agentHost.config.mcpServers.title', "MCP Servers"),
728 > description: localize('agentHost.config.mcpServers.description', "Agent-host-level MCP servers exposed to every session, keyed by server name. Each value is a server configuration (see `<serverName>`)."),
729 > properties: mcpServersValueProperties,
730 > default: {},
731 > }),
732 > });
src/vs/base/common/lifecycle.ts 554 covered LOC · 127 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- lifecycle.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { compareBy, numberComparator } from './arrays.js';
7 > import { groupBy } from './collections.js';
8 > import { SetMap, ResourceMap } from './map.js';
9 > import { URI } from './uri.js';
10 > import { createSingleCallFunction } from './functional.js';
11 > import { Iterable } from './iterator.js';
12 > import { BugIndicatingError, onUnexpectedError } from './errors.js';
13 >
14 > // #region Disposable Tracking
15 >
16 > /**
17 > * Enables logging of potentially leaked disposables.
18 > *
19 > * A disposable is considered leaked if it is not disposed or not registered as the child of
20 > * another disposable. This tracking is very simple an only works for classes that either
21 > * extend Disposable or use a DisposableStore. This means there are a lot of false positives.
22 > */
23 > const TRACK_DISPOSABLES = false;
24 > let disposableTracker: IDisposableTracker | null = null;
25 >
26 > export interface IDisposableTracker {
27 > /**
28 > * Is called on construction of a disposable.
29 > */
30 > trackDisposable(disposable: IDisposable): void;
31 >
32 > /**
33 > * Is called when a disposable is registered as child of another disposable (e.g. {@link DisposableStore}).
34 > * If parent is `null`, the disposable is removed from its former parent.
35 > */
36 > setParent(child: IDisposable, parent: IDisposable | null): void;
37 >
38 > /**
39 > * Is called after a disposable is disposed.
40 > */
41 > markAsDisposed(disposable: IDisposable): void;
42 >
43 > /**
44 > * Indicates that the given object is a singleton which does not need to be disposed.
45 > */
46 > markAsSingleton(disposable: IDisposable): void;
47 > }
48 >
49 > export class GCBasedDisposableTracker implements IDisposableTracker {
50
51 private readonly _registry = new FinalizationRegistry<string>(heldValue => {
52 console.warn(`[LEAKED DISPOSABLE] ${heldValue}`);
53 });
55 > trackDisposable(disposable: IDisposable): void {
56 const stack = new Error('CREATED via:').stack!;
57 this._registry.register(disposable, stack, disposable);
58 }
60 > setParent(child: IDisposable, parent: IDisposable | null): void {
61 if (parent) {
62 this._registry.unregister(child);
65 }
66 }
68 > markAsDisposed(disposable: IDisposable): void {
69 this._registry.unregister(disposable);
70 }
72 > markAsSingleton(disposable: IDisposable): void {
73 this._registry.unregister(disposable);
74 }
75 > } lifecycle.ts
76 >
77 > export interface DisposableInfo {
78 > value: IDisposable;
79 > source: string | null;
80 > parent: IDisposable | null;
81 > isSingleton: boolean;
82 > idx: number;
83 > }
84 >
85 > export class DisposableTracker implements IDisposableTracker {
86 > private static idx = 0; lifecycle.ts
87 >
88 > private readonly livingDisposables = new Map<IDisposable, DisposableInfo>();
90 > private getDisposableData(d: IDisposable): DisposableInfo {
91 > let val = this.livingDisposables.get(d); lifecycle.ts
92 > if (!val) {
93 > val = { parent: null, source: null, isSingleton: false, value: d, idx: DisposableTracker.idx++ };
94 > this.livingDisposables.set(d, val);
95 > }
96 > return val;
97 > }
99 > trackDisposable(d: IDisposable): void {
100 > const data = this.getDisposableData(d); lifecycle.ts
101 > if (!data.source) {
102 > data.source =
103 > new Error().stack!;
104 > }
105 > }
106 > lifecycle.ts
107 > setParent(child: IDisposable, parent: IDisposable | null): void {
108 > const data = this.getDisposableData(child); lifecycle.ts
109 > data.parent = parent;
110 > }
111 > lifecycle.ts
112 > markAsDisposed(x: IDisposable): void {
113 > this.livingDisposables.delete(x); lifecycle.ts
114 > }
115 > lifecycle.ts
116 > markAsSingleton(disposable: IDisposable): void {
117 this.getDisposableData(disposable).isSingleton = true;
118 }
119 > lifecycle.ts
120 > private getRootParent(data: DisposableInfo, cache: Map<DisposableInfo, DisposableInfo>): DisposableInfo {
121 const cacheValue = cache.get(data);
122 if (cacheValue) {
128 return result;
129 }
130 > lifecycle.ts
131 > getTrackedDisposables(): IDisposable[] {
132 const rootParentCache = new Map<DisposableInfo, DisposableInfo>();
133
138 return leaking;
139 }
140 > lifecycle.ts
141 > computeLeakingDisposables(maxReported = 10, preComputedLeaks?: DisposableInfo[]): { leaks: DisposableInfo[]; details: string } | undefined {
142 > let uncoveredLeakingObjs: DisposableInfo[] | undefined; lifecycle.ts
143 > if (preComputedLeaks) {
144 uncoveredLeakingObjs = preComputedLeaks;
145 > } else { lifecycle.ts
146 > const rootParentCache = new Map<DisposableInfo, DisposableInfo>();
147 >
148 > const leakingObjects = [...this.livingDisposables.values()]
149 > .filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton);
150 >
151 > if (leakingObjects.length === 0) {
152 > return; lifecycle.ts
153 > }
154 const leakingObjsSet = new Set(leakingObjects.map(o => o.value));
155
162 throw new Error('There are cyclic diposable chains!');
163 }
164 > } lifecycle.ts
165
166 if (!uncoveredLeakingObjs) {
224
225 return { leaks: uncoveredLeakingObjs, details: message };
226 > } lifecycle.ts
227 > } lifecycle.ts
228 >
229 > export function setDisposableTracker(tracker: IDisposableTracker | null): void {
230 > disposableTracker = tracker; lifecycle.ts
231 > }
232 > lifecycle.ts
233 > if (TRACK_DISPOSABLES) {
234 const __is_disposable_tracked__ = '__is_disposable_tracked__';
235 setDisposableTracker(new class implements IDisposableTracker {
268 });
269 }
270 > lifecycle.ts
271 > export function trackDisposable<T extends IDisposable>(x: T): T {
272 > disposableTracker?.trackDisposable(x); lifecycle.ts
273 > return x;
274 > }
275 > lifecycle.ts
276 > export function markAsDisposed(disposable: IDisposable): void {
277 > disposableTracker?.markAsDisposed(disposable); lifecycle.ts
278 > }
279 > lifecycle.ts
280 > function setParentOfDisposable(child: IDisposable, parent: IDisposable | null): void { lifecycle.ts
281 > disposableTracker?.setParent(child, parent);
282 > }
283 > lifecycle.ts
284 function setParentOfDisposables(children: IDisposable[], parent: IDisposable | null): void {
285 if (!disposableTracker) {
290 }
291 }
292 > lifecycle.ts
293 > /**
294 > * Indicates that the given object is a singleton which does not need to be disposed.
295 > */
296 > export function markAsSingleton<T extends IDisposable>(singleton: T): T {
297 disposableTracker?.markAsSingleton(singleton);
298 return singleton;
299 }
300 > lifecycle.ts
301 > // #endregion
302 >
303 > /**
304 > * An object that performs a cleanup operation when `.dispose()` is called.
305 > *
306 > * Some examples of how disposables are used:
307 > *
308 > * - An event listener that removes itself when `.dispose()` is called.
309 > * - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called.
310 > * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered.
311 > */
312 > export interface IDisposable {
313 > dispose(): void;
314 > }
315 >
316 > /**
317 > * Check if `thing` is {@link IDisposable disposable}.
318 > */
319 > export function isDisposable<E>(thing: E): thing is E & IDisposable {
320 // eslint-disable-next-line local/code-no-any-casts
321 return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;
322 }
323 > lifecycle.ts
324 > /**
325 > * Disposes of the value(s) passed in.
326 > */
327 > export function dispose<T extends IDisposable>(disposable: T): T;
328 > export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
329 > export function dispose<T extends IDisposable, A extends Iterable<T> = Iterable<T>>(disposables: A): A;
330 > export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
331 > export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
332 > export function dispose<T extends IDisposable>(arg: T | Iterable<T> | undefined): any {
333 > if (Iterable.is(arg)) { lifecycle.ts
334 > const errors: any[] = []; lifecycle.ts
335 >
336 > for (const d of arg) {
337 > if (d) { lifecycle.ts
338 > try {
339 > d.dispose();
340 > } catch (e) {
341 errors.push(e);
342 }
343 > } lifecycle.ts
344 > }
345 > lifecycle.ts
346 > if (errors.length === 1) {
347 throw errors[0];
348 > } else if (errors.length > 1) { lifecycle.ts
349 throw new AggregateError(errors, 'Encountered errors while disposing of store');
350 }
351 > lifecycle.ts
352 > return Array.isArray(arg) ? [] : arg; lifecycle.ts
353 > } else if (arg) { lifecycle.ts
354 arg.dispose();
355 return arg;
356 }
357 > } lifecycle.ts
358 > lifecycle.ts
359 > export function disposeIfDisposable<T extends IDisposable | object>(disposables: Array<T>): Array<T> {
360 for (const d of disposables) {
361 if (isDisposable(d)) {
365 return [];
366 }
367 > lifecycle.ts
368 > /**
369 > * Combine multiple disposable values into a single {@link IDisposable}.
370 > */
371 > export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
372 const parent = toDisposable(() => dispose(disposables));
373 setParentOfDisposables(disposables, parent);
374 return parent;
375 }
376 > lifecycle.ts
377 > class FunctionDisposable implements IDisposable {
378 > private _isDisposed: boolean;
379 > private readonly _fn: () => void;
380 >
381 > constructor(fn: () => void) {
382 > this._isDisposed = false; lifecycle.ts
383 > this._fn = fn;
384 > trackDisposable(this);
385 > }
386 > lifecycle.ts
387 > dispose() {
388 > if (this._isDisposed) { lifecycle.ts
389 return;
390 }
391 > if (!this._fn) { lifecycle.ts
392 throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);
393 }
394 > this._isDisposed = true; lifecycle.ts
395 > markAsDisposed(this);
396 > this._fn();
397 > }
398 > } lifecycle.ts
399 >
400 > /**
401 > * Turn a function that implements dispose into an {@link IDisposable}.
402 > *
403 > * @param fn Clean up function, guaranteed to be called only **once**.
404 > */
405 > export function toDisposable(fn: () => void): IDisposable {
406 > return new FunctionDisposable(fn); lifecycle.ts
407 > }
408 > lifecycle.ts
409 > /**
410 > * Manages a collection of disposable values.
411 > *
412 > * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an
413 > * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a
414 > * store that has already been disposed of.
415 > */
416 > export class DisposableStore implements IDisposable {
417 >
418 > static DISABLE_DISPOSED_WARNING = false;
419 >
420 > private readonly _toDispose = new Set<IDisposable>();
421 > private _isDisposed = false;
422 >
423 > constructor() {
424 > trackDisposable(this); lifecycle.ts
425 > }
426 > lifecycle.ts
427 > /**
428 > * Dispose of all registered disposables and mark this object as disposed.
429 > *
430 > * Any future disposables added to this object will be disposed of on `add`.
431 > */
432 > public dispose(): void {
433 > if (this._isDisposed) { lifecycle.ts
434 return;
435 }
436 > lifecycle.ts
437 > markAsDisposed(this);
438 > this._isDisposed = true;
439 > this.clear();
440 > }
441 > lifecycle.ts
442 > /**
443 > * @return `true` if this object has been disposed of.
444 > */
445 > public get isDisposed(): boolean {
446 return this._isDisposed;
447 }
448 > lifecycle.ts
449 > /**
450 > * Dispose of all registered disposables but do not mark this object as disposed.
451 > */
452 > public clear(): void {
453 > if (this._toDispose.size === 0) { lifecycle.ts
454 > return; lifecycle.ts
455 > }
456 > lifecycle.ts
457 > try {
458 > dispose(this._toDispose);
459 > } finally {
460 > this._toDispose.clear();
461 > }
462 > } lifecycle.ts
463 > lifecycle.ts
464 > /**
465 > * Add a new {@link IDisposable disposable} to the collection.
466 > */
467 > public add<T extends IDisposable>(o: T): T {
468 > if (!o || o === Disposable.None) { lifecycle.ts
469 return o;
470 }
471 > if ((o as unknown as DisposableStore) === this) { lifecycle.ts
472 throw new Error('Cannot register a disposable on itself!');
473 }
474 > lifecycle.ts
475 > setParentOfDisposable(o, this);
476 > if (this._isDisposed) {
477 if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
478 console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack);
479 }
480 > } else { lifecycle.ts
481 > this._toDispose.add(o);
482 > }
483 >
484 > return o;
485 > } lifecycle.ts
486 > lifecycle.ts
487 > /**
488 > * Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the
489 > * disposable even when the disposable is not part in the store.
490 > */
491 > public delete<T extends IDisposable>(o: T): void {
492 if (!o) {
493 return;
499 o.dispose();
500 }
501 > lifecycle.ts
502 > /**
503 > * Deletes the value from the store, but does not dispose it.
504 > */
505 > public deleteAndLeak<T extends IDisposable>(o: T): void {
506 if (!o) {
507 return;
511 }
512 }
513 > lifecycle.ts
514 > public assertNotDisposed(): void {
515 if (this._isDisposed) {
516 onUnexpectedError(new BugIndicatingError('Object disposed'));
517 }
518 }
519 > } lifecycle.ts
520 >
521 > /**
522 > * Abstract base class for a {@link IDisposable disposable} object.
523 > *
524 > * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of.
525 > */
526 > export abstract class Disposable implements IDisposable {
527 >
528 > /**
529 > * A disposable that does nothing when it is disposed of.
530 > *
531 > * TODO: This should not be a static property.
532 > */
533 > static readonly None = Object.freeze<IDisposable>({ dispose() { } });
534 >
535 > protected readonly _store = new DisposableStore();
536 >
537 > constructor() {
538 > trackDisposable(this); lifecycle.ts
539 > setParentOfDisposable(this._store, this);
540 > }
541 > lifecycle.ts
542 > public dispose(): void {
543 > markAsDisposed(this); lifecycle.ts
544 >
545 > this._store.dispose();
546 > }
547 > lifecycle.ts
548 > /**
549 > * Adds `o` to the collection of disposables managed by this object.
550 > */
551 > protected _register<T extends IDisposable>(o: T): T {
552 > if ((o as unknown as Disposable) === this) { lifecycle.ts
553 throw new Error('Cannot register a disposable on itself!');
554 }
555 > return this._store.add(o); lifecycle.ts
556 > }
557 > } lifecycle.ts
558 >
559 > /**
560 > * Manages the lifecycle of a disposable value that may be changed.
561 > *
562 > * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
563 > * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
564 > */
565 > export class MutableDisposable<T extends IDisposable> implements IDisposable {
566 > private _value?: T;
567 > private _isDisposed = false;
568 >
569 > constructor() {
570 trackDisposable(this);
571 }
572 > lifecycle.ts
573 > /**
574 > * Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed
575 > */
576 > get value(): T | undefined {
577 return this._isDisposed ? undefined : this._value;
578 }
579 > lifecycle.ts
580 > /**
581 > * Set a new disposable value.
582 > *
583 > * Behaviour:
584 > * - If the MutableDisposable has been disposed, the setter is a no-op.
585 > * - If the new value is strictly equal to the current value, the setter is a no-op.
586 > * - Otherwise the previous value (if any) is disposed and the new value is stored.
587 > *
588 > * Related helpers:
589 > * - clear() resets the value to `undefined` (and disposes the previous value).
590 > * - clearAndLeak() returns the old value without disposing it and removes its parent.
591 > */
592 > set value(value: T | undefined) {
593 if (this._isDisposed || value === this._value) {
594 return;
601 this._value = value;
602 }
603 > lifecycle.ts
604 > /**
605 > * Resets the stored value and disposed of the previously stored value.
606 > */
607 > clear(): void {
608 this.value = undefined;
609 }
610 > lifecycle.ts
611 > dispose(): void {
612 this._isDisposed = true;
613 markAsDisposed(this);
615 this._value = undefined;
616 }
617 > lifecycle.ts
618 > /**
619 > * Clears the value, but does not dispose it.
620 > * The old value is returned.
621 > */
622 > clearAndLeak(): T | undefined {
623 const oldValue = this._value;
624 this._value = undefined;
628 return oldValue;
629 }
630 > } lifecycle.ts
631 >
632 > /**
633 > * Manages the lifecycle of a disposable value that may be changed like {@link MutableDisposable}, but the value must
634 > * exist and cannot be undefined.
635 > */
636 > export class MandatoryMutableDisposable<T extends IDisposable> implements IDisposable {
637 > private readonly _disposable = new MutableDisposable<T>();
638 > private _isDisposed = false;
639 >
640 > constructor(initialValue: T) {
641 this._disposable.value = initialValue;
642 }
643 > lifecycle.ts
644 > get value(): T {
645 return this._disposable.value!;
646 }
647 > lifecycle.ts
648 > set value(value: T) {
649 if (this._isDisposed || value === this._disposable.value) {
650 return;
652 this._disposable.value = value;
653 }
654 > lifecycle.ts
655 > dispose() {
656 this._isDisposed = true;
657 this._disposable.dispose();
658 }
659 > } lifecycle.ts
660 >
661 > export class RefCountedDisposable {
662 >
663 > private _counter: number = 1;
664 >
665 > constructor(
666 private readonly _disposable: IDisposable,
667 ) { }
668 > lifecycle.ts
669 > acquire() {
670 this._counter++;
671 return this;
672 }
673 > lifecycle.ts
674 > release() {
675 if (--this._counter === 0) {
676 this._disposable.dispose();
678 return this;
679 }
680 > } lifecycle.ts
681 >
682 > export interface IReference<T> extends IDisposable {
683 > readonly object: T;
684 > }
685 >
686 > export abstract class ReferenceCollection<T> {
687
688 private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();
689 > lifecycle.ts
690 > acquire(key: string, ...args: unknown[]): IReference<T> {
691 let reference = this.references.get(key);
692
708 return { object, dispose };
709 }
710 > lifecycle.ts
711 > protected abstract createReferencedObject(key: string, ...args: unknown[]): T;
712 > protected abstract destroyReferencedObject(key: string, object: T): void;
713 > }
714 >
715 > /**
716 > * Unwraps a reference collection of promised values. Makes sure
717 > * references are disposed whenever promises get rejected.
718 > */
719 > export class AsyncReferenceCollection<T> {
720 >
721 > constructor(private referenceCollection: ReferenceCollection<Promise<T>>) { }
722 >
723 > async acquire(key: string, ...args: unknown[]): Promise<IReference<T>> {
724 const ref = this.referenceCollection.acquire(key, ...args);
725
736 }
737 }
738 > } lifecycle.ts
739 >
740 > export class ImmortalReference<T> implements IReference<T> {
741 > constructor(public object: T) { }
742 > dispose(): void { /* noop */ }
743 > }
744 >
745 > export function disposeOnReturn(fn: (store: DisposableStore) => void): void {
746 const store = new DisposableStore();
747 try {
751 }
752 }
753 > lifecycle.ts
754 > /**
755 > * A map the manages the lifecycle of the values that it stores.
756 > */
757 > export class DisposableMap<K, V extends IDisposable = IDisposable> implements IDisposable {
758 >
759 > private readonly _store: Map<K, V>;
760 > private _isDisposed = false;
761 >
762 > constructor(store: Map<K, V> = new Map<K, V>()) {
763 this._store = store;
764 trackDisposable(this);
765 }
766 > lifecycle.ts
767 > /**
768 > * Disposes of all stored values and mark this object as disposed.
769 > *
770 > * Trying to use this object after it has been disposed of is an error.
771 > */
772 > dispose(): void {
773 markAsDisposed(this);
774 this._isDisposed = true;
775 this.clearAndDisposeAll();
776 }
777 > lifecycle.ts
778 > /**
779 > * Disposes of all stored values and clear the map, but DO NOT mark this object as disposed.
780 > */
781 > clearAndDisposeAll(): void {
782 if (!this._store.size) {
783 return;
790 }
791 }
792 > lifecycle.ts
793 > has(key: K): boolean {
794 return this._store.has(key);
795 }
796 > lifecycle.ts
797 > get size(): number {
798 return this._store.size;
799 }
800 > lifecycle.ts
801 > get(key: K): V | undefined {
802 return this._store.get(key);
803 }
804 > lifecycle.ts
805 > set(key: K, value: V, skipDisposeOnOverwrite = false): void {
806 if (this._isDisposed) {
807 console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack);
815 setParentOfDisposable(value, this);
816 }
817 > lifecycle.ts
818 > /**
819 > * Delete the value stored for `key` from this map and also dispose of it.
820 > */
821 > deleteAndDispose(key: K): void {
822 this._store.get(key)?.dispose();
823 this._store.delete(key);
824 }
825 > lifecycle.ts
826 > /**
827 > * Delete the value stored for `key` from this map but return it. The caller is
828 > * responsible for disposing of the value.
829 > */
830 > deleteAndLeak(key: K): V | undefined {
831 const value = this._store.get(key);
832 if (value) {
836 return value;
837 }
838 > lifecycle.ts
839 > keys(): IterableIterator<K> {
840 return this._store.keys();
841 }
842 > lifecycle.ts
843 > values(): IterableIterator<V> {
844 return this._store.values();
845 }
846 > lifecycle.ts
847 > [Symbol.iterator](): IterableIterator<[K, V]> {
848 return this._store[Symbol.iterator]();
849 }
850 > } lifecycle.ts
851 >
852 > /**
853 > * A set that manages the lifecycle of the values that it stores.
854 > */
855 > export class DisposableSet<V extends IDisposable = IDisposable> implements IDisposable {
856 >
857 > private readonly _store: Set<V>;
858 > private _isDisposed = false;
859 >
860 > constructor(store: Set<V> = new Set<V>()) {
861 this._store = store;
862 trackDisposable(this);
863 }
864 > lifecycle.ts
865 > /**
866 > * Disposes of all stored values and mark this object as disposed.
867 > *
868 > * Trying to use this object after it has been disposed of is an error.
869 > */
870 > dispose(): void {
871 markAsDisposed(this);
872 this._isDisposed = true;
873 this.clearAndDisposeAll();
874 }
875 > lifecycle.ts
876 > /**
877 > * Disposes of all stored values and clear the set, but DO NOT mark this object as disposed.
878 > */
879 > clearAndDisposeAll(): void {
880 if (!this._store.size) {
881 return;
888 }
889 }
890 > lifecycle.ts
891 > has(value: V): boolean {
892 return this._store.has(value);
893 }
894 > lifecycle.ts
895 > get size(): number {
896 return this._store.size;
897 }
898 > lifecycle.ts
899 > add(value: V): void {
900 if (this._isDisposed) {
901 console.warn(new Error('Trying to add a disposable to a DisposableSet that has already been disposed of. The added object will be leaked!').stack);
905 setParentOfDisposable(value, this);
906 }
907 > lifecycle.ts
908 > /**
909 > * Delete the value from this set and also dispose of it.
910 > */
911 > deleteAndDispose(value: V): void {
912 if (this._store.delete(value)) {
913 value.dispose();
914 }
915 }
916 > lifecycle.ts
917 > /**
918 > * Delete the value from this set but return it. The caller is
919 > * responsible for disposing of the value.
920 > */
921 > deleteAndLeak(value: V): V | undefined {
922 if (this._store.delete(value)) {
923 setParentOfDisposable(value, null);
926 return undefined;
927 }
928 > lifecycle.ts
929 > values(): IterableIterator<V> {
930 return this._store.values();
931 }
932 > lifecycle.ts
933 > [Symbol.iterator](): IterableIterator<V> {
934 return this._store[Symbol.iterator]();
935 }
936 > } lifecycle.ts
937 >
938 > /**
939 > * Call `then` on a Promise, unless the returned disposable is disposed.
940 > */
941 > export function thenIfNotDisposed<T>(promise: Promise<T>, then: (result: T) => void): IDisposable {
942 let disposed = false;
943 promise.then(result => {
951 });
952 }
953 > lifecycle.ts
954 > /**
955 > * Call `then` on a promise that resolves to a {@link IDisposable}, then either register the
956 > * disposable or register it to the {@link DisposableStore}, depending on whether the store is
957 > * disposed or not.
958 > */
959 > export function thenRegisterOrDispose<T extends IDisposable>(promise: Promise<T>, store: DisposableStore): Promise<T> {
960 return promise.then(disposable => {
961 if (store.isDisposed) {
967 });
968 }
969 > lifecycle.ts
970 > export class DisposableResourceMap<V extends IDisposable = IDisposable> extends DisposableMap<URI, V> {
971 > constructor() {
972 super(new ResourceMap());
973 }
974 > } lifecycle.ts
src/vs/base/common/strings.ts 454 covered LOC · 101 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- strings.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { LRUCachedFunction } from './cache.js';
7 > import { CharCode } from './charCode.js';
8 > import { Lazy } from './lazy.js';
9 > import { Constants } from './uint.js';
10 >
11 > export function isFalsyOrWhitespace(str: string | undefined): boolean {
12 if (!str || typeof str !== 'string') {
13 return true;
15 return str.trim().length === 0;
16 }
17 > strings.ts
18 > const _formatRegexp = /{(\d+)}/g;
19 >
20 > /**
21 > * Helper to produce a string with a variable number of arguments. Insert variable segments
22 > * into the string using the {n} notation where N is the index of the argument following the string.
23 > * @param value string to which formatting is applied
24 > * @param args replacements for {n}-entries
25 > */
26 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
27 > export function format(value: string, ...args: any[]): string {
28 if (args.length === 0) {
29 return value;
36 });
37 }
38 > strings.ts
39 > const _format2Regexp = /{([^}]+)}/g;
40 >
41 > /**
42 > * Helper to create a string from a template and a string record.
43 > * Similar to `format` but with objects instead of positional arguments.
44 > */
45 > export function format2(template: string, values: Record<string, unknown>): string {
46 if (Object.keys(values).length === 0) {
47 return template;
49 return template.replace(_format2Regexp, (match, group) => (values[group] ?? match) as string);
50 }
51 > strings.ts
52 > /**
53 > * Encodes the given value so that it can be used as literal value in html attributes.
54 > *
55 > * In other words, computes `$val`, such that `attr` in `<div attr="$val" />` has the runtime value `value`.
56 > * This prevents XSS injection.
57 > */
58 > export function htmlAttributeEncodeValue(value: string): string {
59 return value.replace(/[<>"'&]/g, ch => {
60 switch (ch) {
68 });
69 }
70 > strings.ts
71 > /**
72 > * Converts HTML characters inside the string to use entities instead. Makes the string safe from
73 > * being used e.g. in HTMLElement.innerHTML.
74 > */
75 > export function escape(html: string): string {
76 return html.replace(/[<>&]/g, function (match) {
77 switch (match) {
83 });
84 }
85 > strings.ts
86 > /**
87 > * Escapes regular expression characters in a given string
88 > */
89 > export function escapeRegExpCharacters(value: string): string {
90 return value.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, '\\$&');
91 }
92 > strings.ts
93 > /**
94 > * Counts how often `substr` occurs inside `value`.
95 > */
96 > export function count(value: string, substr: string): number {
97 let result = 0;
98 let index = value.indexOf(substr);
103 return result;
104 }
105 > strings.ts
106 > export function truncate(value: string, maxLength: number, suffix = Ellipsis): string {
107 if (value.length <= maxLength) {
108 return value;
111 return `${value.substr(0, maxLength)}${suffix}`;
112 }
113 > strings.ts
114 > export function truncateMiddle(value: string, maxLength: number, suffix = Ellipsis): string {
115 if (value.length <= maxLength) {
116 return value;
122 return `${value.substr(0, prefixLength)}${suffix}${value.substr(value.length - suffixLength)}`;
123 }
124 > strings.ts
125 > /**
126 > * Removes all occurrences of needle from the beginning and end of haystack.
127 > * @param haystack string to trim
128 > * @param needle the thing to trim (default is a blank)
129 > */
130 > export function trim(haystack: string, needle: string = ' '): string {
131 const trimmed = ltrim(haystack, needle);
132 return rtrim(trimmed, needle);
133 }
134 > strings.ts
135 > /**
136 > * Removes all occurrences of needle from the beginning of haystack.
137 > * @param haystack string to trim
138 > * @param needle the thing to trim
139 > */
140 > export function ltrim(haystack: string, needle: string): string {
141 if (!haystack || !needle) {
142 return haystack;
157 return haystack.substring(offset);
158 }
159 > strings.ts
160 > /**
161 > * Removes all occurrences of needle from the end of haystack.
162 > * @param haystack string to trim
163 > * @param needle the thing to trim
164 > */
165 > export function rtrim(haystack: string, needle: string): string {
166 if (!haystack || !needle) {
167 return haystack;
187 return haystack.substring(0, offset);
188 }
189 > strings.ts
190 > export function convertSimple2RegExpPattern(pattern: string): string {
191 return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
192 }
193 > strings.ts
194 > export interface RegExpOptions {
195 > matchCase?: boolean;
196 > wholeWord?: boolean;
197 > multiline?: boolean;
198 > global?: boolean;
199 > unicode?: boolean;
200 > }
201 >
202 > export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp {
203 if (!searchString) {
204 throw new Error('Cannot create regex from empty string');
231 return new RegExp(searchString, modifiers);
232 }
233 > strings.ts
234 > export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
235 // Exit early if it's one of these special cases which are meant to match
236 // against an empty string
244 return !!(match && regexp.lastIndex === 0);
245 }
246 > strings.ts
247 > export function joinStrings(items: (string | undefined | null | false)[], separator: string): string {
248 return items.filter(item => item !== undefined && item !== null && item !== false).join(separator);
249 }
250 > strings.ts
251 > export function splitLines(str: string): string[] {
252 return str.split(/\r\n|\r|\n/);
253 }
254 > strings.ts
255 > export function splitLinesIncludeSeparators(str: string): string[] {
256 const linesWithSeparators: string[] = [];
257 const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/);
261 return linesWithSeparators;
262 }
263 > strings.ts
264 > export function indexOfPattern(str: string, re: RegExp) {
265 const match = re.exec(str);
266 if (match) {
269 return -1;
270 }
271 > strings.ts
272 > /**
273 > * Returns first index of the string that is not whitespace.
274 > * If string is empty or contains only whitespaces, returns -1
275 > */
276 > export function firstNonWhitespaceIndex(str: string): number {
277 for (let i = 0, len = str.length; i < len; i++) {
278 const chCode = str.charCodeAt(i);
283 return -1;
284 }
285 > strings.ts
286 > /**
287 > * Returns the leading whitespace of the string.
288 > * If the string contains only whitespaces, returns entire string
289 > */
290 > export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string {
291 for (let i = start; i < end; i++) {
292 const chCode = str.charCodeAt(i);
297 return str.substring(start, end);
298 }
299 > strings.ts
300 > /**
301 > * Returns last index of the string that is not whitespace.
302 > * If string is empty or contains only whitespaces, returns -1
303 > */
304 > export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number {
305 for (let i = startIndex; i >= 0; i--) {
306 const chCode = str.charCodeAt(i);
311 return -1;
312 }
313 > strings.ts
314 > export function getIndentationLength(str: string): number {
315 const idx = firstNonWhitespaceIndex(str);
316 if (idx === -1) { return str.length; }
317 return idx;
318 }
319 > strings.ts
320 > /**
321 > * Function that works identically to String.prototype.replace, except, the
322 > * replace function is allowed to be async and return a Promise.
323 > */
324 > export function replaceAsync(str: string, search: RegExp, replacer: (match: string, ...args: unknown[]) => Promise<string>): Promise<string> {
325 const parts: (string | Promise<string>)[] = [];
326
340 return Promise.all(parts).then(p => p.join(''));
341 }
342 > strings.ts
343 > export function compare(a: string, b: string): number {
344 if (a < b) {
345 return -1;
350 }
351 }
352 > strings.ts
353 > export function compareSubstring(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number {
354 for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) {
355 const codeA = a.charCodeAt(aStart);
370 return 0;
371 }
372 > strings.ts
373 > export function compareIgnoreCase(a: string, b: string): number {
374 return compareSubstringIgnoreCase(a, b, 0, a.length, 0, b.length);
375 }
376 > strings.ts
377 > export function compareSubstringIgnoreCase(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number {
378
379 for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) {
421 return 0;
422 }
423 > strings.ts
424 > export function isAsciiDigit(code: number): boolean {
425 return code >= CharCode.Digit0 && code <= CharCode.Digit9;
426 }
427 > strings.ts
428 > export function isLowerAsciiLetter(code: number): boolean {
429 return code >= CharCode.a && code <= CharCode.z;
430 }
431 > strings.ts
432 > export function isUpperAsciiLetter(code: number): boolean {
433 return code >= CharCode.A && code <= CharCode.Z;
434 }
435 > strings.ts
436 > export function equalsIgnoreCase(a: string, b: string): boolean {
437 return a.length === b.length && compareSubstringIgnoreCase(a, b) === 0;
438 }
439 > strings.ts
440 > export function equals(a: string | undefined, b: string | undefined, ignoreCase?: boolean): boolean {
441 return a === b || (!!ignoreCase && a !== undefined && b !== undefined && equalsIgnoreCase(a, b));
442 }
443 > strings.ts
444 > export function startsWithIgnoreCase(str: string, candidate: string): boolean {
445 const len = candidate.length;
446 return len <= str.length && compareSubstringIgnoreCase(str, candidate, 0, len) === 0;
447 }
448 > strings.ts
449 > export function endsWithIgnoreCase(str: string, candidate: string): boolean {
450 const len = str.length;
451 const start = len - candidate.length;
452 return start >= 0 && compareSubstringIgnoreCase(str, candidate, start, len) === 0;
453 }
454 > strings.ts
455 > /**
456 > * @returns the length of the common prefix of the two strings.
457 > */
458 > export function commonPrefixLength(a: string, b: string): number {
459
460 const len = Math.min(a.length, b.length);
469 return len;
470 }
471 > strings.ts
472 > /**
473 > * @returns the length of the common suffix of the two strings.
474 > */
475 > export function commonSuffixLength(a: string, b: string): number {
476
477 const len = Math.min(a.length, b.length);
489 return len;
490 }
491 > strings.ts
492 > /**
493 > * See http://en.wikipedia.org/wiki/Surrogate_pair
494 > */
495 > export function isHighSurrogate(charCode: number): boolean {
496 return (0xD800 <= charCode && charCode <= 0xDBFF);
497 }
498 > strings.ts
499 > /**
500 > * See http://en.wikipedia.org/wiki/Surrogate_pair
501 > */
502 > export function isLowSurrogate(charCode: number): boolean {
503 return (0xDC00 <= charCode && charCode <= 0xDFFF);
504 }
505 > strings.ts
506 > /**
507 > * See http://en.wikipedia.org/wiki/Surrogate_pair
508 > */
509 > export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number {
510 return ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00) + 0x10000;
511 }
512 > strings.ts
513 > /**
514 > * get the code point that begins at offset `offset`
515 > */
516 > export function getNextCodePoint(str: string, len: number, offset: number): number {
517 const charCode = str.charCodeAt(offset);
518 if (isHighSurrogate(charCode) && offset + 1 < len) {
524 return charCode;
525 }
526 > strings.ts
527 > /**
528 > * get the code point that ends right before offset `offset`
529 > */
530 function getPrevCodePoint(str: string, offset: number): number {
531 const charCode = str.charCodeAt(offset - 1);
538 return charCode;
539 }
540 > strings.ts
541 > export class CodePointIterator {
542 >
543 > private readonly _str: string;
544 > private readonly _len: number;
545 > private _offset: number;
546 >
547 > public get offset(): number {
548 return this._offset;
549 }
550 > strings.ts
551 > constructor(str: string, offset: number = 0) {
552 this._str = str;
553 this._len = str.length;
554 this._offset = offset;
555 }
556 > strings.ts
557 > public setOffset(offset: number): void {
558 this._offset = offset;
559 }
560 > strings.ts
561 > public prevCodePoint(): number {
562 const codePoint = getPrevCodePoint(this._str, this._offset);
563 this._offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
564 return codePoint;
565 }
566 > strings.ts
567 > public nextCodePoint(): number {
568 const codePoint = getNextCodePoint(this._str, this._len, this._offset);
569 this._offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
570 return codePoint;
571 }
572 > strings.ts
573 > public eol(): boolean {
574 return (this._offset >= this._len);
575 }
576 > } strings.ts
577 >
578 > export class GraphemeIterator {
579 >
580 > private readonly _iterator: CodePointIterator;
581 >
582 > public get offset(): number {
583 return this._iterator.offset;
584 }
585 > strings.ts
586 > constructor(str: string, offset: number = 0) {
587 this._iterator = new CodePointIterator(str, offset);
588 }
589 > strings.ts
590 > public nextGraphemeLength(): number {
591 const graphemeBreakTree = GraphemeBreakTree.getInstance();
592 const iterator = this._iterator;
606 return (iterator.offset - initialOffset);
607 }
608 > strings.ts
609 > public prevGraphemeLength(): number {
610 const graphemeBreakTree = GraphemeBreakTree.getInstance();
611 const iterator = this._iterator;
625 return (initialOffset - iterator.offset);
626 }
627 > strings.ts
628 > public eol(): boolean {
629 return this._iterator.eol();
630 }
631 > } strings.ts
632 >
633 > export function nextCharLength(str: string, initialOffset: number): number {
634 const iterator = new GraphemeIterator(str, initialOffset);
635 return iterator.nextGraphemeLength();
636 }
637 > strings.ts
638 > export function prevCharLength(str: string, initialOffset: number): number {
639 const iterator = new GraphemeIterator(str, initialOffset);
640 return iterator.prevGraphemeLength();
641 }
642 > strings.ts
643 > export function getCharContainingOffset(str: string, offset: number): [number, number] {
644 if (offset > 0 && isLowSurrogate(str.charCodeAt(offset))) {
645 offset--;
649 return [startOffset, endOffset];
650 }
651 > strings.ts
652 > export function charCount(str: string): number {
653 const iterator = new GraphemeIterator(str);
654 let length = 0;
659 return length;
660 }
661 > strings.ts
662 > let CONTAINS_RTL: RegExp | undefined = undefined;
663 >
664 function makeContainsRtl() {
665 // Generated using https://github.com/alexdima/unicode-utils/blob/main/rtl-test.js
666 return /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/;
667 }
668 > strings.ts
669 > /**
670 > * Returns true if `str` contains any Unicode character that is classified as "R" or "AL".
671 > */
672 > export function containsRTL(str: string): boolean {
673 if (!CONTAINS_RTL) {
674 CONTAINS_RTL = makeContainsRtl();
677 return CONTAINS_RTL.test(str);
678 }
679 > strings.ts
680 > const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/;
681 > /**
682 > * Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t
683 > */
684 > export function isBasicASCII(str: string): boolean {
685 return IS_BASIC_ASCII.test(str);
686 }
687 > strings.ts
688 > export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029]/; // LINE SEPARATOR (LS) or PARAGRAPH SEPARATOR (PS)
689 > /**
690 > * Returns true if `str` contains unusual line terminators, like LS or PS
691 > */
692 > export function containsUnusualLineTerminators(str: string): boolean {
693 return UNUSUAL_LINE_TERMINATORS.test(str);
694 }
695 > strings.ts
696 > export function isFullWidthCharacter(charCode: number): boolean {
697 // Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns
698 // http://jrgraphix.net/research/unicode_blocks.php
741 );
742 }
743 > strings.ts
744 > /**
745 > * A fast function (therefore imprecise) to check if code points are emojis.
746 > * Generated using https://github.com/alexdima/unicode-utils/blob/main/emoji-test.js
747 > */
748 > export function isEmojiImprecise(x: number): boolean {
749 return (
750 (x >= 0x1F1E6 && x <= 0x1F1FF) || (x === 8986) || (x === 8987) || (x === 9200)
755 );
756 }
757 > strings.ts
758 > /**
759 > * Given a string and a max length returns a shorted version. Shorting
760 > * happens at favorable positions - such as whitespace or punctuation characters.
761 > * The return value can be longer than the given value of `n`. Leading whitespace is always trimmed.
762 > */
763 > export function lcut(text: string, n: number, prefix = ''): string {
764 const trimmed = text.trimStart();
765
785 return prefix + trimmed.substring(i).trimStart();
786 }
787 > strings.ts
788 > /**
789 > * Given a string and a max length returns a shortened version keeping the beginning.
790 > * Shortening happens at favorable positions - such as whitespace or punctuation characters.
791 > * Trailing whitespace is always trimmed.
792 > */
793 > export function rcut(text: string, n: number, suffix = ''): string {
794 const trimmed = text.trimEnd();
795
832 return result + suffix;
833 }
834 > strings.ts
835 > // Defacto standard: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
836 > const CSI_SEQUENCE = /(?:\x1b\[|\x9b)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/;
837 > const OSC_SEQUENCE = /(?:\x1b\]|\x9d).*?(?:\x1b\\|\x07|\x9c)/;
838 > const ESC_SEQUENCE = /\x1b(?:[ #%\(\)\*\+\-\.\/]?[a-zA-Z0-9\|}~@])/;
839 > const CONTROL_SEQUENCES = new RegExp('(?:' + [
840 > CSI_SEQUENCE.source,
841 > OSC_SEQUENCE.source,
842 > ESC_SEQUENCE.source,
843 > ].join('|') + ')', 'g');
844 >
845 > /** Iterates over parts of a string with CSI sequences */
846 > export function* forAnsiStringParts(str: string) {
847 let last = 0;
848 for (const match of str.matchAll(CONTROL_SEQUENCES)) {
859 }
860 }
861 > strings.ts
862 > /**
863 > * Strips ANSI escape sequences from a string.
864 > * @param str The dastringa stringo strip the ANSI escape sequences from.
865 > *
866 > * @example
867 > * removeAnsiEscapeCodes('\u001b[31mHello, World!\u001b[0m');
868 > * // 'Hello, World!'
869 > */
870 > export function removeAnsiEscapeCodes(str: string): string {
871 if (str) {
872 str = str.replace(CONTROL_SEQUENCES, '');
875 return str;
876 }
877 > strings.ts
878 > const PROMPT_NON_PRINTABLE = /\\\[.*?\\\]/g;
879 >
880 > /**
881 > * Strips ANSI escape sequences from a UNIX-style prompt string (eg. `$PS1`).
882 > * @param str The string to strip the ANSI escape sequences from.
883 > *
884 > * @example
885 > * removeAnsiEscapeCodesFromPrompt('\n\\[\u001b[01;34m\\]\\w\\[\u001b[00m\\]\n\\[\u001b[1;32m\\]> \\[\u001b[0m\\]');
886 > * // '\n\\w\n> '
887 > */
888 > export function removeAnsiEscapeCodesFromPrompt(str: string): string {
889 return removeAnsiEscapeCodes(str).replace(PROMPT_NON_PRINTABLE, '');
890 }
891 > strings.ts
892 >
893 > // -- UTF-8 BOM
894 >
895 > export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM);
896 >
897 > export function startsWithUTF8BOM(str: string): boolean {
898 return !!(str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM);
899 }
900 > strings.ts
901 > export function stripUTF8BOM(str: string): string {
902 return startsWithUTF8BOM(str) ? str.substr(1) : str;
903 }
904 > strings.ts
905 > /**
906 > * Checks if the characters of the provided query string are included in the
907 > * target string. The characters do not have to be contiguous within the string.
908 > */
909 > export function fuzzyContains(target: string, query: string): boolean {
910 if (!target || !query) {
911 return false; // return early if target or query are undefined
936 return true;
937 }
938 > strings.ts
939 > export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean {
940 if (!target) {
941 return false;
948 return target.toLowerCase() !== target;
949 }
950 > strings.ts
951 > export function uppercaseFirstLetter(str: string): string {
952 return str.charAt(0).toUpperCase() + str.slice(1);
953 }
954 > strings.ts
955 > export function getNLines(str: string, n = 1): string {
956 if (n === 0) {
957 return '';
974 return str.substr(0, idx);
975 }
976 > strings.ts
977 > /**
978 > * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
979 > */
980 > export function singleLetterHash(n: number): string {
981 const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
982
989 return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
990 }
991 > strings.ts
992 > //#region Unicode Grapheme Break
993 >
994 > export function getGraphemeBreakType(codePoint: number): GraphemeBreakType {
995 const graphemeBreakTree = GraphemeBreakTree.getInstance();
996 return graphemeBreakTree.getGraphemeBreakType(codePoint);
997 }
998 > strings.ts
999 function breakBetweenGraphemeBreakType(breakTypeA: GraphemeBreakType, breakTypeB: GraphemeBreakType): boolean {
1000 // http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
1076 return true;
1077 }
1078 > strings.ts
1079 > export const enum GraphemeBreakType {
1080 > Other = 0,
1081 > Prepend = 1,
1082 > CR = 2,
1083 > LF = 3,
1084 > Control = 4,
1085 > Extend = 5,
1086 > Regional_Indicator = 6,
1087 > SpacingMark = 7,
1088 > L = 8,
1089 > V = 9,
1090 > T = 10,
1091 > LV = 11,
1092 > LVT = 12,
1093 > ZWJ = 13,
1094 > Extended_Pictographic = 14
1095 > }
1096 >
1097 > class GraphemeBreakTree {
1098 >
1099 > private static _INSTANCE: GraphemeBreakTree | null = null;
1100 > public static getInstance(): GraphemeBreakTree {
1101 if (!GraphemeBreakTree._INSTANCE) {
1102 GraphemeBreakTree._INSTANCE = new GraphemeBreakTree();
1104 return GraphemeBreakTree._INSTANCE;
1105 }
1106 > strings.ts
1107 > private readonly _data: number[];
1108 >
1109 > constructor() {
1110 this._data = getGraphemeBreakRawData();
1111 }
1112 > strings.ts
1113 > public getGraphemeBreakType(codePoint: number): GraphemeBreakType {
1114 // !!! Let's make 7bit ASCII a bit faster: 0..31
1115 if (codePoint < 32) {
1145 return GraphemeBreakType.Other;
1146 }
1147 > } strings.ts
1148 >
1149 function getGraphemeBreakRawData(): number[] {
1150 // generated using https://github.com/alexdima/unicode-utils/blob/main/grapheme-break.js
1151 return JSON.parse('[0,0,0,51229,51255,12,44061,44087,12,127462,127487,6,7083,7085,5,47645,47671,12,54813,54839,12,128678,128678,14,3270,3270,5,9919,9923,14,45853,45879,12,49437,49463,12,53021,53047,12,71216,71218,7,128398,128399,14,129360,129374,14,2519,2519,5,4448,4519,9,9742,9742,14,12336,12336,14,44957,44983,12,46749,46775,12,48541,48567,12,50333,50359,12,52125,52151,12,53917,53943,12,69888,69890,5,73018,73018,5,127990,127990,14,128558,128559,14,128759,128760,14,129653,129655,14,2027,2035,5,2891,2892,7,3761,3761,5,6683,6683,5,8293,8293,4,9825,9826,14,9999,9999,14,43452,43453,5,44509,44535,12,45405,45431,12,46301,46327,12,47197,47223,12,48093,48119,12,48989,49015,12,49885,49911,12,50781,50807,12,51677,51703,12,52573,52599,12,53469,53495,12,54365,54391,12,65279,65279,4,70471,70472,7,72145,72147,7,119173,119179,5,127799,127818,14,128240,128244,14,128512,128512,14,128652,128652,14,128721,128722,14,129292,129292,14,129445,129450,14,129734,129743,14,1476,1477,5,2366,2368,7,2750,2752,7,3076,3076,5,3415,3415,5,4141,4144,5,6109,6109,5,6964,6964,5,7394,7400,5,9197,9198,14,9770,9770,14,9877,9877,14,9968,9969,14,10084,10084,14,43052,43052,5,43713,43713,5,44285,44311,12,44733,44759,12,45181,45207,12,45629,45655,12,46077,46103,12,46525,46551,12,46973,46999,12,47421,47447,12,47869,47895,12,48317,48343,12,48765,48791,12,49213,49239,12,49661,49687,12,50109,50135,12,50557,50583,12,51005,51031,12,51453,51479,12,51901,51927,12,52349,52375,12,52797,52823,12,53245,53271,12,53693,53719,12,54141,54167,12,54589,54615,12,55037,55063,12,69506,69509,5,70191,70193,5,70841,70841,7,71463,71467,5,72330,72342,5,94031,94031,5,123628,123631,5,127763,127765,14,127941,127941,14,128043,128062,14,128302,128317,14,128465,128467,14,128539,128539,14,128640,128640,14,128662,128662,14,128703,128703,14,128745,128745,14,129004,129007,14,129329,129330,14,129402,129402,14,129483,129483,14,129686,129704,14,130048,131069,14,173,173,4,1757,1757,1,2200,2207,5,2434,2435,7,2631,2632,5,2817,2817,5,3008,3008,5,3201,3201,5,3387,3388,5,3542,3542,5,3902,3903,7,4190,4192,5,6002,6003,5,6439,6440,5,6765,6770,7,7019,7027,5,7154,7155,7,8205,8205,13,8505,8505,14,9654,9654,14,9757,9757,14,9792,9792,14,9852,9853,14,9890,9894,14,9937,9937,14,9981,9981,14,10035,10036,14,11035,11036,14,42654,42655,5,43346,43347,7,43587,43587,5,44006,44007,7,44173,44199,12,44397,44423,12,44621,44647,12,44845,44871,12,45069,45095,12,45293,45319,12,45517,45543,12,45741,45767,12,45965,45991,12,46189,46215,12,46413,46439,12,46637,46663,12,46861,46887,12,47085,47111,12,47309,47335,12,47533,47559,12,47757,47783,12,47981,48007,12,48205,48231,12,48429,48455,12,48653,48679,12,48877,48903,12,49101,49127,12,49325,49351,12,49549,49575,12,49773,49799,12,49997,50023,12,50221,50247,12,50445,50471,12,50669,50695,12,50893,50919,12,51117,51143,12,51341,51367,12,51565,51591,12,51789,51815,12,52013,52039,12,52237,52263,12,52461,52487,12,52685,52711,12,52909,52935,12,53133,53159,12,53357,53383,12,53581,53607,12,53805,53831,12,54029,54055,12,54253,54279,12,54477,54503,12,54701,54727,12,54925,54951,12,55149,55175,12,68101,68102,5,69762,69762,7,70067,70069,7,70371,70378,5,70720,70721,7,71087,71087,5,71341,71341,5,71995,71996,5,72249,72249,7,72850,72871,5,73109,73109,5,118576,118598,5,121505,121519,5,127245,127247,14,127568,127569,14,127777,127777,14,127872,127891,14,127956,127967,14,128015,128016,14,128110,128172,14,128259,128259,14,128367,128368,14,128424,128424,14,128488,128488,14,128530,128532,14,128550,128551,14,128566,128566,14,128647,128647,14,128656,128656,14,128667,128673,14,128691,128693,14,128715,128715,14,128728,128732,14,128752,128752,14,128765,128767,14,129096,129103,14,129311,129311,14,129344,129349,14,129394,129394,14,129413,129425,14,129466,129471,14,129511,129535,14,129664,129666,14,129719,129722,14,129760,129767,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2307,2307,7,2382,2383,7,2497,2500,5,2563,2563,7,2677,2677,5,2763,2764,7,2879,2879,5,2914,2915,5,3021,3021,5,3142,3144,5,3263,3263,5,3285,3286,5,3398,3400,7,3530,3530,5,3633,3633,5,3864,3865,5,3974,3975,5,4155,4156,7,4229,4230,5,5909,5909,7,6078,6085,7,6277,6278,5,6451,6456,7,6744,6750,5,6846,6846,5,6972,6972,5,7074,7077,5,7146,7148,7,7222,7223,5,7416,7417,5,8234,8238,4,8417,8417,5,9000,9000,14,9203,9203,14,9730,9731,14,9748,9749,14,9762,9763,14,9776,9783,14,9800,9811,14,9831,9831,14,9872,9873,14,9882,9882,14,9900,9903,14,9929,9933,14,9941,9960,14,9974,9974,14,9989,9989,14,10006,10006,14,10062,10062,14,10160,10160,14,11647,11647,5,12953,12953,14,43019,43019,5,43232,43249,5,43443,43443,5,43567,43568,7,43696,43696,5,43765,43765,7,44013,44013,5,44117,44143,12,44229,44255,12,44341,44367,12,44453,44479,12,44565,44591,12,44677,44703,12,44789,44815,12,44901,44927,12,45013,45039,12,45125,45151,12,45237,45263,12,45349,45375,12,45461,45487,12,45573,45599,12,45685,45711,12,45797,45823,12,45909,45935,12,46021,46047,12,46133,46159,12,46245,46271,12,46357,46383,12,46469,46495,12,46581,46607,12,46693,46719,12,46805,46831,12,46917,46943,12,47029,47055,12,47141,47167,12,47253,47279,12,47365,47391,12,47477,47503,12,47589,47615,12,47701,47727,12,47813,47839,12,47925,47951,12,48037,48063,12,48149,48175,12,48261,48287,12,48373,48399,12,48485,48511,12,48597,48623,12,48709,48735,12,48821,48847,12,48933,48959,12,49045,49071,12,49157,49183,12,49269,49295,12,49381,49407,12,49493,49519,12,49605,49631,12,49717,49743,12,49829,49855,12,49941,49967,12,50053,50079,12,50165,50191,12,50277,50303,12,50389,50415,12,50501,50527,12,50613,50639,12,50725,50751,12,50837,50863,12,50949,50975,12,51061,51087,12,51173,51199,12,51285,51311,12,51397,51423,12,51509,51535,12,51621,51647,12,51733,51759,12,51845,51871,12,51957,51983,12,52069,52095,12,52181,52207,12,52293,52319,12,52405,52431,12,52517,52543,12,52629,52655,12,52741,52767,12,52853,52879,12,52965,52991,12,53077,53103,12,53189,53215,12,53301,53327,12,53413,53439,12,53525,53551,12,53637,53663,12,53749,53775,12,53861,53887,12,53973,53999,12,54085,54111,12,54197,54223,12,54309,54335,12,54421,54447,12,54533,54559,12,54645,54671,12,54757,54783,12,54869,54895,12,54981,55007,12,55093,55119,12,55243,55291,10,66045,66045,5,68325,68326,5,69688,69702,5,69817,69818,5,69957,69958,7,70089,70092,5,70198,70199,5,70462,70462,5,70502,70508,5,70750,70750,5,70846,70846,7,71100,71101,5,71230,71230,7,71351,71351,5,71737,71738,5,72000,72000,7,72160,72160,5,72273,72278,5,72752,72758,5,72882,72883,5,73031,73031,5,73461,73462,7,94192,94193,7,119149,119149,7,121403,121452,5,122915,122916,5,126980,126980,14,127358,127359,14,127535,127535,14,127759,127759,14,127771,127771,14,127792,127793,14,127825,127867,14,127897,127899,14,127945,127945,14,127985,127986,14,128000,128007,14,128021,128021,14,128066,128100,14,128184,128235,14,128249,128252,14,128266,128276,14,128335,128335,14,128379,128390,14,128407,128419,14,128444,128444,14,128481,128481,14,128499,128499,14,128526,128526,14,128536,128536,14,128543,128543,14,128556,128556,14,128564,128564,14,128577,128580,14,128643,128645,14,128649,128649,14,128654,128654,14,128660,128660,14,128664,128664,14,128675,128675,14,128686,128689,14,128695,128696,14,128705,128709,14,128717,128719,14,128725,128725,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129009,129023,14,129160,129167,14,129296,129304,14,129320,129327,14,129340,129342,14,129356,129356,14,129388,129392,14,129399,129400,14,129404,129407,14,129432,129442,14,129454,129455,14,129473,129474,14,129485,129487,14,129648,129651,14,129659,129660,14,129671,129679,14,129709,129711,14,129728,129730,14,129751,129753,14,129776,129782,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2274,2274,1,2363,2363,7,2377,2380,7,2402,2403,5,2494,2494,5,2507,2508,7,2558,2558,5,2622,2624,7,2641,2641,5,2691,2691,7,2759,2760,5,2786,2787,5,2876,2876,5,2881,2884,5,2901,2902,5,3006,3006,5,3014,3016,7,3072,3072,5,3134,3136,5,3157,3158,5,3260,3260,5,3266,3266,5,3274,3275,7,3328,3329,5,3391,3392,7,3405,3405,5,3457,3457,5,3536,3537,7,3551,3551,5,3636,3642,5,3764,3772,5,3895,3895,5,3967,3967,7,3993,4028,5,4146,4151,5,4182,4183,7,4226,4226,5,4253,4253,5,4957,4959,5,5940,5940,7,6070,6070,7,6087,6088,7,6158,6158,4,6432,6434,5,6448,6449,7,6679,6680,5,6742,6742,5,6754,6754,5,6783,6783,5,6912,6915,5,6966,6970,5,6978,6978,5,7042,7042,7,7080,7081,5,7143,7143,7,7150,7150,7,7212,7219,5,7380,7392,5,7412,7412,5,8203,8203,4,8232,8232,4,8265,8265,14,8400,8412,5,8421,8432,5,8617,8618,14,9167,9167,14,9200,9200,14,9410,9410,14,9723,9726,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9774,14,9786,9786,14,9794,9794,14,9823,9823,14,9828,9828,14,9833,9850,14,9855,9855,14,9875,9875,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9935,9935,14,9939,9939,14,9962,9962,14,9972,9972,14,9978,9978,14,9986,9986,14,9997,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10133,10135,14,10548,10549,14,11093,11093,14,12330,12333,5,12441,12442,5,42608,42610,5,43010,43010,5,43045,43046,5,43188,43203,7,43302,43309,5,43392,43394,5,43446,43449,5,43493,43493,5,43571,43572,7,43597,43597,7,43703,43704,5,43756,43757,5,44003,44004,7,44009,44010,7,44033,44059,12,44089,44115,12,44145,44171,12,44201,44227,12,44257,44283,12,44313,44339,12,44369,44395,12,44425,44451,12,44481,44507,12,44537,44563,12,44593,44619,12,44649,44675,12,44705,44731,12,44761,44787,12,44817,44843,12,44873,44899,12,44929,44955,12,44985,45011,12,45041,45067,12,45097,45123,12,45153,45179,12,45209,45235,12,45265,45291,12,45321,45347,12,45377,45403,12,45433,45459,12,45489,45515,12,45545,45571,12,45601,45627,12,45657,45683,12,45713,45739,12,45769,45795,12,45825,45851,12,45881,45907,12,45937,45963,12,45993,46019,12,46049,46075,12,46105,46131,12,46161,46187,12,46217,46243,12,46273,46299,12,46329,46355,12,46385,46411,12,46441,46467,12,46497,46523,12,46553,46579,12,46609,46635,12,46665,46691,12,46721,46747,12,46777,46803,12,46833,46859,12,46889,46915,12,46945,46971,12,47001,47027,12,47057,47083,12,47113,47139,12,47169,47195,12,47225,47251,12,47281,47307,12,47337,47363,12,47393,47419,12,47449,47475,12,47505,47531,12,47561,47587,12,47617,47643,12,47673,47699,12,47729,47755,12,47785,47811,12,47841,47867,12,47897,47923,12,47953,47979,12,48009,48035,12,48065,48091,12,48121,48147,12,48177,48203,12,48233,48259,12,48289,48315,12,48345,48371,12,48401,48427,12,48457,48483,12,48513,48539,12,48569,48595,12,48625,48651,12,48681,48707,12,48737,48763,12,48793,48819,12,48849,48875,12,48905,48931,12,48961,48987,12,49017,49043,12,49073,49099,12,49129,49155,12,49185,49211,12,49241,49267,12,49297,49323,12,49353,49379,12,49409,49435,12,49465,49491,12,49521,49547,12,49577,49603,12,49633,49659,12,49689,49715,12,49745,49771,12,49801,49827,12,49857,49883,12,49913,49939,12,49969,49995,12,50025,50051,12,50081,50107,12,50137,50163,12,50193,50219,12,50249,50275,12,50305,50331,12,50361,50387,12,50417,50443,12,50473,50499,12,50529,50555,12,50585,50611,12,50641,50667,12,50697,50723,12,50753,50779,12,50809,50835,12,50865,50891,12,50921,50947,12,50977,51003,12,51033,51059,12,51089,51115,12,51145,51171,12,51201,51227,12,51257,51283,12,51313,51339,12,51369,51395,12,51425,51451,12,51481,51507,12,51537,51563,12,51593,51619,12,51649,51675,12,51705,51731,12,51761,51787,12,51817,51843,12,51873,51899,12,51929,51955,12,51985,52011,12,52041,52067,12,52097,52123,12,52153,52179,12,52209,52235,12,52265,52291,12,52321,52347,12,52377,52403,12,52433,52459,12,52489,52515,12,52545,52571,12,52601,52627,12,52657,52683,12,52713,52739,12,52769,52795,12,52825,52851,12,52881,52907,12,52937,52963,12,52993,53019,12,53049,53075,12,53105,53131,12,53161,53187,12,53217,53243,12,53273,53299,12,53329,53355,12,53385,53411,12,53441,53467,12,53497,53523,12,53553,53579,12,53609,53635,12,53665,53691,12,53721,53747,12,53777,53803,12,53833,53859,12,53889,53915,12,53945,53971,12,54001,54027,12,54057,54083,12,54113,54139,12,54169,54195,12,54225,54251,12,54281,54307,12,54337,54363,12,54393,54419,12,54449,54475,12,54505,54531,12,54561,54587,12,54617,54643,12,54673,54699,12,54729,54755,12,54785,54811,12,54841,54867,12,54897,54923,12,54953,54979,12,55009,55035,12,55065,55091,12,55121,55147,12,55177,55203,12,65024,65039,5,65520,65528,4,66422,66426,5,68152,68154,5,69291,69292,5,69633,69633,5,69747,69748,5,69811,69814,5,69826,69826,5,69932,69932,7,70016,70017,5,70079,70080,7,70095,70095,5,70196,70196,5,70367,70367,5,70402,70403,7,70464,70464,5,70487,70487,5,70709,70711,7,70725,70725,7,70833,70834,7,70843,70844,7,70849,70849,7,71090,71093,5,71103,71104,5,71227,71228,7,71339,71339,5,71344,71349,5,71458,71461,5,71727,71735,5,71985,71989,7,71998,71998,5,72002,72002,7,72154,72155,5,72193,72202,5,72251,72254,5,72281,72283,5,72344,72345,5,72766,72766,7,72874,72880,5,72885,72886,5,73023,73029,5,73104,73105,5,73111,73111,5,92912,92916,5,94095,94098,5,113824,113827,4,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,125252,125258,5,127183,127183,14,127340,127343,14,127377,127386,14,127491,127503,14,127548,127551,14,127744,127756,14,127761,127761,14,127769,127769,14,127773,127774,14,127780,127788,14,127796,127797,14,127820,127823,14,127869,127869,14,127894,127895,14,127902,127903,14,127943,127943,14,127947,127950,14,127972,127972,14,127988,127988,14,127992,127994,14,128009,128011,14,128019,128019,14,128023,128041,14,128064,128064,14,128102,128107,14,128174,128181,14,128238,128238,14,128246,128247,14,128254,128254,14,128264,128264,14,128278,128299,14,128329,128330,14,128348,128359,14,128371,128377,14,128392,128393,14,128401,128404,14,128421,128421,14,128433,128434,14,128450,128452,14,128476,128478,14,128483,128483,14,128495,128495,14,128506,128506,14,128519,128520,14,128528,128528,14,128534,128534,14,128538,128538,14,128540,128542,14,128544,128549,14,128552,128555,14,128557,128557,14,128560,128563,14,128565,128565,14,128567,128576,14,128581,128591,14,128641,128642,14,128646,128646,14,128648,128648,14,128650,128651,14,128653,128653,14,128655,128655,14,128657,128659,14,128661,128661,14,128663,128663,14,128665,128666,14,128674,128674,14,128676,128677,14,128679,128685,14,128690,128690,14,128694,128694,14,128697,128702,14,128704,128704,14,128710,128714,14,128716,128716,14,128720,128720,14,128723,128724,14,128726,128727,14,128733,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129008,129008,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129661,129663,14,129667,129670,14,129680,129685,14,129705,129708,14,129712,129718,14,129723,129727,14,129731,129733,14,129744,129750,14,129754,129759,14,129768,129775,14,129783,129791,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2192,2193,1,2250,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3132,3132,5,3137,3140,7,3146,3149,5,3170,3171,5,3202,3203,7,3262,3262,7,3264,3265,7,3267,3268,7,3271,3272,7,3276,3277,5,3298,3299,5,3330,3331,7,3390,3390,5,3393,3396,5,3402,3404,7,3406,3406,1,3426,3427,5,3458,3459,7,3535,3535,5,3538,3540,5,3544,3550,7,3570,3571,7,3635,3635,7,3655,3662,5,3763,3763,7,3784,3789,5,3893,3893,5,3897,3897,5,3953,3966,5,3968,3972,5,3981,3991,5,4038,4038,5,4145,4145,7,4153,4154,5,4157,4158,5,4184,4185,5,4209,4212,5,4228,4228,7,4237,4237,5,4352,4447,8,4520,4607,10,5906,5908,5,5938,5939,5,5970,5971,5,6068,6069,5,6071,6077,5,6086,6086,5,6089,6099,5,6155,6157,5,6159,6159,5,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6862,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7679,5,8204,8204,5,8206,8207,4,8233,8233,4,8252,8252,14,8288,8292,4,8294,8303,4,8413,8416,5,8418,8420,5,8482,8482,14,8596,8601,14,8986,8987,14,9096,9096,14,9193,9196,14,9199,9199,14,9201,9202,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9729,14,9732,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9775,9775,14,9784,9785,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9874,14,9876,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9934,14,9936,9936,14,9938,9938,14,9940,9940,14,9961,9961,14,9963,9967,14,9970,9971,14,9973,9973,14,9975,9977,14,9979,9980,14,9982,9985,14,9987,9988,14,9992,9996,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10083,14,10085,10087,14,10145,10145,14,10175,10175,14,11013,11015,14,11088,11088,14,11503,11505,5,11744,11775,5,12334,12335,5,12349,12349,14,12951,12951,14,42607,42607,5,42612,42621,5,42736,42737,5,43014,43014,5,43043,43044,7,43047,43047,7,43136,43137,7,43204,43205,5,43263,43263,5,43335,43345,5,43360,43388,8,43395,43395,7,43444,43445,7,43450,43451,7,43454,43456,7,43561,43566,5,43569,43570,5,43573,43574,5,43596,43596,5,43644,43644,5,43698,43700,5,43710,43711,5,43755,43755,7,43758,43759,7,43766,43766,5,44005,44005,5,44008,44008,5,44012,44012,7,44032,44032,11,44060,44060,11,44088,44088,11,44116,44116,11,44144,44144,11,44172,44172,11,44200,44200,11,44228,44228,11,44256,44256,11,44284,44284,11,44312,44312,11,44340,44340,11,44368,44368,11,44396,44396,11,44424,44424,11,44452,44452,11,44480,44480,11,44508,44508,11,44536,44536,11,44564,44564,11,44592,44592,11,44620,44620,11,44648,44648,11,44676,44676,11,44704,44704,11,44732,44732,11,44760,44760,11,44788,44788,11,44816,44816,11,44844,44844,11,44872,44872,11,44900,44900,11,44928,44928,11,44956,44956,11,44984,44984,11,45012,45012,11,45040,45040,11,45068,45068,11,45096,45096,11,45124,45124,11,45152,45152,11,45180,45180,11,45208,45208,11,45236,45236,11,45264,45264,11,45292,45292,11,45320,45320,11,45348,45348,11,45376,45376,11,45404,45404,11,45432,45432,11,45460,45460,11,45488,45488,11,45516,45516,11,45544,45544,11,45572,45572,11,45600,45600,11,45628,45628,11,45656,45656,11,45684,45684,11,45712,45712,11,45740,45740,11,45768,45768,11,45796,45796,11,45824,45824,11,45852,45852,11,45880,45880,11,45908,45908,11,45936,45936,11,45964,45964,11,45992,45992,11,46020,46020,11,46048,46048,11,46076,46076,11,46104,46104,11,46132,46132,11,46160,46160,11,46188,46188,11,46216,46216,11,46244,46244,11,46272,46272,11,46300,46300,11,46328,46328,11,46356,46356,11,46384,46384,11,46412,46412,11,46440,46440,11,46468,46468,11,46496,46496,11,46524,46524,11,46552,46552,11,46580,46580,11,46608,46608,11,46636,46636,11,46664,46664,11,46692,46692,11,46720,46720,11,46748,46748,11,46776,46776,11,46804,46804,11,46832,46832,11,46860,46860,11,46888,46888,11,46916,46916,11,46944,46944,11,46972,46972,11,47000,47000,11,47028,47028,11,47056,47056,11,47084,47084,11,47112,47112,11,47140,47140,11,47168,47168,11,47196,47196,11,47224,47224,11,47252,47252,11,47280,47280,11,47308,47308,11,47336,47336,11,47364,47364,11,47392,47392,11,47420,47420,11,47448,47448,11,47476,47476,11,47504,47504,11,47532,47532,11,47560,47560,11,47588,47588,11,47616,47616,11,47644,47644,11,47672,47672,11,47700,47700,11,47728,47728,11,47756,47756,11,47784,47784,11,47812,47812,11,47840,47840,11,47868,47868,11,47896,47896,11,47924,47924,11,47952,47952,11,47980,47980,11,48008,48008,11,48036,48036,11,48064,48064,11,48092,48092,11,48120,48120,11,48148,48148,11,48176,48176,11,48204,48204,11,48232,48232,11,48260,48260,11,48288,48288,11,48316,48316,11,48344,48344,11,48372,48372,11,48400,48400,11,48428,48428,11,48456,48456,11,48484,48484,11,48512,48512,11,48540,48540,11,48568,48568,11,48596,48596,11,48624,48624,11,48652,48652,11,48680,48680,11,48708,48708,11,48736,48736,11,48764,48764,11,48792,48792,11,48820,48820,11,48848,48848,11,48876,48876,11,48904,48904,11,48932,48932,11,48960,48960,11,48988,48988,11,49016,49016,11,49044,49044,11,49072,49072,11,49100,49100,11,49128,49128,11,49156,49156,11,49184,49184,11,49212,49212,11,49240,49240,11,49268,49268,11,49296,49296,11,49324,49324,11,49352,49352,11,49380,49380,11,49408,49408,11,49436,49436,11,49464,49464,11,49492,49492,11,49520,49520,11,49548,49548,11,49576,49576,11,49604,49604,11,49632,49632,11,49660,49660,11,49688,49688,11,49716,49716,11,49744,49744,11,49772,49772,11,49800,49800,11,49828,49828,11,49856,49856,11,49884,49884,11,49912,49912,11,49940,49940,11,49968,49968,11,49996,49996,11,50024,50024,11,50052,50052,11,50080,50080,11,50108,50108,11,50136,50136,11,50164,50164,11,50192,50192,11,50220,50220,11,50248,50248,11,50276,50276,11,50304,50304,11,50332,50332,11,50360,50360,11,50388,50388,11,50416,50416,11,50444,50444,11,50472,50472,11,50500,50500,11,50528,50528,11,50556,50556,11,50584,50584,11,50612,50612,11,50640,50640,11,50668,50668,11,50696,50696,11,50724,50724,11,50752,50752,11,50780,50780,11,50808,50808,11,50836,50836,11,50864,50864,11,50892,50892,11,50920,50920,11,50948,50948,11,50976,50976,11,51004,51004,11,51032,51032,11,51060,51060,11,51088,51088,11,51116,51116,11,51144,51144,11,51172,51172,11,51200,51200,11,51228,51228,11,51256,51256,11,51284,51284,11,51312,51312,11,51340,51340,11,51368,51368,11,51396,51396,11,51424,51424,11,51452,51452,11,51480,51480,11,51508,51508,11,51536,51536,11,51564,51564,11,51592,51592,11,51620,51620,11,51648,51648,11,51676,51676,11,51704,51704,11,51732,51732,11,51760,51760,11,51788,51788,11,51816,51816,11,51844,51844,11,51872,51872,11,51900,51900,11,51928,51928,11,51956,51956,11,51984,51984,11,52012,52012,11,52040,52040,11,52068,52068,11,52096,52096,11,52124,52124,11,52152,52152,11,52180,52180,11,52208,52208,11,52236,52236,11,52264,52264,11,52292,52292,11,52320,52320,11,52348,52348,11,52376,52376,11,52404,52404,11,52432,52432,11,52460,52460,11,52488,52488,11,52516,52516,11,52544,52544,11,52572,52572,11,52600,52600,11,52628,52628,11,52656,52656,11,52684,52684,11,52712,52712,11,52740,52740,11,52768,52768,11,52796,52796,11,52824,52824,11,52852,52852,11,52880,52880,11,52908,52908,11,52936,52936,11,52964,52964,11,52992,52992,11,53020,53020,11,53048,53048,11,53076,53076,11,53104,53104,11,53132,53132,11,53160,53160,11,53188,53188,11,53216,53216,11,53244,53244,11,53272,53272,11,53300,53300,11,53328,53328,11,53356,53356,11,53384,53384,11,53412,53412,11,53440,53440,11,53468,53468,11,53496,53496,11,53524,53524,11,53552,53552,11,53580,53580,11,53608,53608,11,53636,53636,11,53664,53664,11,53692,53692,11,53720,53720,11,53748,53748,11,53776,53776,11,53804,53804,11,53832,53832,11,53860,53860,11,53888,53888,11,53916,53916,11,53944,53944,11,53972,53972,11,54000,54000,11,54028,54028,11,54056,54056,11,54084,54084,11,54112,54112,11,54140,54140,11,54168,54168,11,54196,54196,11,54224,54224,11,54252,54252,11,54280,54280,11,54308,54308,11,54336,54336,11,54364,54364,11,54392,54392,11,54420,54420,11,54448,54448,11,54476,54476,11,54504,54504,11,54532,54532,11,54560,54560,11,54588,54588,11,54616,54616,11,54644,54644,11,54672,54672,11,54700,54700,11,54728,54728,11,54756,54756,11,54784,54784,11,54812,54812,11,54840,54840,11,54868,54868,11,54896,54896,11,54924,54924,11,54952,54952,11,54980,54980,11,55008,55008,11,55036,55036,11,55064,55064,11,55092,55092,11,55120,55120,11,55148,55148,11,55176,55176,11,55216,55238,9,64286,64286,5,65056,65071,5,65438,65439,5,65529,65531,4,66272,66272,5,68097,68099,5,68108,68111,5,68159,68159,5,68900,68903,5,69446,69456,5,69632,69632,7,69634,69634,7,69744,69744,5,69759,69761,5,69808,69810,7,69815,69816,7,69821,69821,1,69837,69837,1,69927,69931,5,69933,69940,5,70003,70003,5,70018,70018,7,70070,70078,5,70082,70083,1,70094,70094,7,70188,70190,7,70194,70195,7,70197,70197,7,70206,70206,5,70368,70370,7,70400,70401,5,70459,70460,5,70463,70463,7,70465,70468,7,70475,70477,7,70498,70499,7,70512,70516,5,70712,70719,5,70722,70724,5,70726,70726,5,70832,70832,5,70835,70840,5,70842,70842,5,70845,70845,5,70847,70848,5,70850,70851,5,71088,71089,7,71096,71099,7,71102,71102,7,71132,71133,5,71219,71226,5,71229,71229,5,71231,71232,5,71340,71340,7,71342,71343,7,71350,71350,7,71453,71455,5,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,118528,118573,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123566,123566,5,125136,125142,5,126976,126979,14,126981,127182,14,127184,127231,14,127279,127279,14,127344,127345,14,127374,127374,14,127405,127461,14,127489,127490,14,127514,127514,14,127538,127546,14,127561,127567,14,127570,127743,14,127757,127758,14,127760,127760,14,127762,127762,14,127766,127768,14,127770,127770,14,127772,127772,14,127775,127776,14,127778,127779,14,127789,127791,14,127794,127795,14,127798,127798,14,127819,127819,14,127824,127824,14,127868,127868,14,127870,127871,14,127892,127893,14,127896,127896,14,127900,127901,14,127904,127940,14,127942,127942,14,127944,127944,14,127946,127946,14,127951,127955,14,127968,127971,14,127973,127984,14,127987,127987,14,127989,127989,14,127991,127991,14,127995,127999,5,128008,128008,14,128012,128014,14,128017,128018,14,128020,128020,14,128022,128022,14,128042,128042,14,128063,128063,14,128065,128065,14,128101,128101,14,128108,128109,14,128173,128173,14,128182,128183,14,128236,128237,14,128239,128239,14,128245,128245,14,128248,128248,14,128253,128253,14,128255,128258,14,128260,128263,14,128265,128265,14,128277,128277,14,128300,128301,14,128326,128328,14,128331,128334,14,128336,128347,14,128360,128366,14,128369,128370,14,128378,128378,14,128391,128391,14,128394,128397,14,128400,128400,14,128405,128406,14,128420,128420,14,128422,128423,14,128425,128432,14,128435,128443,14,128445,128449,14,128453,128464,14,128468,128475,14,128479,128480,14,128482,128482,14,128484,128487,14,128489,128494,14,128496,128498,14,128500,128505,14,128507,128511,14,128513,128518,14,128521,128525,14,128527,128527,14,128529,128529,14,128533,128533,14,128535,128535,14,128537,128537,14]');
1152 }
1153 > strings.ts
1154 > //#endregion
1155 >
1156 > /**
1157 > * Computes the offset after performing a left delete on the given string,
1158 > * while considering unicode grapheme/emoji rules.
1159 > */
1160 > export function getLeftDeleteOffset(offset: number, str: string): number {
1161 if (offset === 0) {
1162 return 0;
1174 return iterator.offset;
1175 }
1176 > strings.ts
1177 function getOffsetBeforeLastEmojiComponent(initialOffset: number, str: string): number | undefined {
1178 // See https://www.unicode.org/reports/tr51/tr51-14.html#EBNF_and_Regex for the
1210 return resultOffset;
1211 }
1212 > strings.ts
1213 function isEmojiModifier(codePoint: number): boolean {
1214 return 0x1F3FB <= codePoint && codePoint <= 0x1F3FF;
1215 }
1216 > strings.ts
1217 > const enum CodePoint {
1218 > zwj = 0x200D,
1219 >
1220 > /**
1221 > * Variation Selector-16 (VS16)
1222 > */
1223 > emojiVariantSelector = 0xFE0F,
1224 >
1225 > /**
1226 > * Combining Enclosing Keycap
1227 > */
1228 > enclosingKeyCap = 0x20E3,
1229 >
1230 > space = 0x0020,
1231 > }
1232 >
1233 > export const noBreakWhitespace = '\xa0';
1234 >
1235 > export class AmbiguousCharacters {
1236 > private static readonly ambiguousCharacterData = new Lazy<
1237 > Record<
1238 > string | '_common' | '_default',
1239 > /* code point -> ascii code point */ number[]
1240 > >
1241 > >(() => {
1242 // Generated using https://github.com/hediet/vscode-unicode-data
1243 // Stored as key1, value1, key2, value2, ...
1245 '{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,1523,96,8242,96,1370,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,118002,50,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,118003,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,118004,52,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,118005,53,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,118006,54,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,118007,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,118008,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,118009,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,117974,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,117975,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71913,67,71922,67,65315,67,8557,67,8450,67,8493,67,117976,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,117977,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,117978,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,117979,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,117980,71,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,117981,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,117983,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,117984,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,118001,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,117982,108,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,117985,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,117986,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,117987,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,118000,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,117988,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,117989,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,117990,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,117991,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,117992,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,117993,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,117994,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,117995,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71910,87,71919,87,117996,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,117997,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,117998,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,71909,90,66293,90,65338,90,8484,90,8488,90,117999,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65283,35,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125,119846,109],\"_default\":[160,32,8211,45,65374,126,8218,44,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"cs\":[65374,126,8218,44,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"es\":[8211,45,65374,126,8218,44,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"fr\":[65374,126,8218,44,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,8218,44,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"ja\":[8211,45,8218,44,65281,33,8216,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65292,44,65297,49,65307,59],\"ko\":[8211,45,65374,126,8218,44,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"pt-BR\":[65374,126,8218,44,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,8218,44,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"ru\":[65374,126,8218,44,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,8218,44,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41,65292,44,65297,49,65307,59,65311,63],\"zh-hans\":[160,32,65374,126,8218,44,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65297,49],\"zh-hant\":[8211,45,65374,126,8218,44,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89]}'
1246 );
1247 > }); strings.ts
1248 >
1249 > private static readonly cache = new LRUCachedFunction<string, AmbiguousCharacters>((localesStr) => {
1250 const locales = localesStr.split(',');
1251
1304
1305 return new AmbiguousCharacters(map);
1306 > }); strings.ts
1307 >
1308 > public static getInstance(locales: Iterable<string>): AmbiguousCharacters {
1309 return AmbiguousCharacters.cache.get(Array.from(locales).join(','));
1310 }
1311 > strings.ts
1312 > private static _locales = new Lazy<string[]>(() =>
1313 Object.keys(AmbiguousCharacters.ambiguousCharacterData.value).filter(
1314 (k) => !k.startsWith('_')
1315 )
1316 > ); strings.ts
1317 > public static getLocales(): string[] {
1318 return AmbiguousCharacters._locales.value;
1319 }
1320 > strings.ts
1321 > private constructor(
1322 private readonly confusableDictionary: Map<number, number>
1323 ) { }
1324 > strings.ts
1325 > public isAmbiguous(codePoint: number): boolean {
1326 return this.confusableDictionary.has(codePoint);
1327 }
1328 > strings.ts
1329 > public containsAmbiguousCharacter(str: string): boolean {
1330 for (let i = 0; i < str.length; i++) {
1331 const codePoint = str.codePointAt(i);
1336 return false;
1337 }
1338 > strings.ts
1339 > /**
1340 > * Returns the non basic ASCII code point that the given code point can be confused,
1341 > * or undefined if such code point does note exist.
1342 > */
1343 > public getPrimaryConfusable(codePoint: number): number | undefined {
1344 return this.confusableDictionary.get(codePoint);
1345 }
1346 > strings.ts
1347 > public getConfusableCodePoints(): ReadonlySet<number> {
1348 return new Set(this.confusableDictionary.keys());
1349 }
1350 > } strings.ts
1351 >
1352 > export class InvisibleCharacters {
1353 > private static getRawData(): Record<string | '_common', number[]> {
1354 // Generated using https://github.com/hediet/vscode-unicode-data
1355 return JSON.parse('{\"_common\":[11,12,13,127,847,1564,4447,4448,6068,6069,6155,6156,6157,6158,7355,7356,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8204,8205,8206,8207,8234,8235,8236,8237,8238,8239,8287,8288,8289,8290,8291,8292,8293,8294,8295,8296,8297,8298,8299,8300,8301,8302,8303,10240,12644,65024,65025,65026,65027,65028,65029,65030,65031,65032,65033,65034,65035,65036,65037,65038,65039,65279,65440,65520,65521,65522,65523,65524,65525,65526,65527,65528,65532,78844,119155,119156,119157,119158,119159,119160,119161,119162,917504,917505,917506,917507,917508,917509,917510,917511,917512,917513,917514,917515,917516,917517,917518,917519,917520,917521,917522,917523,917524,917525,917526,917527,917528,917529,917530,917531,917532,917533,917534,917535,917536,917537,917538,917539,917540,917541,917542,917543,917544,917545,917546,917547,917548,917549,917550,917551,917552,917553,917554,917555,917556,917557,917558,917559,917560,917561,917562,917563,917564,917565,917566,917567,917568,917569,917570,917571,917572,917573,917574,917575,917576,917577,917578,917579,917580,917581,917582,917583,917584,917585,917586,917587,917588,917589,917590,917591,917592,917593,917594,917595,917596,917597,917598,917599,917600,917601,917602,917603,917604,917605,917606,917607,917608,917609,917610,917611,917612,917613,917614,917615,917616,917617,917618,917619,917620,917621,917622,917623,917624,917625,917626,917627,917628,917629,917630,917631,917760,917761,917762,917763,917764,917765,917766,917767,917768,917769,917770,917771,917772,917773,917774,917775,917776,917777,917778,917779,917780,917781,917782,917783,917784,917785,917786,917787,917788,917789,917790,917791,917792,917793,917794,917795,917796,917797,917798,917799,917800,917801,917802,917803,917804,917805,917806,917807,917808,917809,917810,917811,917812,917813,917814,917815,917816,917817,917818,917819,917820,917821,917822,917823,917824,917825,917826,917827,917828,917829,917830,917831,917832,917833,917834,917835,917836,917837,917838,917839,917840,917841,917842,917843,917844,917845,917846,917847,917848,917849,917850,917851,917852,917853,917854,917855,917856,917857,917858,917859,917860,917861,917862,917863,917864,917865,917866,917867,917868,917869,917870,917871,917872,917873,917874,917875,917876,917877,917878,917879,917880,917881,917882,917883,917884,917885,917886,917887,917888,917889,917890,917891,917892,917893,917894,917895,917896,917897,917898,917899,917900,917901,917902,917903,917904,917905,917906,917907,917908,917909,917910,917911,917912,917913,917914,917915,917916,917917,917918,917919,917920,917921,917922,917923,917924,917925,917926,917927,917928,917929,917930,917931,917932,917933,917934,917935,917936,917937,917938,917939,917940,917941,917942,917943,917944,917945,917946,917947,917948,917949,917950,917951,917952,917953,917954,917955,917956,917957,917958,917959,917960,917961,917962,917963,917964,917965,917966,917967,917968,917969,917970,917971,917972,917973,917974,917975,917976,917977,917978,917979,917980,917981,917982,917983,917984,917985,917986,917987,917988,917989,917990,917991,917992,917993,917994,917995,917996,917997,917998,917999],\"cs\":[173,8203,12288],\"de\":[173,8203,12288],\"es\":[8203,12288],\"fr\":[173,8203,12288],\"it\":[160,173,12288],\"ja\":[173],\"ko\":[173,12288],\"pl\":[173,8203,12288],\"pt-BR\":[173,8203,12288],\"qps-ploc\":[160,173,8203,12288],\"ru\":[173,12288],\"tr\":[160,173,8203,12288],\"zh-hans\":[160,173,8203,12288],\"zh-hant\":[173,12288]}');
1356 }
1357 > strings.ts
1358 > private static _data: Set<number> | undefined = undefined;
1359 >
1360 > private static getData() {
1361 if (!this._data) {
1362 this._data = new Set([...Object.values(InvisibleCharacters.getRawData())].flat());
1364 return this._data;
1365 }
1366 > strings.ts
1367 > public static isInvisibleCharacter(codePoint: number): boolean {
1368 return InvisibleCharacters.getData().has(codePoint);
1369 }
1370 > strings.ts
1371 > public static containsInvisibleCharacter(str: string): boolean {
1372 for (let i = 0; i < str.length; i++) {
1373 const codePoint = str.codePointAt(i);
1378 return false;
1379 }
1380 > strings.ts
1381 > public static get codePoints(): ReadonlySet<number> {
1382 return InvisibleCharacters.getData();
1383 }
1384 > } strings.ts
1385 >
1386 > export const Ellipsis = '\u2026';
1387 >
1388 > /**
1389 > * Convert a Unicode string to a string in which each 16-bit unit occupies only one byte
1390 > *
1391 > * From https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa
1392 > */
1393 function toBinary(str: string): string {
1394 const codeUnits = new Uint16Array(str.length);
1403 return binary;
1404 }
1405 > strings.ts
1406 > /**
1407 > * Version of the global `btoa` function that handles multi-byte characters instead
1408 > * of throwing an exception.
1409 > */
1410 >
1411 > export function multibyteAwareBtoa(str: string): string {
1412 return btoa(toBinary(str));
1413 }
src/vs/base/common/charCode.ts 450 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- charCode.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
7 >
8 > /**
9 > * An inlined enum containing useful character codes (to be used with String.charCodeAt).
10 > * Please leave the const keyword such that it gets inlined when compiled to JavaScript!
11 > */
12 > export const enum CharCode {
13 > Null = 0,
14 > /**
15 > * The `\b` character.
16 > */
17 > Backspace = 8,
18 > /**
19 > * The `\t` character.
20 > */
21 > Tab = 9,
22 > /**
23 > * The `\n` character.
24 > */
25 > LineFeed = 10,
26 > /**
27 > * The `\r` character.
28 > */
29 > CarriageReturn = 13,
30 > Space = 32,
31 > /**
32 > * The `!` character.
33 > */
34 > ExclamationMark = 33,
35 > /**
36 > * The `"` character.
37 > */
38 > DoubleQuote = 34,
39 > /**
40 > * The `#` character.
41 > */
42 > Hash = 35,
43 > /**
44 > * The `$` character.
45 > */
46 > DollarSign = 36,
47 > /**
48 > * The `%` character.
49 > */
50 > PercentSign = 37,
51 > /**
52 > * The `&` character.
53 > */
54 > Ampersand = 38,
55 > /**
56 > * The `'` character.
57 > */
58 > SingleQuote = 39,
59 > /**
60 > * The `(` character.
61 > */
62 > OpenParen = 40,
63 > /**
64 > * The `)` character.
65 > */
66 > CloseParen = 41,
67 > /**
68 > * The `*` character.
69 > */
70 > Asterisk = 42,
71 > /**
72 > * The `+` character.
73 > */
74 > Plus = 43,
75 > /**
76 > * The `,` character.
77 > */
78 > Comma = 44,
79 > /**
80 > * The `-` character.
81 > */
82 > Dash = 45,
83 > /**
84 > * The `.` character.
85 > */
86 > Period = 46,
87 > /**
88 > * The `/` character.
89 > */
90 > Slash = 47,
91 >
92 > Digit0 = 48,
93 > Digit1 = 49,
94 > Digit2 = 50,
95 > Digit3 = 51,
96 > Digit4 = 52,
97 > Digit5 = 53,
98 > Digit6 = 54,
99 > Digit7 = 55,
100 > Digit8 = 56,
101 > Digit9 = 57,
102 >
103 > /**
104 > * The `:` character.
105 > */
106 > Colon = 58,
107 > /**
108 > * The `;` character.
109 > */
110 > Semicolon = 59,
111 > /**
112 > * The `<` character.
113 > */
114 > LessThan = 60,
115 > /**
116 > * The `=` character.
117 > */
118 > Equals = 61,
119 > /**
120 > * The `>` character.
121 > */
122 > GreaterThan = 62,
123 > /**
124 > * The `?` character.
125 > */
126 > QuestionMark = 63,
127 > /**
128 > * The `@` character.
129 > */
130 > AtSign = 64,
131 >
132 > A = 65,
133 > B = 66,
134 > C = 67,
135 > D = 68,
136 > E = 69,
137 > F = 70,
138 > G = 71,
139 > H = 72,
140 > I = 73,
141 > J = 74,
142 > K = 75,
143 > L = 76,
144 > M = 77,
145 > N = 78,
146 > O = 79,
147 > P = 80,
148 > Q = 81,
149 > R = 82,
150 > S = 83,
151 > T = 84,
152 > U = 85,
153 > V = 86,
154 > W = 87,
155 > X = 88,
156 > Y = 89,
157 > Z = 90,
158 >
159 > /**
160 > * The `[` character.
161 > */
162 > OpenSquareBracket = 91,
163 > /**
164 > * The `\` character.
165 > */
166 > Backslash = 92,
167 > /**
168 > * The `]` character.
169 > */
170 > CloseSquareBracket = 93,
171 > /**
172 > * The `^` character.
173 > */
174 > Caret = 94,
175 > /**
176 > * The `_` character.
177 > */
178 > Underline = 95,
179 > /**
180 > * The ``(`)`` character.
181 > */
182 > BackTick = 96,
183 >
184 > a = 97,
185 > b = 98,
186 > c = 99,
187 > d = 100,
188 > e = 101,
189 > f = 102,
190 > g = 103,
191 > h = 104,
192 > i = 105,
193 > j = 106,
194 > k = 107,
195 > l = 108,
196 > m = 109,
197 > n = 110,
198 > o = 111,
199 > p = 112,
200 > q = 113,
201 > r = 114,
202 > s = 115,
203 > t = 116,
204 > u = 117,
205 > v = 118,
206 > w = 119,
207 > x = 120,
208 > y = 121,
209 > z = 122,
210 >
211 > /**
212 > * The `{` character.
213 > */
214 > OpenCurlyBrace = 123,
215 > /**
216 > * The `|` character.
217 > */
218 > Pipe = 124,
219 > /**
220 > * The `}` character.
221 > */
222 > CloseCurlyBrace = 125,
223 > /**
224 > * The `~` character.
225 > */
226 > Tilde = 126,
227 >
228 > /**
229 > * The &nbsp; (no-break space) character.
230 > * Unicode Character 'NO-BREAK SPACE' (U+00A0)
231 > */
232 > NoBreakSpace = 160,
233 >
234 > U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent
235 > U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent
236 > U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent
237 > U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde
238 > U_Combining_Macron = 0x0304, // U+0304 Combining Macron
239 > U_Combining_Overline = 0x0305, // U+0305 Combining Overline
240 > U_Combining_Breve = 0x0306, // U+0306 Combining Breve
241 > U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above
242 > U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis
243 > U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above
244 > U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above
245 > U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent
246 > U_Combining_Caron = 0x030C, // U+030C Combining Caron
247 > U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above
248 > U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above
249 > U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent
250 > U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu
251 > U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve
252 > U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above
253 > U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above
254 > U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above
255 > U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right
256 > U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below
257 > U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below
258 > U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below
259 > U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below
260 > U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above
261 > U_Combining_Horn = 0x031B, // U+031B Combining Horn
262 > U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below
263 > U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below
264 > U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below
265 > U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below
266 > U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below
267 > U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below
268 > U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below
269 > U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below
270 > U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below
271 > U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below
272 > U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below
273 > U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla
274 > U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek
275 > U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below
276 > U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below
277 > U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below
278 > U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below
279 > U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below
280 > U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below
281 > U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below
282 > U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below
283 > U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below
284 > U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line
285 > U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line
286 > U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay
287 > U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay
288 > U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay
289 > U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay
290 > U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay
291 > U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below
292 > U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below
293 > U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below
294 > U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below
295 > U_Combining_X_Above = 0x033D, // U+033D Combining X Above
296 > U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde
297 > U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline
298 > U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark
299 > U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark
300 > U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni
301 > U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis
302 > U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos
303 > U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni
304 > U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above
305 > U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below
306 > U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below
307 > U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below
308 > U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above
309 > U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above
310 > U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above
311 > U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below
312 > U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below
313 > U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner
314 > U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above
315 > U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above
316 > U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata
317 > U_Combining_X_Below = 0x0353, // U+0353 Combining X Below
318 > U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below
319 > U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below
320 > U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below
321 > U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above
322 > U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right
323 > U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below
324 > U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below
325 > U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above
326 > U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below
327 > U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve
328 > U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron
329 > U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below
330 > U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde
331 > U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve
332 > U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below
333 > U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A
334 > U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E
335 > U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I
336 > U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O
337 > U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U
338 > U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C
339 > U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D
340 > U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H
341 > U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M
342 > U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R
343 > U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T
344 > U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V
345 > U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X
346 >
347 > /**
348 > * Unicode Character 'LINE SEPARATOR' (U+2028)
349 > * http://www.fileformat.info/info/unicode/char/2028/index.htm
350 > */
351 > LINE_SEPARATOR = 0x2028,
352 > /**
353 > * Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
354 > * http://www.fileformat.info/info/unicode/char/2029/index.htm
355 > */
356 > PARAGRAPH_SEPARATOR = 0x2029,
357 > /**
358 > * Unicode Character 'NEXT LINE' (U+0085)
359 > * http://www.fileformat.info/info/unicode/char/0085/index.htm
360 > */
361 > NEXT_LINE = 0x0085,
362 >
363 > // http://www.fileformat.info/info/unicode/category/Sk/list.htm
364 > U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
365 > U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT
366 > U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS
367 > U_MACRON = 0x00AF, // U+00AF MACRON
368 > U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT
369 > U_CEDILLA = 0x00B8, // U+00B8 CEDILLA
370 > U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD
371 > U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD
372 > U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD
373 > U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD
374 > U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING
375 > U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING
376 > U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK
377 > U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK
378 > U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN
379 > U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN
380 > U_BREVE = 0x02D8, // U+02D8 BREVE
381 > U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE
382 > U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE
383 > U_OGONEK = 0x02DB, // U+02DB OGONEK
384 > U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE
385 > U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT
386 > U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK
387 > U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT
388 > U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR
389 > U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR
390 > U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR
391 > U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR
392 > U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR
393 > U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK
394 > U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK
395 > U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED
396 > U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD
397 > U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD
398 > U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD
399 > U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD
400 > U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING
401 > U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT
402 > U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
403 > U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
404 > U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE
405 > U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON
406 > U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE
407 > U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE
408 > U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE
409 > U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE
410 > U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF
411 > U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF
412 > U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW
413 > U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN
414 > U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS
415 > U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS
416 > U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS
417 > U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI
418 > U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI
419 > U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI
420 > U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA
421 > U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA
422 > U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI
423 > U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA
424 > U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA
425 > U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI
426 > U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA
427 > U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA
428 > U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA
429 > U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA
430 > U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA
431 >
432 > U_IDEOGRAPHIC_FULL_STOP = 0x3002, // U+3002 IDEOGRAPHIC FULL STOP
433 > U_LEFT_CORNER_BRACKET = 0x300C, // U+300C LEFT CORNER BRACKET
434 > U_RIGHT_CORNER_BRACKET = 0x300D, // U+300D RIGHT CORNER BRACKET
435 > U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010, // U+3010 LEFT BLACK LENTICULAR BRACKET
436 > U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011, // U+3011 RIGHT BLACK LENTICULAR BRACKET
437 >
438 >
439 > U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
440 >
441 > /**
442 > * UTF-8 BOM
443 > * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
444 > * http://www.fileformat.info/info/unicode/char/feff/index.htm
445 > */
446 > UTF8_BOM = 65279,
447 >
448 > U_FULLWIDTH_SEMICOLON = 0xFF1B, // U+FF1B FULLWIDTH SEMICOLON
449 > U_FULLWIDTH_COMMA = 0xFF0C, // U+FF0C FULLWIDTH COMMA
450 > }
src/vs/platform/log/common/log.ts 447 covered LOC · 73 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- log.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as nls from '../../../nls.js';
7 > import { toErrorMessage } from '../../../base/common/errorMessage.js';
8 > import { Emitter, Event } from '../../../base/common/event.js';
9 > import { hash } from '../../../base/common/hash.js';
10 > import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
11 > import { ResourceMap } from '../../../base/common/map.js';
12 > import { isWindows } from '../../../base/common/platform.js';
13 > import { joinPath } from '../../../base/common/resources.js';
14 > import { Mutable, isNumber, isString } from '../../../base/common/types.js';
15 > import { URI } from '../../../base/common/uri.js';
16 > import { ILocalizedString } from '../../action/common/action.js';
17 > import { RawContextKey } from '../../contextkey/common/contextkey.js';
18 > import { IEnvironmentService } from '../../environment/common/environment.js';
19 > import { createDecorator } from '../../instantiation/common/instantiation.js';
20 >
21 > export const ILogService = createDecorator<ILogService>('logService');
22 > export const ILoggerService = createDecorator<ILoggerService>('loggerService');
23 >
24 function now(): string {
25 return new Date().toISOString();
26 }
27 > log.ts
28 > export function isLogLevel(thing: unknown): thing is LogLevel {
29 return isNumber(thing);
30 }
31 > log.ts
32 > export enum LogLevel {
33 > Off,
34 > Trace,
35 > Debug,
36 > Info,
37 > Warning,
38 > Error
39 > }
40 >
41 > export const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info;
42 >
43 > export interface ILogger extends IDisposable {
44 > readonly onDidChangeLogLevel: Event<LogLevel>;
45 > getLevel(): LogLevel;
46 > setLevel(level: LogLevel): void;
47 >
48 > trace(message: string, ...args: unknown[]): void;
49 > debug(message: string, ...args: unknown[]): void;
50 > info(message: string, ...args: unknown[]): void;
51 > warn(message: string, ...args: unknown[]): void;
52 > error(message: string | Error, ...args: unknown[]): void;
53 >
54 > /**
55 > * An operation to flush the contents. Can be synchronous.
56 > */
57 > flush(): void;
58 > }
59 >
60 > export function canLog(loggerLevel: LogLevel, messageLevel: LogLevel): boolean {
61 return loggerLevel !== LogLevel.Off && loggerLevel <= messageLevel;
62 }
63 > log.ts
64 > export function log(logger: ILogger, level: LogLevel, message: string): void {
65 switch (level) {
66 case LogLevel.Trace: logger.trace(message); break;
73 }
74 }
75 > log.ts
76 > type ConsoleMethod = 'debug' | 'error' | 'info' | 'log' | 'warn';
77 > type ConsoleMethodFn = (...args: unknown[]) => void;
78 >
79 > /**
80 > * Flag to enable forwarding of console.* calls to the log service in development.
81 > * This is intended for the use of agents to quickly instrument the code with console.logs
82 > * which will end up in the log service's file outputs.
83 > */
84 > export const isDevConsoleLogForwardingEnabled = false
85 > // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this
86 > ;
87 >
88 > let isConsoleForwarding = false;
89 > let isLogServiceConsoleEcho = false;
90 >
91 function getConsoleMethod(method: ConsoleMethod): ConsoleMethodFn {
92 switch (method) {
98 }
99 }
100 > log.ts
101 function setConsoleMethod(method: ConsoleMethod, fn: ConsoleMethodFn): void {
102 switch (method) {
108 }
109 }
110 > log.ts
111 function logToConsole(method: ConsoleMethod, ...args: unknown[]): void {
112 if (isConsoleForwarding) {
120 }
121 }
122 > log.ts
123 > export function registerDevConsoleLogForwarder(logService: ILogService): IDisposable {
124 const originalConsoleMethods: Record<ConsoleMethod, ConsoleMethodFn> = {
125 debug: console.debug,
177 });
178 }
179 > log.ts
180 > export function format(args: any, verbose: boolean = false): string {
181 let result = '';
182
199 return result;
200 }
201 > log.ts
202 > export type LoggerGroup = {
203 > readonly id: string;
204 > readonly name: string;
205 > };
206 >
207 > export interface ILogService extends ILogger {
208 > readonly _serviceBrand: undefined;
209 > }
210 >
211 > export interface ILoggerOptions {
212 >
213 > /**
214 > * Id of the logger.
215 > */
216 > id?: string;
217 >
218 > /**
219 > * Name of the logger.
220 > */
221 > name?: string;
222 >
223 > /**
224 > * Do not create rotating files if max size exceeds.
225 > */
226 > donotRotate?: boolean;
227 >
228 > /**
229 > * Do not use formatters.
230 > */
231 > donotUseFormatters?: boolean;
232 >
233 > /**
234 > * When to log. Set to `always` to log always.
235 > */
236 > logLevel?: 'always' | LogLevel;
237 >
238 > /**
239 > * Whether the log should be hidden from the user.
240 > */
241 > hidden?: boolean;
242 >
243 > /**
244 > * Condition which must be true to show this logger
245 > */
246 > when?: string;
247 >
248 > /**
249 > * Id of the extension that created this logger.
250 > */
251 > extensionId?: string;
252 >
253 > /**
254 > * Group of the logger.
255 > */
256 > group?: LoggerGroup;
257 > }
258 >
259 > export interface ILoggerResource {
260 > readonly resource: URI;
261 > readonly id: string;
262 > readonly name?: string;
263 > readonly logLevel?: LogLevel;
264 > readonly hidden?: boolean;
265 > readonly when?: string;
266 > readonly extensionId?: string;
267 > readonly group?: LoggerGroup;
268 > }
269 >
270 > export type DidChangeLoggersEvent = {
271 > readonly added: Iterable<ILoggerResource>;
272 > readonly removed: Iterable<ILoggerResource>;
273 > };
274 >
275 > export interface ILoggerService {
276 >
277 > readonly _serviceBrand: undefined;
278 >
279 > /**
280 > * Creates a logger for the given resource, or gets one if it already exists.
281 > *
282 > * This will also register the logger with the logger service.
283 > */
284 > createLogger(resource: URI, options?: ILoggerOptions): ILogger;
285 >
286 > /**
287 > * Creates a logger with the given id in the logs folder, or gets one if it already exists.
288 > *
289 > * This will also register the logger with the logger service.
290 > */
291 > createLogger(id: string, options?: Omit<ILoggerOptions, 'id'>): ILogger;
292 >
293 > /**
294 > * Gets an existing logger, if any.
295 > */
296 > getLogger(resourceOrId: URI | string): ILogger | undefined;
297 >
298 > /**
299 > * An event which fires when the log level of a logger has changed
300 > */
301 > readonly onDidChangeLogLevel: Event<LogLevel | [URI, LogLevel]>;
302 >
303 > /**
304 > * Set default log level.
305 > */
306 > setLogLevel(level: LogLevel): void;
307 >
308 > /**
309 > * Set log level for a logger.
310 > */
311 > setLogLevel(resource: URI, level: LogLevel): void;
312 >
313 > /**
314 > * Get log level for a logger or the default log level.
315 > */
316 > getLogLevel(resource?: URI): LogLevel;
317 >
318 > /**
319 > * An event which fires when the visibility of a logger has changed
320 > */
321 > readonly onDidChangeVisibility: Event<[URI, boolean]>;
322 >
323 > /**
324 > * Set the visibility of a logger.
325 > */
326 > setVisibility(resourceOrId: URI | string, visible: boolean): void;
327 >
328 > /**
329 > * An event which fires when the logger resources are changed
330 > */
331 > readonly onDidChangeLoggers: Event<DidChangeLoggersEvent>;
332 >
333 > /**
334 > * Register a logger with the logger service.
335 > *
336 > * Note that this will not create a logger, but only register it.
337 > *
338 > * Use `createLogger` to create a logger and register it.
339 > *
340 > * Use it when you want to register a logger that is not created by the logger service.
341 > */
342 > registerLogger(resource: ILoggerResource): void;
343 >
344 > /**
345 > * Deregister the logger for the given resource.
346 > */
347 > deregisterLogger(idOrResource: URI | string): void;
348 >
349 > /**
350 > * Get all registered loggers
351 > */
352 > getRegisteredLoggers(): Iterable<ILoggerResource>;
353 >
354 > /**
355 > * Get the registered logger for the given resource.
356 > */
357 > getRegisteredLogger(resource: URI): ILoggerResource | undefined;
358 > }
359 >
360 > export abstract class AbstractLogger extends Disposable implements ILogger {
361
362 private level: LogLevel = DEFAULT_LOG_LEVEL;
363 private readonly _onDidChangeLogLevel: Emitter<LogLevel> = this._register(new Emitter<LogLevel>());
364 > get onDidChangeLogLevel(): Event<LogLevel> { return this._onDidChangeLogLevel.event; } log.ts
365 >
366 > setLevel(level: LogLevel): void {
367 if (this.level !== level) {
368 this.level = level;
370 }
371 }
372 > log.ts
373 > getLevel(): LogLevel {
374 return this.level;
375 }
376 > log.ts
377 > protected checkLogLevel(level: LogLevel): boolean {
378 return canLog(this.level, level);
379 }
380 > log.ts
381 > protected canLog(level: LogLevel): boolean {
382 if (this._store.isDisposed) {
383 return false;
385 return this.checkLogLevel(level);
386 }
387 > log.ts
388 > abstract trace(message: string, ...args: unknown[]): void;
389 > abstract debug(message: string, ...args: unknown[]): void;
390 > abstract info(message: string, ...args: unknown[]): void;
391 > abstract warn(message: string, ...args: unknown[]): void;
392 > abstract error(message: string | Error, ...args: unknown[]): void;
393 > abstract flush(): void;
394 > }
395 >
396 > export abstract class AbstractMessageLogger extends AbstractLogger implements ILogger {
397 >
398 > constructor(private readonly logAlways?: boolean) {
399 super();
400 }
401 > log.ts
402 > protected override checkLogLevel(level: LogLevel): boolean {
403 return this.logAlways || super.checkLogLevel(level);
404 }
405 > log.ts
406 > trace(message: string, ...args: unknown[]): void {
407 if (this.canLog(LogLevel.Trace)) {
408 this.log(LogLevel.Trace, format([message, ...args], true));
409 }
410 }
411 > log.ts
412 > debug(message: string, ...args: unknown[]): void {
413 if (this.canLog(LogLevel.Debug)) {
414 this.log(LogLevel.Debug, format([message, ...args]));
415 }
416 }
417 > log.ts
418 > info(message: string, ...args: unknown[]): void {
419 if (this.canLog(LogLevel.Info)) {
420 this.log(LogLevel.Info, format([message, ...args]));
421 }
422 }
423 > log.ts
424 > warn(message: string, ...args: unknown[]): void {
425 if (this.canLog(LogLevel.Warning)) {
426 this.log(LogLevel.Warning, format([message, ...args]));
427 }
428 }
429 > log.ts
430 > error(message: string | Error, ...args: unknown[]): void {
431 if (this.canLog(LogLevel.Error)) {
432 if (message instanceof Error) {
439 }
440 }
441 > log.ts
442 > flush(): void { }
443 >
444 > protected abstract log(level: LogLevel, message: string): void;
445 > }
446 >
447 >
448 > export class ConsoleMainLogger extends AbstractLogger implements ILogger {
449 >
450 > private useColors: boolean;
451 >
452 > constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
453 super();
454 this.setLevel(logLevel);
455 this.useColors = !isWindows;
456 }
457 > log.ts
458 > trace(message: string, ...args: unknown[]): void {
459 if (this.canLog(LogLevel.Trace)) {
460 if (this.useColors) {
465 }
466 }
467 > log.ts
468 > debug(message: string, ...args: unknown[]): void {
469 if (this.canLog(LogLevel.Debug)) {
470 if (this.useColors) {
475 }
476 }
477 > log.ts
478 > info(message: string, ...args: unknown[]): void {
479 if (this.canLog(LogLevel.Info)) {
480 if (this.useColors) {
485 }
486 }
487 > log.ts
488 > warn(message: string | Error, ...args: unknown[]): void {
489 if (this.canLog(LogLevel.Warning)) {
490 if (this.useColors) {
495 }
496 }
497 > log.ts
498 > error(message: string, ...args: unknown[]): void {
499 if (this.canLog(LogLevel.Error)) {
500 if (this.useColors) {
505 }
506 }
507 > log.ts
508 > flush(): void {
509 // noop
510 }
511 > log.ts
512 > }
513 >
514 > export class ConsoleLogger extends AbstractLogger implements ILogger {
515 >
516 > constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL, private readonly useColors: boolean = true) {
517 super();
518 this.setLevel(logLevel);
519 }
520 > log.ts
521 > trace(message: string, ...args: unknown[]): void {
522 if (this.canLog(LogLevel.Trace)) {
523 if (this.useColors) {
528 }
529 }
530 > log.ts
531 > debug(message: string, ...args: unknown[]): void {
532 if (this.canLog(LogLevel.Debug)) {
533 if (this.useColors) {
538 }
539 }
540 > log.ts
541 > info(message: string, ...args: unknown[]): void {
542 if (this.canLog(LogLevel.Info)) {
543 if (this.useColors) {
548 }
549 }
550 > log.ts
551 > warn(message: string | Error, ...args: unknown[]): void {
552 if (this.canLog(LogLevel.Warning)) {
553 if (this.useColors) {
558 }
559 }
560 > log.ts
561 > error(message: string, ...args: unknown[]): void {
562 if (this.canLog(LogLevel.Error)) {
563 if (this.useColors) {
568 }
569 }
570 > log.ts
571 >
572 > flush(): void {
573 // noop
574 }
575 > } log.ts
576 >
577 > export class AdapterLogger extends AbstractLogger implements ILogger {
578 >
579 > constructor(private readonly adapter: { log: (logLevel: LogLevel, args: any[]) => void }, logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
580 super();
581 this.setLevel(logLevel);
582 }
583 > log.ts
584 > trace(message: string, ...args: unknown[]): void {
585 if (this.canLog(LogLevel.Trace)) {
586 this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]);
587 }
588 }
589 > log.ts
590 > debug(message: string, ...args: unknown[]): void {
591 if (this.canLog(LogLevel.Debug)) {
592 this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]);
593 }
594 }
595 > log.ts
596 > info(message: string, ...args: unknown[]): void {
597 if (this.canLog(LogLevel.Info)) {
598 this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]);
599 }
600 }
601 > log.ts
602 > warn(message: string | Error, ...args: unknown[]): void {
603 if (this.canLog(LogLevel.Warning)) {
604 this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]);
605 }
606 }
607 > log.ts
608 > error(message: string | Error, ...args: unknown[]): void {
609 if (this.canLog(LogLevel.Error)) {
610 this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]);
611 }
612 }
613 > log.ts
614 > private extractMessage(msg: string | Error): string {
615 if (typeof msg === 'string') {
616 return msg;
619 return toErrorMessage(msg, this.canLog(LogLevel.Trace));
620 }
621 > log.ts
622 > flush(): void {
623 // noop
624 }
625 > } log.ts
626 >
627 > export class MultiplexLogger extends AbstractLogger implements ILogger {
628 >
629 > constructor(private readonly loggers: ReadonlyArray<ILogger>) {
630 super();
631 if (loggers.length) {
633 }
634 }
635 > log.ts
636 > override setLevel(level: LogLevel): void {
637 for (const logger of this.loggers) {
638 logger.setLevel(level);
640 super.setLevel(level);
641 }
642 > log.ts
643 > trace(message: string, ...args: unknown[]): void {
644 for (const logger of this.loggers) {
645 logger.trace(message, ...args);
646 }
647 }
648 > log.ts
649 > debug(message: string, ...args: unknown[]): void {
650 for (const logger of this.loggers) {
651 logger.debug(message, ...args);
652 }
653 }
654 > log.ts
655 > info(message: string, ...args: unknown[]): void {
656 for (const logger of this.loggers) {
657 logger.info(message, ...args);
658 }
659 }
660 > log.ts
661 > warn(message: string, ...args: unknown[]): void {
662 for (const logger of this.loggers) {
663 logger.warn(message, ...args);
664 }
665 }
666 > log.ts
667 > error(message: string | Error, ...args: unknown[]): void {
668 for (const logger of this.loggers) {
669 logger.error(message, ...args);
670 }
671 }
672 > log.ts
673 > flush(): void {
674 for (const logger of this.loggers) {
675 logger.flush();
676 }
677 }
678 > log.ts
679 > override dispose(): void {
680 for (const logger of this.loggers) {
681 logger.dispose();
683 super.dispose();
684 }
685 > } log.ts
686 >
687 > type LoggerEntry = { logger: ILogger | undefined; info: Mutable<ILoggerResource> };
688 >
689 > export abstract class AbstractLoggerService extends Disposable implements ILoggerService {
690 >
691 > declare readonly _serviceBrand: undefined;
692 >
693 > private readonly _loggers = new ResourceMap<LoggerEntry>();
694 >
695 > private _onDidChangeLoggers = this._register(new Emitter<{ added: ILoggerResource[]; removed: ILoggerResource[] }>);
696 > readonly onDidChangeLoggers = this._onDidChangeLoggers.event;
697 >
698 > private _onDidChangeLogLevel = this._register(new Emitter<LogLevel | [URI, LogLevel]>);
699 > readonly onDidChangeLogLevel = this._onDidChangeLogLevel.event;
700 >
701 > private _onDidChangeVisibility = this._register(new Emitter<[URI, boolean]>);
702 > readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
703 >
704 > constructor(
705 protected logLevel: LogLevel,
706 private readonly logsHome: URI,
714 }
715 }
716 > log.ts
717 > private getLoggerEntry(resourceOrId: URI | string): LoggerEntry | undefined {
718 if (isString(resourceOrId)) {
719 return [...this._loggers.values()].find(logger => logger.info.id === resourceOrId);
721 return this._loggers.get(resourceOrId);
722 }
723 > log.ts
724 > getLogger(resourceOrId: URI | string): ILogger | undefined {
725 return this.getLoggerEntry(resourceOrId)?.logger;
726 }
727 > log.ts
728 > createLogger(idOrResource: URI | string, options?: ILoggerOptions): ILogger {
729 const resource = this.toResource(idOrResource);
730 const id = isString(idOrResource) ? idOrResource : (options?.id ?? hash(resource.toString()).toString(16));
752 return logger;
753 }
754 > log.ts
755 > protected toResource(idOrResource: string | URI): URI {
756 return isString(idOrResource) ? joinPath(this.logsHome, `${idOrResource.replace(/[\\/:\*\?"<>\|]/g, '')}.log`) : idOrResource;
757 }
758 > log.ts
759 > setLogLevel(logLevel: LogLevel): void;
760 > setLogLevel(resource: URI, logLevel: LogLevel): void;
761 > setLogLevel(arg1: any, arg2?: any): void {
762 if (URI.isUri(arg1)) {
763 const resource = arg1;
780 }
781 }
782 > log.ts
783 > setVisibility(resourceOrId: URI | string, visibility: boolean): void {
784 const logger = this.getLoggerEntry(resourceOrId);
785 if (logger && visibility !== !logger.info.hidden) {
789 }
790 }
791 > log.ts
792 > getLogLevel(resource?: URI): LogLevel {
793 let logLevel;
794 if (resource) {
797 return logLevel ?? this.logLevel;
798 }
799 > log.ts
800 > registerLogger(resource: ILoggerResource): void {
801 const existing = this._loggers.get(resource.resource);
802 if (existing) {
809 }
810 }
811 > log.ts
812 > deregisterLogger(idOrResource: URI | string): void {
813 const resource = this.toResource(idOrResource);
814 const existing = this._loggers.get(resource);
821 }
822 }
823 > log.ts
824 > *getRegisteredLoggers(): Iterable<ILoggerResource> {
825 for (const entry of this._loggers.values()) {
826 yield entry.info;
827 }
828 }
829 > log.ts
830 > getRegisteredLogger(resource: URI): ILoggerResource | undefined {
831 return this._loggers.get(resource)?.info;
832 }
833 > log.ts
834 > override dispose(): void {
835 this._loggers.forEach(logger => logger.logger?.dispose());
836 this._loggers.clear();
837 super.dispose();
838 }
839 > log.ts
840 > protected abstract doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger;
841 > }
842 >
843 > export class NullLogger implements ILogger {
844 > readonly onDidChangeLogLevel: Event<LogLevel> = new Emitter<LogLevel>().event; log.ts
845 > setLevel(level: LogLevel): void { } log.ts
846 > getLevel(): LogLevel { return LogLevel.Info; }
847 > trace(message: string, ...args: unknown[]): void { }
848 > debug(message: string, ...args: unknown[]): void { }
849 > info(message: string, ...args: unknown[]): void { }
850 > warn(message: string, ...args: unknown[]): void { }
851 > error(message: string | Error, ...args: unknown[]): void { }
852 > critical(message: string | Error, ...args: unknown[]): void { }
853 > dispose(): void { }
854 > flush(): void { }
855 > }
856 >
857 > export class NullLogService extends NullLogger implements ILogService {
858 > declare readonly _serviceBrand: undefined;
859 > }
860 >
861 > export class NullLoggerService extends AbstractLoggerService {
862 > constructor() {
863 super(LogLevel.Off, URI.parse('log:///log'));
864 }
865 > protected override doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { log.ts
866 return new NullLogger();
867 }
868 > } log.ts
869 >
870 > export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
871 if (environmentService.verbose) {
872 return LogLevel.Trace;
880 return DEFAULT_LOG_LEVEL;
881 }
882 > log.ts
883 > export function LogLevelToString(logLevel: LogLevel): string {
884 > switch (logLevel) {
885 > case LogLevel.Trace: return 'trace';
886 > case LogLevel.Debug: return 'debug';
887 > case LogLevel.Info: return 'info';
888 > case LogLevel.Warning: return 'warn';
889 > case LogLevel.Error: return 'error';
890 > case LogLevel.Off: return 'off';
891 > }
892 > }
893 >
894 > export function LogLevelToLocalizedString(logLevel: LogLevel): ILocalizedString {
895 switch (logLevel) {
896 case LogLevel.Trace: return { original: 'Trace', value: nls.localize('trace', "Trace") };
902 }
903 }
904 > log.ts
905 > export function parseLogLevel(logLevel: string): LogLevel | undefined {
906 switch (logLevel) {
907 case 'trace':
922 return undefined;
923 }
924 > log.ts
925 > // Contexts
926 > export const CONTEXT_LOG_LEVEL = new RawContextKey<string>('logLevel', LogLevelToString(LogLevel.Info));
src/vs/base/common/uri.ts 424 covered LOC · 73 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- uri.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from './charCode.js';
7 > import { MarshalledId } from './marshallingIds.js';
8 > import * as paths from './path.js';
9 > import { isWindows } from './platform.js';
10 >
11 > const _schemePattern = /^\w[\w\d+.-]*$/;
12 > const _singleSlashStart = /^\//;
13 > const _doubleSlashStart = /^\/\//;
14 >
15 > function _validateUri(ret: URI, _strict?: boolean): void { uri.ts
16 >
17 > // scheme, must be set
18 > if (!ret.scheme && _strict) {
19 throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
20 }
21 > uri.ts
22 > // scheme, https://tools.ietf.org/html/rfc3986#section-3.1
23 > // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
24 > if (ret.scheme && !_schemePattern.test(ret.scheme)) { uri.ts
25 const matches = [...ret.scheme.matchAll(/[^\w\d+.-]/gu)];
26 const detail = matches.length > 0
29 throw new Error(`[UriError]: Scheme contains illegal characters.${detail} (len:${ret.scheme.length})`);
30 }
31 > uri.ts
32 > // path, http://tools.ietf.org/html/rfc3986#section-3.3
33 > // If a URI contains an authority component, then the path component
34 > // must either be empty or begin with a slash ("/") character. If a URI
35 > // does not contain an authority component, then the path cannot begin
36 > // with two slash characters ("//").
37 > if (ret.path) {
38 > if (ret.authority) { uri.ts
39 > if (!_singleSlashStart.test(ret.path)) { uri.ts
40 throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character');
41 }
42 > } else { uri.ts
43 > if (_doubleSlashStart.test(ret.path)) { uri.ts
44 throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
45 }
46 > } uri.ts
47 > } uri.ts
48 > } uri.ts
49 > uri.ts
50 > // for a while we allowed uris *without* schemes and this is the migration
51 > // for them, e.g. an uri without scheme and without strict-mode warns and falls
52 > // back to the file-scheme. that should cause the least carnage and still be a
53 > // clear warning
54 > function _schemeFix(scheme: string, _strict: boolean): string { uri.ts
55 > if (!scheme && !_strict) {
56 return 'file';
57 }
58 > return scheme; uri.ts
59 > }
60 > uri.ts
61 > // implements a bit of https://tools.ietf.org/html/rfc3986#section-5
62 > function _referenceResolution(scheme: string, path: string): string { uri.ts
63 >
64 > // the slash-character is our 'default base' as we don't
65 > // support constructing URIs relative to other URIs. This
66 > // also means that we alter and potentially break paths.
67 > // see https://tools.ietf.org/html/rfc3986#section-5.1.4
68 > switch (scheme) {
69 > case 'https':
70 > case 'http':
71 > case 'file':
72 if (!path) {
73 path = _slash;
76 }
77 break;
78 > } uri.ts
79 > return path;
80 > }
81 > uri.ts
82 > const _empty = '';
83 > const _slash = '/';
84 > const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
85 >
86 > /**
87 > * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
88 > * This class is a simple parser which creates the basic component parts
89 > * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
90 > * and encoding.
91 > *
92 > * ```txt
93 > * foo://example.com:8042/over/there?name=ferret#nose
94 > * \_/ \______________/\_________/ \_________/ \__/
95 > * | | | | |
96 > * scheme authority path query fragment
97 > * | _____________________|__
98 > * / \ / \
99 > * urn:example:animal:ferret:nose
100 > * ```
101 > */
102 > export class URI implements UriComponents {
103 >
104 > static isUri(thing: unknown): thing is URI {
105 if (thing instanceof URI) {
106 return true;
118 && typeof (<URI>thing).toString === 'function';
119 }
120 > uri.ts
121 > /**
122 > * scheme is the 'http' part of 'http://www.example.com/some/path?query#fragment'.
123 > * The part before the first colon.
124 > */
125 > readonly scheme: string;
126 >
127 > /**
128 > * authority is the 'www.example.com' part of 'http://www.example.com/some/path?query#fragment'.
129 > * The part between the first double slashes and the next slash.
130 > */
131 > readonly authority: string;
132 >
133 > /**
134 > * path is the '/some/path' part of 'http://www.example.com/some/path?query#fragment'.
135 > */
136 > readonly path: string;
137 >
138 > /**
139 > * query is the 'query' part of 'http://www.example.com/some/path?query#fragment'.
140 > */
141 > readonly query: string;
142 >
143 > /**
144 > * fragment is the 'fragment' part of 'http://www.example.com/some/path?query#fragment'.
145 > */
146 > readonly fragment: string;
147 >
148 > /**
149 > * @internal
150 > */
151 > protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
152 >
153 > /**
154 > * @internal
155 > */
156 > protected constructor(components: UriComponents);
157 >
158 > /**
159 > * @internal
160 > */
161 > protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
162 > uri.ts
163 > if (typeof schemeOrData === 'object') {
164 this.scheme = schemeOrData.scheme || _empty;
165 this.authority = schemeOrData.authority || _empty;
170 // that creates uri components.
171 // _validateUri(this);
172 > } else { uri.ts
173 > this.scheme = _schemeFix(schemeOrData, _strict);
174 > this.authority = authority || _empty;
175 > this.path = _referenceResolution(this.scheme, path || _empty);
176 > this.query = query || _empty;
177 > this.fragment = fragment || _empty;
178 >
179 > _validateUri(this, _strict);
180 > }
181 > }
182 > uri.ts
183 > // ---- filesystem path -----------------------
184 >
185 > /**
186 > * Returns a string representing the corresponding file system path of this URI.
187 > * Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the
188 > * platform specific path separator.
189 > *
190 > * * Will *not* validate the path for invalid characters and semantics.
191 > * * Will *not* look at the scheme of this URI.
192 > * * The result shall *not* be used for display purposes but for accessing a file on disk.
193 > *
194 > *
195 > * The *difference* to `URI#path` is the use of the platform specific separator and the handling
196 > * of UNC paths. See the below sample of a file-uri with an authority (UNC path).
197 > *
198 > * ```ts
199 > const u = URI.parse('file://server/c$/folder/file.txt')
200 > u.authority === 'server'
201 > u.path === '/shares/c$/file.txt'
202 > u.fsPath === '\\server\c$\folder\file.txt'
203 > ```
204 > *
205 > * Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,
206 > * namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working
207 > * with URIs that represent files on disk (`file` scheme).
208 > */
209 > get fsPath(): string {
210 // if (this.scheme !== 'file') {
211 // console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
213 return uriToFsPath(this, false);
214 }
215 > uri.ts
216 > // ---- modify to new -------------------------
217 >
218 > with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
219
220 if (!change) {
260 return new Uri(scheme, authority, path, query, fragment);
261 }
262 > uri.ts
263 > // ---- parse & validate ------------------------
264 >
265 > /**
266 > * Creates a new URI from a string, e.g. `http://www.example.com/some/path`,
267 > * `file:///usr/home`, or `scheme:with/path`.
268 > *
269 > * @param value A string which represents an URI (see `URI#toString`).
270 > */
271 > static parse(value: string, _strict: boolean = false): URI {
272 > const match = _regexp.exec(value); uri.ts
273 > if (!match) {
274 return new Uri(_empty, _empty, _empty, _empty, _empty);
275 }
276 > return new Uri( uri.ts
277 > match[2] || _empty,
278 > percentDecode(match[4] || _empty),
279 > percentDecode(match[5] || _empty),
280 > percentDecode(match[7] || _empty),
281 > percentDecode(match[9] || _empty),
282 > _strict
283 > );
284 > }
285 > uri.ts
286 > /**
287 > * Creates a new URI from a file system path, e.g. `c:\my\files`,
288 > * `/usr/home`, or `\\server\share\some\path`.
289 > *
290 > * The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument
291 > * as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as**
292 > * `URI.parse('file://' + path)` because the path might contain characters that are
293 > * interpreted (# and ?). See the following sample:
294 > * ```ts
295 > const good = URI.file('/coding/c#/project1');
296 > good.scheme === 'file';
297 > good.path === '/coding/c#/project1';
298 > good.fragment === '';
299 > const bad = URI.parse('file://' + '/coding/c#/project1');
300 > bad.scheme === 'file';
301 > bad.path === '/coding/c'; // path is now broken
302 > bad.fragment === '/project1';
303 > ```
304 > *
305 > * @param path A file system path (see `URI#fsPath`)
306 > */
307 > static file(path: string): URI {
308
309 let authority = _empty;
331 return new Uri('file', authority, path, _empty, _empty);
332 }
333 > uri.ts
334 > /**
335 > * Creates new URI from uri components.
336 > *
337 > * Unless `strict` is `true` the scheme is defaults to be `file`. This function performs
338 > * validation and should be used for untrusted uri components retrieved from storage,
339 > * user input, command arguments etc
340 > */
341 > static from(components: UriComponents, strict?: boolean): URI {
342 > const result = new Uri( uri.ts
343 > components.scheme,
344 > components.authority,
345 > components.path,
346 > components.query,
347 > components.fragment,
348 > strict
349 > );
350 > return result;
351 > }
352 > uri.ts
353 > /**
354 > * Join a URI path with path fragments and normalizes the resulting path.
355 > *
356 > * @param uri The input URI.
357 > * @param pathFragment The path fragment to add to the URI path.
358 > * @returns The resulting URI.
359 > */
360 > static joinPath(uri: URI, ...pathFragment: string[]): URI {
361 if (!uri.path) {
362 throw new Error(`[UriError]: cannot call joinPath on URI without path: ${uri.toString()}`);
370 return uri.with({ path: newPath });
371 }
372 > uri.ts
373 > // ---- printing/externalize ---------------------------
374 >
375 > /**
376 > * Creates a string representation for this URI. It's guaranteed that calling
377 > * `URI.parse` with the result of this function creates an URI which is equal
378 > * to this URI.
379 > *
380 > * * The result shall *not* be used for display purposes but for externalization or transport.
381 > * * The result will be encoded using the percentage encoding and encoding happens mostly
382 > * ignore the scheme-specific encoding rules.
383 > *
384 > * @param skipEncoding Do not encode the result, default is `false`
385 > */
386 > toString(skipEncoding: boolean = false): string {
387 return _asFormatted(this, skipEncoding);
388 }
389 > uri.ts
390 > toJSON(): UriComponents {
391 return this;
392 }
393 > uri.ts
394 > /**
395 > * A helper function to revive URIs.
396 > *
397 > * **Note** that this function should only be used when receiving URI#toJSON generated data
398 > * and that it doesn't do any validation. Use {@link URI.from} when received "untrusted"
399 > * uri components such as command arguments or data from storage.
400 > *
401 > * @param data The URI components or URI to revive.
402 > * @returns The revived URI or undefined or null.
403 > */
404 > static revive(data: UriComponents | URI): URI;
405 > static revive(data: UriComponents | URI | undefined): URI | undefined;
406 > static revive(data: UriComponents | URI | null): URI | null;
407 > static revive(data: UriComponents | URI | undefined | null): URI | undefined | null;
408 > static revive(data: UriComponents | URI | undefined | null): URI | undefined | null {
409 if (!data) {
410 return data;
418 }
419 }
420 > uri.ts
421 > [Symbol.for('debug.description')]() {
422 return `URI(${this.toString()})`;
423 }
424 > } uri.ts
425 >
426 > export interface UriComponents {
427 > scheme: string;
428 > authority?: string;
429 > path?: string;
430 > query?: string;
431 > fragment?: string;
432 > }
433 >
434 > export function isUriComponents(thing: unknown): thing is UriComponents {
435 if (!thing || typeof thing !== 'object') {
436 return false;
442 && (typeof (<UriComponents>thing).fragment === 'string' || typeof (<UriComponents>thing).fragment === 'undefined');
443 }
444 > uri.ts
445 > interface UriState extends UriComponents {
446 > $mid: MarshalledId.Uri;
447 > external?: string;
448 > fsPath?: string;
449 > _sep?: 1;
450 > }
451 >
452 > const _pathSepMarker = isWindows ? 1 : undefined;
453 >
454 > // This class exists so that URI is compatible with vscode.Uri (API).
455 > class Uri extends URI { uri.ts
456 >
457 > _formatted: string | null = null;
458 > _fsPath: string | null = null;
459 > uri.ts
460 > override get fsPath(): string {
461 if (!this._fsPath) {
462 this._fsPath = uriToFsPath(this, false);
464 return this._fsPath;
465 }
466 > uri.ts
467 > override toString(skipEncoding: boolean = false): string {
468 > if (!skipEncoding) { uri.ts
469 > if (!this._formatted) { uri.ts
470 > this._formatted = _asFormatted(this, false);
471 > }
472 > return this._formatted;
473 > } else { uri.ts
474 // we don't cache that
475 return _asFormatted(this, true);
476 }
477 > } uri.ts
478 > uri.ts
479 > override toJSON(): UriComponents {
480 // eslint-disable-next-line local/code-no-dangerous-type-assertions
481 const res = <UriState>{
512 return res;
513 }
514 > } uri.ts
515 >
516 > // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
517 > const encodeTable: { [ch: number]: string } = {
518 > [CharCode.Colon]: '%3A', // gen-delims
519 > [CharCode.Slash]: '%2F',
520 > [CharCode.QuestionMark]: '%3F',
521 > [CharCode.Hash]: '%23',
522 > [CharCode.OpenSquareBracket]: '%5B',
523 > [CharCode.CloseSquareBracket]: '%5D',
524 > [CharCode.AtSign]: '%40',
525 >
526 > [CharCode.ExclamationMark]: '%21', // sub-delims
527 > [CharCode.DollarSign]: '%24',
528 > [CharCode.Ampersand]: '%26',
529 > [CharCode.SingleQuote]: '%27',
530 > [CharCode.OpenParen]: '%28',
531 > [CharCode.CloseParen]: '%29',
532 > [CharCode.Asterisk]: '%2A',
533 > [CharCode.Plus]: '%2B',
534 > [CharCode.Comma]: '%2C',
535 > [CharCode.Semicolon]: '%3B',
536 > [CharCode.Equals]: '%3D',
537 >
538 > [CharCode.Space]: '%20',
539 > };
540 >
541 > function encodeURIComponentFast(uriComponent: string, isPath: boolean, isAuthority: boolean): string { uri.ts
542 > let res: string | undefined = undefined;
543 > let nativeEncodePos = -1;
544 >
545 > for (let pos = 0; pos < uriComponent.length; pos++) {
546 > const code = uriComponent.charCodeAt(pos);
547 >
548 > // unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3
549 > if (
550 > (code >= CharCode.a && code <= CharCode.z)
551 > || (code >= CharCode.A && code <= CharCode.Z) uri.ts
552 > || (code >= CharCode.Digit0 && code <= CharCode.Digit9)
553 > || code === CharCode.Dash uri.ts
554 > || code === CharCode.Period uri.ts
555 > || code === CharCode.Underline
556 > || code === CharCode.Tilde
557 > || (isPath && code === CharCode.Slash)
558 || (isAuthority && code === CharCode.OpenSquareBracket)
559 || (isAuthority && code === CharCode.CloseSquareBracket)
560 || (isAuthority && code === CharCode.Colon)
561 > ) { uri.ts
562 > // check if we are delaying native encode
563 > if (nativeEncodePos !== -1) {
564 res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
565 nativeEncodePos = -1;
566 }
567 > // check if we write into a new string (by default we try to return the param) uri.ts
568 > if (res !== undefined) {
569 res += uriComponent.charAt(pos);
570 }
571 > uri.ts
572 > } else {
573 // encoding needed, we need to allocate a new string
574 if (res === undefined) {
594 }
595 }
596 > } uri.ts
597 >
598 > if (nativeEncodePos !== -1) {
599 res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
600 }
601 > uri.ts
602 > return res !== undefined ? res : uriComponent;
603 > }
604 > uri.ts
605 function encodeURIComponentMinimal(path: string): string {
606 let res: string | undefined = undefined;
620 return res !== undefined ? res : path;
621 }
622 > uri.ts
623 > /**
624 > * Compute `fsPath` for the given uri
625 > */
626 > export function uriToFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
627
628 let value: string;
650 return value;
651 }
652 > uri.ts
653 > /**
654 > * Create the external version of a uri
655 > */
656 > function _asFormatted(uri: URI, skipEncoding: boolean): string { uri.ts
657 >
658 > const encoder = !skipEncoding
659 > ? encodeURIComponentFast uri.ts
660 : encodeURIComponentMinimal;
661 > uri.ts
662 > let res = '';
663 > let { scheme, authority, path, query, fragment } = uri;
664 > if (scheme) {
665 > res += scheme;
666 > res += ':';
667 > }
668 > if (authority || scheme === 'file') {
669 res += _slash;
670 res += _slash;
671 }
672 > if (authority) { uri.ts
673 let idx = authority.indexOf('@');
674 if (idx !== -1) {
697 }
698 }
699 > if (path) { uri.ts
700 > // lower-case windows drive letters in /C:/fff or C:/fff uri.ts
701 > if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) {
702 const code = path.charCodeAt(1);
703 if (code >= CharCode.A && code <= CharCode.Z) {
704 path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
705 }
706 > } else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) { uri.ts
707 const code = path.charCodeAt(0);
708 if (code >= CharCode.A && code <= CharCode.Z) {
710 }
711 }
712 > // encode the rest of the path uri.ts
713 > res += encoder(path, true, false);
714 > }
715 > if (query) { uri.ts
716 res += '?';
717 res += encoder(query, false, false);
718 }
719 > if (fragment) { uri.ts
720 res += '#';
721 res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment;
722 }
723 > return res; uri.ts
724 > }
725 > uri.ts
726 > // --- decode
727 >
728 function decodeURIComponentGraceful(str: string): string {
729 try {
737 }
738 }
739 > uri.ts
740 > const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
741 >
742 > function percentDecode(str: string): string { uri.ts
743 > if (!str.match(_rEncodedAsHex)) {
744 > return str;
745 > }
746 return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
747 }
748 > uri.ts
749 > /**
750 > * Mapped-type that replaces all occurrences of URI with UriComponents
751 > */
752 > export type UriDto<T> = { [K in keyof T]: T[K] extends URI
753 > ? UriComponents
754 > : UriDto<T[K]> };
src/vs/base/common/arrays.ts 408 covered LOC · 75 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- arrays.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { findFirstIdxMonotonousOrArrLen } from './arraysFind.js';
7 > import { CancellationToken } from './cancellation.js';
8 > import { CancellationError } from './errors.js';
9 > import { ISplice } from './sequence.js';
10 >
11 > /**
12 > * Returns the last entry and the initial N-1 entries of the array, as a tuple of [rest, last].
13 > *
14 > * The array must have at least one element.
15 > *
16 > * @param arr The input array
17 > * @returns A tuple of [rest, last] where rest is all but the last element and last is the last element
18 > * @throws Error if the array is empty
19 > */
20 > export function tail<T>(arr: T[]): [T[], T] {
21 if (arr.length === 0) {
22 throw new Error('Invalid tail call');
25 return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
26 }
27 > arrays.ts
28 > export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArray<T> | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
29 if (one === other) {
30 return true;
47 return true;
48 }
49 > arrays.ts
50 > /**
51 > * Remove the element at `index` by replacing it with the last element. This is faster than `splice`
52 > * but changes the order of the array
53 > */
54 > export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {
55 const last = array.length - 1;
56 if (index < last) {
59 array.pop();
60 }
61 > arrays.ts
62 > /**
63 > * Performs a binary search algorithm over a sorted array.
64 > *
65 > * @param array The array being searched.
66 > * @param key The value we search for.
67 > * @param comparator A function that takes two array elements and returns zero
68 > * if they are equal, a negative number if the first element precedes the
69 > * second one in the sorting order, or a positive number if the second element
70 > * precedes the first one.
71 > * @return See {@link binarySearch2}
72 > */
73 > export function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {
74 return binarySearch2(array.length, i => comparator(array[i], key));
75 }
76 > arrays.ts
77 > /**
78 > * Performs a binary search algorithm over a sorted collection. Useful for cases
79 > * when we need to perform a binary search over something that isn't actually an
80 > * array, and converting data to an array would defeat the use of binary search
81 > * in the first place.
82 > *
83 > * @param length The collection length.
84 > * @param compareToKey A function that takes an index of an element in the
85 > * collection and returns zero if the value at this index is equal to the
86 > * search key, a negative number if the value precedes the search key in the
87 > * sorting order, or a positive number if the search key precedes the value.
88 > * @return A non-negative index of an element, if found. If not found, the
89 > * result is -(n+1) (or ~n, using bitwise notation), where n is the index
90 > * where the key should be inserted to maintain the sorting order.
91 > */
92 > export function binarySearch2(length: number, compareToKey: (index: number) => number): number {
93 let low = 0,
94 high = length - 1;
107 return -(low + 1);
108 }
109 > arrays.ts
110 > type Compare<T> = (a: T, b: T) => number;
111 >
112 > /**
113 > * Finds the nth smallest element in the array using quickselect algorithm.
114 > * The data does not need to be sorted.
115 > *
116 > * @param nth The zero-based index of the element to find (0 = smallest, 1 = second smallest, etc.)
117 > * @param data The unsorted array
118 > * @param compare A comparator function that defines the sort order
119 > * @returns The nth smallest element
120 > * @throws TypeError if nth is >= data.length
121 > */
122 > export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
123
124 nth = nth | 0;
152 }
153 }
154 > arrays.ts
155 > export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
156 const result: T[][] = [];
157 let currentGroup: T[] | undefined = undefined;
166 return result;
167 }
168 > arrays.ts
169 > /**
170 > * Splits the given items into a list of (non-empty) groups.
171 > * `shouldBeGrouped` is used to decide if two consecutive items should be in the same group.
172 > * The order of the items is preserved.
173 > */
174 > export function* groupAdjacentBy<T>(items: Iterable<T>, shouldBeGrouped: (item1: T, item2: T) => boolean): Iterable<T[]> {
175 let currentGroup: T[] | undefined;
176 let last: T | undefined;
190 }
191 }
192 > arrays.ts
193 > export function forEachAdjacent<T>(arr: T[], f: (item1: T | undefined, item2: T | undefined) => void): void {
194 for (let i = 0; i <= arr.length; i++) {
195 f(i === 0 ? undefined : arr[i - 1], i === arr.length ? undefined : arr[i]);
196 }
197 }
198 > arrays.ts
199 > export function forEachWithNeighbors<T>(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void {
200 for (let i = 0; i < arr.length; i++) {
201 f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]);
202 }
203 }
204 > arrays.ts
205 > export function concatArrays<T extends any[]>(...arrays: T): T[number][number][] {
206 return [].concat(...arrays);
207 }
208 > arrays.ts
209 > interface IMutableSplice<T> extends ISplice<T> {
210 > readonly toInsert: T[];
211 > deleteCount: number;
212 > }
213 >
214 > /**
215 > * Diffs two *sorted* arrays and computes the splices which apply the diff.
216 > */
217 > export function sortedDiff<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): ISplice<T>[] {
218 const result: IMutableSplice<T>[] = [];
219
266 return result;
267 }
268 > arrays.ts
269 > /**
270 > * Takes two *sorted* arrays and computes their delta (removed, added elements).
271 > * Finishes in `Math.min(before.length, after.length)` steps.
272 > */
273 > export function delta<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): { removed: T[]; added: T[] } {
274 const splices = sortedDiff(before, after, compare);
275 const removed: T[] = [];
283 return { removed, added };
284 }
285 > arrays.ts
286 > /**
287 > * Returns the top N elements from the array.
288 > *
289 > * Faster than sorting the entire array when the array is a lot larger than N.
290 > *
291 > * @param array The unsorted array.
292 > * @param compare A sort function for the elements.
293 > * @param n The number of elements to return.
294 > * @return The first n elements from array when sorted with compare.
295 > */
296 > export function top<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, n: number): T[] {
297 if (n === 0) {
298 return [];
302 return result;
303 }
304 > arrays.ts
305 > /**
306 > * Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run.
307 > *
308 > * Returns the top N elements from the array.
309 > *
310 > * Faster than sorting the entire array when the array is a lot larger than N.
311 > *
312 > * @param array The unsorted array.
313 > * @param compare A sort function for the elements.
314 > * @param n The number of elements to return.
315 > * @param batch The number of elements to examine before yielding to the event loop.
316 > * @return The first n elements from array when sorted with compare.
317 > */
318 > export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: number, batch: number, token?: CancellationToken): Promise<T[]> {
319 if (n === 0) {
320 return Promise.resolve([]);
339 });
340 }
341 > arrays.ts
342 function topStep<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
343 for (const n = result.length; i < m; i++) {
350 }
351 }
352 > arrays.ts
353 > /**
354 > * @returns New array with all falsy values removed. The original array IS NOT modified.
355 > */
356 > export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
357 return array.filter((e): e is T => !!e);
358 }
359 > arrays.ts
360 > /**
361 > * Remove all falsy values from `array`. The original array IS modified.
362 > */
363 > export function coalesceInPlace<T>(array: Array<T | undefined | null>): asserts array is Array<T> {
364 let to = 0;
365 for (let i = 0; i < array.length; i++) {
371 array.length = to;
372 }
373 > arrays.ts
374 > /**
375 > * @deprecated Use `Array.copyWithin` instead
376 > */
377 > export function move(array: unknown[], from: number, to: number): void {
378 array.splice(to, 0, array.splice(from, 1)[0]);
379 }
380 > arrays.ts
381 > /**
382 > * @returns false if the provided object is an array and not empty.
383 > */
384 > export function isFalsyOrEmpty(obj: unknown): boolean {
385 return !Array.isArray(obj) || obj.length === 0;
386 }
387 > arrays.ts
388 > /**
389 > * @returns True if the provided object is an array and has at least one element.
390 > */
391 > export function isNonEmptyArray<T>(obj: T[] | undefined | null): obj is T[];
392 > export function isNonEmptyArray<T>(obj: readonly T[] | undefined | null): obj is readonly T[];
393 > export function isNonEmptyArray<T>(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] {
394 return Array.isArray(obj) && obj.length > 0;
395 }
396 > arrays.ts
397 > /**
398 > * Removes duplicates from the given array. The optional keyFn allows to specify
399 > * how elements are checked for equality by returning an alternate value for each.
400 > */
401 > export function distinct<T>(array: ReadonlyArray<T>, keyFn: (value: T) => unknown = value => value): T[] {
402 const seen = new Set<any>();
403
411 });
412 }
413 > arrays.ts
414 > export function uniqueFilter<T, R>(keyFn: (t: T) => R): (t: T) => boolean {
415 const seen = new Set<R>();
416
426 };
427 }
428 > arrays.ts
429 > export function commonPrefixLength<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
430 let result = 0;
431
436 return result;
437 }
438 > arrays.ts
439 > export function range(to: number): number[];
440 > export function range(from: number, to: number): number[];
441 > export function range(arg: number, to?: number): number[] {
442 let from = typeof to === 'number' ? arg : 0;
443
463 return result;
464 }
465 > arrays.ts
466 > export function index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T };
467 > export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R };
468 > export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R } {
469 return array.reduce((r, t) => {
470 r[indexer(t)] = mapper ? mapper(t) : t;
472 }, Object.create(null));
473 }
474 > arrays.ts
475 > /**
476 > * Inserts an element into an array. Returns a function which, when
477 > * called, will remove that element from the array.
478 > *
479 > * @deprecated In almost all cases, use a `Set<T>` instead.
480 > */
481 > export function insert<T>(array: T[], element: T): () => void {
482 array.push(element);
483
484 return () => remove(array, element);
485 }
486 > arrays.ts
487 > /**
488 > * Removes an element from an array if it can be found.
489 > *
490 > * @deprecated In almost all cases, use a `Set<T>` instead.
491 > */
492 > export function remove<T>(array: T[], element: T): T | undefined {
493 const index = array.indexOf(element);
494 if (index > -1) {
500 return undefined;
501 }
502 > arrays.ts
503 > /**
504 > * Insert `insertArr` inside `target` at `insertIndex`.
505 > * Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array
506 > */
507 > export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[]): T[] {
508 const before = target.slice(0, insertIndex);
509 const after = target.slice(insertIndex);
510 return before.concat(insertArr, after);
511 }
512 > arrays.ts
513 > /**
514 > * Uses Fisher-Yates shuffle to shuffle the given array
515 > */
516 > export function shuffle<T>(array: T[], _seed?: number): void {
517 let rand: () => number;
518
536 }
537 }
538 > arrays.ts
539 > /**
540 > * Pushes an element to the start of the array, if found.
541 > */
542 > export function pushToStart<T>(arr: T[], value: T): void {
543 const index = arr.indexOf(value);
544
548 }
549 }
550 > arrays.ts
551 > /**
552 > * Pushes an element to the end of the array, if found.
553 > */
554 > export function pushToEnd<T>(arr: T[], value: T): void {
555 const index = arr.indexOf(value);
556
560 }
561 }
562 > arrays.ts
563 > export function pushMany<T>(arr: T[], items: ReadonlyArray<T>): void {
564 for (const item of items) {
565 arr.push(item);
566 }
567 }
568 > arrays.ts
569 > export function mapArrayOrNot<T, U>(items: T | T[], fn: (_: T) => U): U | U[] {
570 return Array.isArray(items) ?
571 items.map(fn) :
572 fn(items);
573 }
574 > arrays.ts
575 > export function mapFilter<T, U>(array: ReadonlyArray<T>, fn: (t: T) => U | undefined): U[] {
576 const result: U[] = [];
577 for (const item of array) {
583 return result;
584 }
585 > arrays.ts
586 > export function withoutDuplicates<T>(array: ReadonlyArray<T>): T[] {
587 const s = new Set(array);
588 return Array.from(s);
589 }
590 > arrays.ts
591 > export function asArray<T>(x: T | T[]): T[];
592 > export function asArray<T>(x: T | readonly T[]): readonly T[];
593 > export function asArray<T>(x: T | T[]): T[] {
594 return Array.isArray(x) ? x : [x];
595 }
596 > arrays.ts
597 > export function getRandomElement<T>(arr: T[]): T | undefined {
598 return arr[Math.floor(Math.random() * arr.length)];
599 }
600 > arrays.ts
601 > /**
602 > * Insert the new items in the array.
603 > * @param array The original array.
604 > * @param start The zero-based location in the array from which to start inserting elements.
605 > * @param newItems The items to be inserted
606 > */
607 > export function insertInto<T>(array: T[], start: number, newItems: T[]): void {
608 const startIdx = getActualStartIndex(array, start);
609 const originalLength = array.length;
619 }
620 }
621 > arrays.ts
622 > /**
623 > * Removes elements from an array and inserts new elements in their place, returning the deleted elements. Alternative to the native Array.splice method, it
624 > * can only support limited number of items due to the maximum call stack size limit.
625 > * @param array The original array.
626 > * @param start The zero-based location in the array from which to start removing elements.
627 > * @param deleteCount The number of elements to remove.
628 > * @returns An array containing the elements that were deleted.
629 > */
630 > export function splice<T>(array: T[], start: number, deleteCount: number, newItems: T[]): T[] {
631 const index = getActualStartIndex(array, start);
632 let result = array.splice(index, deleteCount);
638 return result;
639 }
640 > arrays.ts
641 > /**
642 > * Determine the actual start index (same logic as the native splice() or slice())
643 > * If greater than the length of the array, start will be set to the length of the array. In this case, no element will be deleted but the method will behave as an adding function, adding as many element as item[n*] provided.
644 > * If negative, it will begin that many elements from the end of the array. (In this case, the origin -1, meaning -n is the index of the nth last element, and is therefore equivalent to the index of array.length - n.) If array.length + start is less than 0, it will begin from index 0.
645 > * @param array The target array.
646 > * @param start The operation index.
647 > */
648 function getActualStartIndex<T>(array: T[], start: number): number {
649 return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length);
650 }
651 > arrays.ts
652 >
653 >
654 > /**
655 > * When comparing two values,
656 > * a negative number indicates that the first value is less than the second,
657 > * a positive number indicates that the first value is greater than the second,
658 > * and zero indicates that neither is the case.
659 > */
660 > export type CompareResult = number;
661 >
662 > export namespace CompareResult {
663 > export function isLessThan(result: CompareResult): boolean {
664 return result < 0;
665 }
666 > arrays.ts
667 > export function isLessThanOrEqual(result: CompareResult): boolean {
668 return result <= 0;
669 }
670 > arrays.ts
671 > export function isGreaterThan(result: CompareResult): boolean {
672 return result > 0;
673 }
674 > arrays.ts
675 > export function isNeitherLessOrGreaterThan(result: CompareResult): boolean {
676 return result === 0;
677 }
678 > arrays.ts
679 > export const greaterThan = 1;
680 > export const lessThan = -1;
681 > export const neitherLessOrGreaterThan = 0;
682 > }
683 >
684 > /**
685 > * A comparator `c` defines a total order `<=` on `T` as following:
686 > * `c(a, b) <= 0` iff `a` <= `b`.
687 > * We also have `c(a, b) == 0` iff `c(b, a) == 0`.
688 > */
689 > export type Comparator<T> = (a: T, b: T) => CompareResult;
690 >
691 > export function compareBy<TItem, TCompareBy>(selector: (item: TItem) => TCompareBy, comparator: Comparator<TCompareBy>): Comparator<TItem> {
692 > return (a, b) => comparator(selector(a), selector(b)); arrays.ts
693 > }
694 > arrays.ts
695 > export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]): Comparator<TItem> {
696 > return (item1, item2) => { arrays.ts
697 for (const comparator of comparators) {
698 const result = comparator(item1, item2);
703 return CompareResult.neitherLessOrGreaterThan;
704 };
705 > } arrays.ts
706 > arrays.ts
707 > /**
708 > * The natural order on numbers.
709 > */
710 > export const numberComparator: Comparator<number> = (a, b) => a - b;
711 >
712 > export const booleanComparator: Comparator<boolean> = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);
713 >
714 > export function reverseOrder<TItem>(comparator: Comparator<TItem>): Comparator<TItem> {
715 return (a, b) => -comparator(a, b);
716 }
717 > arrays.ts
718 > /**
719 > * Returns a new comparator that treats `undefined` as the smallest value.
720 > * All other values are compared using the given comparator.
721 > */
722 > export function compareUndefinedSmallest<T>(comparator: Comparator<T>): Comparator<T | undefined> {
723 return (a, b) => {
724 if (a === undefined) {
731 };
732 }
733 > arrays.ts
734 > export class ArrayQueue<T> {
735 > private readonly items: readonly T[];
736 > private firstIdx = 0;
737 > private lastIdx: number;
738 >
739 > /**
740 > * Constructs a queue that is backed by the given array. Runtime is O(1).
741 > */
742 > constructor(items: readonly T[]) {
743 this.items = items;
744 this.lastIdx = this.items.length - 1;
745 }
746 > arrays.ts
747 > get length(): number {
748 return this.lastIdx - this.firstIdx + 1;
749 }
750 > arrays.ts
751 > /**
752 > * Consumes elements from the beginning of the queue as long as the predicate returns true.
753 > * If no elements were consumed, `null` is returned. Has a runtime of O(result.length).
754 > */
755 > takeWhile(predicate: (value: T) => boolean): T[] | null {
756 // P(k) := k <= this.lastIdx && predicate(this.items[k])
757 // Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s)
765 return result;
766 }
767 > arrays.ts
768 > /**
769 > * Consumes elements from the end of the queue as long as the predicate returns true.
770 > * If no elements were consumed, `null` is returned.
771 > * The result has the same order as the underlying array!
772 > */
773 > takeFromEndWhile(predicate: (value: T) => boolean): T[] | null {
774 // P(k) := this.firstIdx >= k && predicate(this.items[k])
775 // Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx]
783 return result;
784 }
785 > arrays.ts
786 > peek(): T | undefined {
787 if (this.length === 0) {
788 return undefined;
790 return this.items[this.firstIdx];
791 }
792 > arrays.ts
793 > peekLast(): T | undefined {
794 if (this.length === 0) {
795 return undefined;
797 return this.items[this.lastIdx];
798 }
799 > arrays.ts
800 > dequeue(): T | undefined {
801 const result = this.items[this.firstIdx];
802 this.firstIdx++;
803 return result;
804 }
805 > arrays.ts
806 > removeLast(): T | undefined {
807 const result = this.items[this.lastIdx];
808 this.lastIdx--;
809 return result;
810 }
811 > arrays.ts
812 > takeCount(count: number): T[] {
813 const result = this.items.slice(this.firstIdx, this.firstIdx + count);
814 this.firstIdx += count;
815 return result;
816 }
817 > } arrays.ts
818 >
819 > /**
820 > * This class is faster than an iterator and array for lazy computed data.
821 > */
822 > export class CallbackIterable<T> {
823 > public static readonly empty = new CallbackIterable<never>(_callback => { });
824 >
825 > constructor(
826 > /**
827 > * Calls the callback for every item.
828 > * Stops when the callback returns false.
829 > */
830 > public readonly iterate: (callback: (item: T) => boolean) => void
831 > ) {
832 > }
833 >
834 > forEach(handler: (item: T) => void) {
835 this.iterate(item => { handler(item); return true; });
836 }
837 > arrays.ts
838 > toArray(): T[] {
839 const result: T[] = [];
840 this.iterate(item => { result.push(item); return true; });
841 return result;
842 }
843 > arrays.ts
844 > filter(predicate: (item: T) => boolean): CallbackIterable<T> {
845 return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true));
846 }
847 > arrays.ts
848 > map<TResult>(mapFn: (item: T) => TResult): CallbackIterable<TResult> {
849 return new CallbackIterable<TResult>(cb => this.iterate(item => cb(mapFn(item))));
850 }
851 > arrays.ts
852 > some(predicate: (item: T) => boolean): boolean {
853 let result = false;
854 this.iterate(item => { result = predicate(item); return !result; });
855 return result;
856 }
857 > arrays.ts
858 > findFirst(predicate: (item: T) => boolean): T | undefined {
859 let result: T | undefined;
860 this.iterate(item => {
867 return result;
868 }
869 > arrays.ts
870 > findLast(predicate: (item: T) => boolean): T | undefined {
871 let result: T | undefined;
872 this.iterate(item => {
878 return result;
879 }
880 > arrays.ts
881 > findLastMaxBy(comparator: Comparator<T>): T | undefined {
882 let result: T | undefined;
883 let first = true;
891 return result;
892 }
893 > } arrays.ts
894 >
895 > /**
896 > * Represents a re-arrangement of items in an array.
897 > */
898 > export class Permutation {
899 > constructor(private readonly _indexMap: readonly number[]) { }
900 >
901 > /**
902 > * Returns a permutation that sorts the given array according to the given compare function.
903 > */
904 > public static createSortPermutation<T>(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation {
905 const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2]));
906 return new Permutation(sortIndices);
907 }
908 > arrays.ts
909 > /**
910 > * Returns a new array with the elements of the given array re-arranged according to this permutation.
911 > */
912 > apply<T>(arr: readonly T[]): T[] {
913 return arr.map((_, index) => arr[this._indexMap[index]]);
914 }
915 > arrays.ts
916 > /**
917 > * Returns a new permutation that undoes the re-arrangement of this permutation.
918 > */
919 > inverse(): Permutation {
920 const inverseIndexMap = this._indexMap.slice();
921 for (let i = 0; i < this._indexMap.length; i++) {
924 return new Permutation(inverseIndexMap);
925 }
926 > } arrays.ts
927 >
928 > /**
929 > * Asynchronous variant of `Array.find()`, returning the first element in
930 > * the array for which the predicate returns true.
931 > *
932 > * This implementation does not bail early and waits for all promises to
933 > * resolve before returning.
934 > */
935 export async function findAsync<T>(array: readonly T[], predicate: (element: T, index: number) => Promise<boolean>): Promise<T | undefined> {
936 const results = await Promise.all(array.map(
940 return results.find(r => r.ok)?.element;
941 }
942 > arrays.ts
943 > export function sum(array: readonly number[]): number {
944 return array.reduce((acc, value) => acc + value, 0);
945 }
946 > arrays.ts
947 > export function sumBy<T>(array: readonly T[], selector: (value: T) => number): number {
948 return array.reduce((acc, value) => acc + selector(value), 0);
949 }
src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts 365 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- action-origin.generated.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > // Generated from types/actions.ts — do not edit
10 > // Run `npm run generate` to regenerate.
11 >
12 > import { ActionType, type StateAction, type RootAgentsChangedAction, type RootActiveSessionsChangedAction, type RootTerminalsChangedAction, type RootConfigChangedAction, type SessionReadyAction, type SessionCreationFailedAction, type SessionChatAddedAction, type SessionChatRemovedAction, type SessionChatUpdatedAction, type SessionDefaultChatChangedAction, type SessionTitleChangedAction, type SessionServerToolsChangedAction, type SessionActiveClientSetAction, type SessionActiveClientRemovedAction, type SessionWorkingDirectorySetAction, type SessionWorkingDirectoryRemovedAction, type SessionInputNeededSetAction, type SessionInputNeededRemovedAction, type SessionCustomizationsChangedAction, type SessionCustomizationToggledAction, type SessionCustomizationUpdatedAction, type SessionCustomizationRemovedAction, type SessionMcpServerStateChangedAction, type SessionMcpServerStartRequestedAction, type SessionMcpServerStopRequestedAction, type SessionIsReadChangedAction, type SessionIsArchivedChangedAction, type SessionActivityChangedAction, type SessionChangesetsChangedAction, type SessionConfigChangedAction, type SessionMetaChangedAction, type ChatTurnStartedAction, type ChatDeltaAction, type ChatResponsePartAction, type ChatToolCallStartAction, type ChatToolCallDeltaAction, type ChatToolCallReadyAction, type ChatToolCallConfirmedAction, type ChatToolCallCompleteAction, type ChatToolCallResultConfirmedAction, type ChatToolCallContentChangedAction, type ChatToolCallAuthRequiredAction, type ChatToolCallAuthResolvedAction, type ChatTurnCompleteAction, type ChatTurnCancelledAction, type ChatErrorAction, type ChatActivityChangedAction, type ChatWorkingDirectorySetAction, type ChatWorkingDirectoryRemovedAction, type ChatUsageAction, type ChatReasoningAction, type ChatPendingMessageSetAction, type ChatPendingMessageRemovedAction, type ChatQueuedMessagesReorderedAction, type ChatDraftChangedAction, type ChatInputRequestedAction, type ChatInputAnswerChangedAction, type ChatInputCompletedAction, type ChatTruncatedAction, type ChatTurnsLoadedAction, type ChangesetStatusChangedAction, type ChangesetFileSetAction, type ChangesetFileRemovedAction, type ChangesetFilesReviewChangedAction, type ChangesetContentChangedAction, type ChangesetOperationsChangedAction, type ChangesetOperationStatusChangedAction, type ChangesetClearedAction, type AnnotationsSetAction, type AnnotationsUpdatedAction, type AnnotationsRemovedAction, type AnnotationsEntrySetAction, type AnnotationsEntryRemovedAction, type TerminalDataAction, type TerminalInputAction, type TerminalResizedAction, type TerminalClaimedAction, type TerminalTitleChangedAction, type TerminalCwdChangedAction, type TerminalExitedAction, type TerminalClearedAction, type TerminalCommandDetectionAvailableAction, type TerminalCommandExecutedAction, type TerminalCommandFinishedAction, type ResourceWatchChangedAction } from './actions.js';
13 >
14 >
15 > // ─── Root vs Session vs Chat vs Terminal vs Changeset Action Unions ─────────────────
16 >
17 > /** Union of all root-scoped actions. */
18 > export type RootAction =
19 > | RootAgentsChangedAction
20 > | RootActiveSessionsChangedAction
21 > | RootTerminalsChangedAction
22 > | RootConfigChangedAction
23 > ;
24 >
25 > /** Union of root actions that clients may dispatch. */
26 > export type ClientRootAction =
27 > | RootConfigChangedAction
28 > ;
29 >
30 > /** Union of root actions that only the server may produce. */
31 > export type ServerRootAction =
32 > | RootAgentsChangedAction
33 > | RootActiveSessionsChangedAction
34 > | RootTerminalsChangedAction
35 > ;
36 >
37 > /** Union of all session-scoped actions. */
38 > export type SessionAction =
39 > | SessionReadyAction
40 > | SessionCreationFailedAction
41 > | SessionChatAddedAction
42 > | SessionChatRemovedAction
43 > | SessionChatUpdatedAction
44 > | SessionDefaultChatChangedAction
45 > | SessionTitleChangedAction
46 > | SessionServerToolsChangedAction
47 > | SessionActiveClientSetAction
48 > | SessionActiveClientRemovedAction
49 > | SessionWorkingDirectorySetAction
50 > | SessionWorkingDirectoryRemovedAction
51 > | SessionInputNeededSetAction
52 > | SessionInputNeededRemovedAction
53 > | SessionCustomizationsChangedAction
54 > | SessionCustomizationToggledAction
55 > | SessionCustomizationUpdatedAction
56 > | SessionCustomizationRemovedAction
57 > | SessionMcpServerStateChangedAction
58 > | SessionMcpServerStartRequestedAction
59 > | SessionMcpServerStopRequestedAction
60 > | SessionIsReadChangedAction
61 > | SessionIsArchivedChangedAction
62 > | SessionActivityChangedAction
63 > | SessionChangesetsChangedAction
64 > | SessionConfigChangedAction
65 > | SessionMetaChangedAction
66 > ;
67 >
68 > /** Union of session actions that clients may dispatch. */
69 > export type ClientSessionAction =
70 > | SessionTitleChangedAction
71 > | SessionActiveClientSetAction
72 > | SessionActiveClientRemovedAction
73 > | SessionWorkingDirectorySetAction
74 > | SessionWorkingDirectoryRemovedAction
75 > | SessionCustomizationToggledAction
76 > | SessionMcpServerStartRequestedAction
77 > | SessionMcpServerStopRequestedAction
78 > | SessionIsReadChangedAction
79 > | SessionIsArchivedChangedAction
80 > | SessionConfigChangedAction
81 > ;
82 >
83 > /** Union of session actions that only the server may produce. */
84 > export type ServerSessionAction =
85 > | SessionReadyAction
86 > | SessionCreationFailedAction
87 > | SessionChatAddedAction
88 > | SessionChatRemovedAction
89 > | SessionChatUpdatedAction
90 > | SessionDefaultChatChangedAction
91 > | SessionServerToolsChangedAction
92 > | SessionInputNeededSetAction
93 > | SessionInputNeededRemovedAction
94 > | SessionCustomizationsChangedAction
95 > | SessionCustomizationUpdatedAction
96 > | SessionCustomizationRemovedAction
97 > | SessionMcpServerStateChangedAction
98 > | SessionActivityChangedAction
99 > | SessionChangesetsChangedAction
100 > | SessionMetaChangedAction
101 > ;
102 >
103 > /** Union of all chat-scoped actions. */
104 > export type ChatAction =
105 > | ChatTurnStartedAction
106 > | ChatDeltaAction
107 > | ChatResponsePartAction
108 > | ChatToolCallStartAction
109 > | ChatToolCallDeltaAction
110 > | ChatToolCallReadyAction
111 > | ChatToolCallConfirmedAction
112 > | ChatToolCallCompleteAction
113 > | ChatToolCallResultConfirmedAction
114 > | ChatToolCallContentChangedAction
115 > | ChatToolCallAuthRequiredAction
116 > | ChatToolCallAuthResolvedAction
117 > | ChatTurnCompleteAction
118 > | ChatTurnCancelledAction
119 > | ChatErrorAction
120 > | ChatActivityChangedAction
121 > | ChatWorkingDirectorySetAction
122 > | ChatWorkingDirectoryRemovedAction
123 > | ChatUsageAction
124 > | ChatReasoningAction
125 > | ChatPendingMessageSetAction
126 > | ChatPendingMessageRemovedAction
127 > | ChatQueuedMessagesReorderedAction
128 > | ChatDraftChangedAction
129 > | ChatInputRequestedAction
130 > | ChatInputAnswerChangedAction
131 > | ChatInputCompletedAction
132 > | ChatTruncatedAction
133 > | ChatTurnsLoadedAction
134 > ;
135 >
136 > /** Union of chat actions that clients may dispatch. */
137 > export type ClientChatAction =
138 > | ChatTurnStartedAction
139 > | ChatToolCallConfirmedAction
140 > | ChatToolCallCompleteAction
141 > | ChatToolCallResultConfirmedAction
142 > | ChatToolCallContentChangedAction
143 > | ChatTurnCancelledAction
144 > | ChatWorkingDirectorySetAction
145 > | ChatWorkingDirectoryRemovedAction
146 > | ChatPendingMessageSetAction
147 > | ChatPendingMessageRemovedAction
148 > | ChatQueuedMessagesReorderedAction
149 > | ChatDraftChangedAction
150 > | ChatInputAnswerChangedAction
151 > | ChatInputCompletedAction
152 > | ChatTruncatedAction
153 > ;
154 >
155 > /** Union of chat actions that only the server may produce. */
156 > export type ServerChatAction =
157 > | ChatDeltaAction
158 > | ChatResponsePartAction
159 > | ChatToolCallStartAction
160 > | ChatToolCallDeltaAction
161 > | ChatToolCallReadyAction
162 > | ChatToolCallAuthRequiredAction
163 > | ChatToolCallAuthResolvedAction
164 > | ChatTurnCompleteAction
165 > | ChatErrorAction
166 > | ChatActivityChangedAction
167 > | ChatUsageAction
168 > | ChatReasoningAction
169 > | ChatInputRequestedAction
170 > | ChatTurnsLoadedAction
171 > ;
172 >
173 > /** Union of all terminal-scoped actions. */
174 > export type TerminalAction =
175 > | TerminalDataAction
176 > | TerminalInputAction
177 > | TerminalResizedAction
178 > | TerminalClaimedAction
179 > | TerminalTitleChangedAction
180 > | TerminalCwdChangedAction
181 > | TerminalExitedAction
182 > | TerminalClearedAction
183 > | TerminalCommandDetectionAvailableAction
184 > | TerminalCommandExecutedAction
185 > | TerminalCommandFinishedAction
186 > ;
187 >
188 > /** Union of terminal actions that clients may dispatch. */
189 > export type ClientTerminalAction =
190 > | TerminalInputAction
191 > | TerminalResizedAction
192 > | TerminalClaimedAction
193 > | TerminalTitleChangedAction
194 > | TerminalClearedAction
195 > ;
196 >
197 > /** Union of terminal actions that only the server may produce. */
198 > export type ServerTerminalAction =
199 > | TerminalDataAction
200 > | TerminalCwdChangedAction
201 > | TerminalExitedAction
202 > | TerminalCommandDetectionAvailableAction
203 > | TerminalCommandExecutedAction
204 > | TerminalCommandFinishedAction
205 > ;
206 >
207 > /** Union of all changeset-scoped actions. */
208 > export type ChangesetAction =
209 > | ChangesetStatusChangedAction
210 > | ChangesetFileSetAction
211 > | ChangesetFileRemovedAction
212 > | ChangesetFilesReviewChangedAction
213 > | ChangesetContentChangedAction
214 > | ChangesetOperationsChangedAction
215 > | ChangesetOperationStatusChangedAction
216 > | ChangesetClearedAction
217 > ;
218 >
219 > /** Union of changeset actions that clients may dispatch. */
220 > export type ClientChangesetAction =
221 > | ChangesetFilesReviewChangedAction
222 > ;
223 >
224 > /** Union of changeset actions that only the server may produce. */
225 > export type ServerChangesetAction =
226 > | ChangesetStatusChangedAction
227 > | ChangesetFileSetAction
228 > | ChangesetFileRemovedAction
229 > | ChangesetContentChangedAction
230 > | ChangesetOperationsChangedAction
231 > | ChangesetOperationStatusChangedAction
232 > | ChangesetClearedAction
233 > ;
234 >
235 > /** Union of all annotations-scoped actions. */
236 > export type AnnotationsAction =
237 > | AnnotationsSetAction
238 > | AnnotationsUpdatedAction
239 > | AnnotationsRemovedAction
240 > | AnnotationsEntrySetAction
241 > | AnnotationsEntryRemovedAction
242 > ;
243 >
244 > /** Union of annotations actions that clients may dispatch. */
245 > export type ClientAnnotationsAction =
246 > | AnnotationsSetAction
247 > | AnnotationsUpdatedAction
248 > | AnnotationsRemovedAction
249 > | AnnotationsEntrySetAction
250 > | AnnotationsEntryRemovedAction
251 > ;
252 >
253 > /** Union of annotations actions that only the server may produce. */
254 > export type ServerAnnotationsAction =
255 > never
256 > ;
257 >
258 > /** Union of all resource-watch-scoped actions. */
259 > export type ResourceWatchAction =
260 > | ResourceWatchChangedAction
261 > ;
262 >
263 > /** Union of resource-watch actions that clients may dispatch. */
264 > export type ClientResourceWatchAction =
265 > never
266 > ;
267 >
268 > /** Union of resource-watch actions that only the server may produce. */
269 > export type ServerResourceWatchAction =
270 > | ResourceWatchChangedAction
271 > ;
272 >
273 > // ─── Client-Dispatchable Map ─────────────────────────────────────────────────
274 >
275 > /**
276 > * Exhaustive map indicating which action types may be dispatched by clients.
277 > * Adding a new action to StateAction without adding it here is a compile error.
278 > */
279 > export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: boolean } = {
280 > [ActionType.RootAgentsChanged]: false,
281 > [ActionType.RootActiveSessionsChanged]: false,
282 > [ActionType.RootTerminalsChanged]: false,
283 > [ActionType.RootConfigChanged]: true,
284 > [ActionType.SessionReady]: false,
285 > [ActionType.SessionCreationFailed]: false,
286 > [ActionType.SessionChatAdded]: false,
287 > [ActionType.SessionChatRemoved]: false,
288 > [ActionType.SessionChatUpdated]: false,
289 > [ActionType.SessionDefaultChatChanged]: false,
290 > [ActionType.SessionTitleChanged]: true,
291 > [ActionType.SessionServerToolsChanged]: false,
292 > [ActionType.SessionActiveClientSet]: true,
293 > [ActionType.SessionActiveClientRemoved]: true,
294 > [ActionType.SessionWorkingDirectorySet]: true,
295 > [ActionType.SessionWorkingDirectoryRemoved]: true,
296 > [ActionType.SessionInputNeededSet]: false,
297 > [ActionType.SessionInputNeededRemoved]: false,
298 > [ActionType.SessionCustomizationsChanged]: false,
299 > [ActionType.SessionCustomizationToggled]: true,
300 > [ActionType.SessionCustomizationUpdated]: false,
301 > [ActionType.SessionCustomizationRemoved]: false,
302 > [ActionType.SessionMcpServerStateChanged]: false,
303 > [ActionType.SessionMcpServerStartRequested]: true,
304 > [ActionType.SessionMcpServerStopRequested]: true,
305 > [ActionType.SessionIsReadChanged]: true,
306 > [ActionType.SessionIsArchivedChanged]: true,
307 > [ActionType.SessionActivityChanged]: false,
308 > [ActionType.SessionChangesetsChanged]: false,
309 > [ActionType.SessionConfigChanged]: true,
310 > [ActionType.SessionMetaChanged]: false,
311 > [ActionType.ChatTurnStarted]: true,
312 > [ActionType.ChatDelta]: false,
313 > [ActionType.ChatResponsePart]: false,
314 > [ActionType.ChatToolCallStart]: false,
315 > [ActionType.ChatToolCallDelta]: false,
316 > [ActionType.ChatToolCallReady]: false,
317 > [ActionType.ChatToolCallConfirmed]: true,
318 > [ActionType.ChatToolCallComplete]: true,
319 > [ActionType.ChatToolCallResultConfirmed]: true,
320 > [ActionType.ChatToolCallContentChanged]: true,
321 > [ActionType.ChatToolCallAuthRequired]: false,
322 > [ActionType.ChatToolCallAuthResolved]: false,
323 > [ActionType.ChatTurnComplete]: false,
324 > [ActionType.ChatTurnCancelled]: true,
325 > [ActionType.ChatError]: false,
326 > [ActionType.ChatActivityChanged]: false,
327 > [ActionType.ChatWorkingDirectorySet]: true,
328 > [ActionType.ChatWorkingDirectoryRemoved]: true,
329 > [ActionType.ChatUsage]: false,
330 > [ActionType.ChatReasoning]: false,
331 > [ActionType.ChatPendingMessageSet]: true,
332 > [ActionType.ChatPendingMessageRemoved]: true,
333 > [ActionType.ChatQueuedMessagesReordered]: true,
334 > [ActionType.ChatDraftChanged]: true,
335 > [ActionType.ChatInputRequested]: false,
336 > [ActionType.ChatInputAnswerChanged]: true,
337 > [ActionType.ChatInputCompleted]: true,
338 > [ActionType.ChatTruncated]: true,
339 > [ActionType.ChatTurnsLoaded]: false,
340 > [ActionType.ChangesetStatusChanged]: false,
341 > [ActionType.ChangesetFileSet]: false,
342 > [ActionType.ChangesetFileRemoved]: false,
343 > [ActionType.ChangesetFilesReviewChanged]: true,
344 > [ActionType.ChangesetContentChanged]: false,
345 > [ActionType.ChangesetOperationsChanged]: false,
346 > [ActionType.ChangesetOperationStatusChanged]: false,
347 > [ActionType.ChangesetCleared]: false,
348 > [ActionType.AnnotationsSet]: true,
349 > [ActionType.AnnotationsUpdated]: true,
350 > [ActionType.AnnotationsRemoved]: true,
351 > [ActionType.AnnotationsEntrySet]: true,
352 > [ActionType.AnnotationsEntryRemoved]: true,
353 > [ActionType.TerminalData]: false,
354 > [ActionType.TerminalInput]: true,
355 > [ActionType.TerminalResized]: true,
356 > [ActionType.TerminalClaimed]: true,
357 > [ActionType.TerminalTitleChanged]: true,
358 > [ActionType.TerminalCwdChanged]: false,
359 > [ActionType.TerminalExited]: false,
360 > [ActionType.TerminalCleared]: true,
361 > [ActionType.TerminalCommandDetectionAvailable]: false,
362 > [ActionType.TerminalCommandExecuted]: false,
363 > [ActionType.TerminalCommandFinished]: false,
364 > [ActionType.ResourceWatchChanged]: false,
365 > };
src/vs/base/common/stream.ts 325 covered LOC · 29 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- stream.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken } from './cancellation.js';
7 > import { onUnexpectedError } from './errors.js';
8 > import { DisposableStore, toDisposable } from './lifecycle.js';
9 >
10 > /**
11 > * The payload that flows in readable stream events.
12 > */
13 > export type ReadableStreamEventPayload<T> = T | Error | 'end';
14 >
15 > export interface ReadableStreamEvents<T> {
16 >
17 > /**
18 > * The 'data' event is emitted whenever the stream is
19 > * relinquishing ownership of a chunk of data to a consumer.
20 > *
21 > * NOTE: PLEASE UNDERSTAND THAT ADDING A DATA LISTENER CAN
22 > * TURN THE STREAM INTO FLOWING MODE. IT IS THEREFOR THE
23 > * LAST LISTENER THAT SHOULD BE ADDED AND NOT THE FIRST
24 > *
25 > * Use `listenStream` as a helper method to listen to
26 > * stream events in the right order.
27 > */
28 > on(event: 'data', callback: (data: T) => void): void;
29 >
30 > /**
31 > * Emitted when any error occurs.
32 > */
33 > on(event: 'error', callback: (err: Error) => void): void;
34 >
35 > /**
36 > * The 'end' event is emitted when there is no more data
37 > * to be consumed from the stream. The 'end' event will
38 > * not be emitted unless the data is completely consumed.
39 > */
40 > on(event: 'end', callback: () => void): void;
41 > }
42 >
43 > /**
44 > * A interface that emulates the API shape of a node.js readable
45 > * stream for use in native and web environments.
46 > */
47 > export interface ReadableStream<T> extends ReadableStreamEvents<T> {
48 >
49 > /**
50 > * Stops emitting any events until resume() is called.
51 > */
52 > pause(): void;
53 >
54 > /**
55 > * Starts emitting events again after pause() was called.
56 > */
57 > resume(): void;
58 >
59 > /**
60 > * Destroys the stream and stops emitting any event.
61 > */
62 > destroy(): void;
63 >
64 > /**
65 > * Allows to remove a listener that was previously added.
66 > */
67 > removeListener(event: string, callback: Function): void;
68 > }
69 >
70 > /**
71 > * A interface that emulates the API shape of a node.js readable
72 > * for use in native and web environments.
73 > */
74 > export interface Readable<T> {
75 >
76 > /**
77 > * Read data from the underlying source. Will return
78 > * null to indicate that no more data can be read.
79 > */
80 > read(): T | null;
81 > }
82 >
83 > export function isReadable<T>(obj: unknown): obj is Readable<T> {
84 const candidate = obj as Readable<T> | undefined;
85 if (!candidate) {
89 return typeof candidate.read === 'function';
90 }
91 > stream.ts
92 > /**
93 > * A interface that emulates the API shape of a node.js writeable
94 > * stream for use in native and web environments.
95 > */
96 > export interface WriteableStream<T> extends ReadableStream<T> {
97 >
98 > /**
99 > * Writing data to the stream will trigger the on('data')
100 > * event listener if the stream is flowing and buffer the
101 > * data otherwise until the stream is flowing.
102 > *
103 > * If a `highWaterMark` is configured and writing to the
104 > * stream reaches this mark, a promise will be returned
105 > * that should be awaited on before writing more data.
106 > * Otherwise there is a risk of buffering a large number
107 > * of data chunks without consumer.
108 > */
109 > write(data: T): void | Promise<void>;
110 >
111 > /**
112 > * Signals an error to the consumer of the stream via the
113 > * on('error') handler if the stream is flowing.
114 > *
115 > * NOTE: call `end` to signal that the stream has ended,
116 > * this DOES NOT happen automatically from `error`.
117 > */
118 > error(error: Error): void;
119 >
120 > /**
121 > * Signals the end of the stream to the consumer. If the
122 > * result is provided, will trigger the on('data') event
123 > * listener if the stream is flowing and buffer the data
124 > * otherwise until the stream is flowing.
125 > */
126 > end(result?: T): void;
127 > }
128 >
129 > /**
130 > * A stream that has a buffer already read. Returns the original stream
131 > * that was read as well as the chunks that got read.
132 > *
133 > * The `ended` flag indicates if the stream has been fully consumed.
134 > */
135 > export interface ReadableBufferedStream<T> {
136 >
137 > /**
138 > * The original stream that is being read.
139 > */
140 > stream: ReadableStream<T>;
141 >
142 > /**
143 > * An array of chunks already read from this stream.
144 > */
145 > buffer: T[];
146 >
147 > /**
148 > * Signals if the stream has ended or not. If not, consumers
149 > * should continue to read from the stream until consumed.
150 > */
151 > ended: boolean;
152 > }
153 >
154 > export function isReadableStream<T>(obj: unknown): obj is ReadableStream<T> {
155 const candidate = obj as ReadableStream<T> | undefined;
156 if (!candidate) {
160 return [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
161 }
162 > stream.ts
163 > export function isReadableBufferedStream<T>(obj: unknown): obj is ReadableBufferedStream<T> {
164 const candidate = obj as ReadableBufferedStream<T> | undefined;
165 if (!candidate) {
169 return isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean';
170 }
171 > stream.ts
172 > export interface IReducer<T, R = T> {
173 > (data: T[]): R;
174 > }
175 >
176 > export interface IDataTransformer<Original, Transformed> {
177 > (data: Original): Transformed;
178 > }
179 >
180 > export interface IErrorTransformer {
181 > (error: Error): Error;
182 > }
183 >
184 > export interface ITransformer<Original, Transformed> {
185 > data: IDataTransformer<Original, Transformed>;
186 > error?: IErrorTransformer;
187 > }
188 >
189 > export function newWriteableStream<T>(reducer: IReducer<T> | null, options?: WriteableStreamOptions): WriteableStream<T> {
190 return new WriteableStreamImpl<T>(reducer, options);
191 }
192 > stream.ts
193 > export interface WriteableStreamOptions {
194 >
195 > /**
196 > * The number of objects to buffer before WriteableStream#write()
197 > * signals back that the buffer is full. Can be used to reduce
198 > * the memory pressure when the stream is not flowing.
199 > */
200 > highWaterMark?: number;
201 > }
202 >
203 > class WriteableStreamImpl<T> implements WriteableStream<T> {
204 >
205 > private readonly state = {
206 > flowing: false,
207 > ended: false,
208 > destroyed: false
209 > };
210 >
211 > private readonly buffer = {
212 > data: [] as T[],
213 > error: [] as Error[]
214 > };
215 >
216 > private readonly listeners = {
217 > data: [] as { (data: T): void }[],
218 > error: [] as { (error: Error): void }[],
219 > end: [] as { (): void }[]
220 > };
221 >
222 > private readonly pendingWritePromises: Function[] = [];
223 >
224 > /**
225 > * @param reducer a function that reduces the buffered data into a single object;
226 > * because some objects can be complex and non-reducible, we also
227 > * allow passing the explicit `null` value to skip the reduce step
228 > * @param options stream options
229 > */
230 > constructor(private reducer: IReducer<T> | null, private options?: WriteableStreamOptions) { }
231 >
232 > pause(): void {
233 if (this.state.destroyed) {
234 return;
237 this.state.flowing = false;
238 }
239 > stream.ts
240 > resume(): void {
241 if (this.state.destroyed) {
242 return;
252 }
253 }
254 > stream.ts
255 > write(data: T): void | Promise<void> {
256 if (this.state.destroyed) {
257 return;
273 }
274 }
275 > stream.ts
276 > error(error: Error): void {
277 if (this.state.destroyed) {
278 return;
289 }
290 }
291 > stream.ts
292 > end(result?: T): void {
293 if (this.state.destroyed) {
294 return;
312 }
313 }
314 > stream.ts
315 > private emitData(data: T): void {
316 this.listeners.data.slice(0).forEach(listener => listener(data)); // slice to avoid listener mutation from delivering event
317 }
318 > stream.ts
319 > private emitError(error: Error): void {
320 if (this.listeners.error.length === 0) {
321 onUnexpectedError(error); // nobody listened to this error so we log it as unexpected
324 }
325 }
326 > stream.ts
327 > private emitEnd(): void {
328 this.listeners.end.slice(0).forEach(listener => listener()); // slice to avoid listener mutation from delivering event
329 }
330 > stream.ts
331 > on(event: 'data', callback: (data: T) => void): void;
332 > on(event: 'error', callback: (err: Error) => void): void;
333 > on(event: 'end', callback: () => void): void;
334 > on(event: 'data' | 'error' | 'end', callback: ((data: T) => void) | ((err: Error) => void) | (() => void)): void {
335 if (this.state.destroyed) {
336 return;
372 }
373 }
374 > stream.ts
375 > removeListener(event: string, callback: Function): void {
376 if (this.state.destroyed) {
377 return;
401 }
402 }
403 > stream.ts
404 > private flowData(): void {
405 // if buffer is empty, nothing to do
406 if (this.buffer.data.length === 0) {
428 pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise());
429 }
430 > stream.ts
431 > private flowErrors(): void {
432 if (this.listeners.error.length > 0) {
433 for (const error of this.buffer.error) {
438 }
439 }
440 > stream.ts
441 > private flowEnd(): boolean {
442 if (this.state.ended) {
443 this.emitEnd();
448 return false;
449 }
450 > stream.ts
451 > destroy(): void {
452 if (!this.state.destroyed) {
453 this.state.destroyed = true;
464 }
465 }
466 > } stream.ts
467 >
468 > /**
469 > * Helper to fully read a T readable into a T.
470 > */
471 > export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>): T {
472 const chunks: T[] = [];
473
479 return reducer(chunks);
480 }
481 > stream.ts
482 > /**
483 > * Helper to read a T readable up to a maximum of chunks. If the limit is
484 > * reached, will return a readable instead to ensure all data can still
485 > * be read.
486 > */
487 > export function peekReadable<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
488 const chunks: T[] = [];
489
527 };
528 }
529 > stream.ts
530 > /**
531 > * Helper to fully read a T stream into a T or consuming
532 > * a stream fully, awaiting all the events without caring
533 > * about the data.
534 > */
535 > export function consumeStream<T, R = T>(stream: ReadableStreamEvents<T>, reducer: IReducer<T, R>): Promise<R>;
536 > export function consumeStream(stream: ReadableStreamEvents<unknown>): Promise<undefined>;
537 > export function consumeStream<T, R = T>(stream: ReadableStreamEvents<T>, reducer?: IReducer<T, R>): Promise<R | undefined> {
538 return new Promise((resolve, reject) => {
539 const chunks: T[] = [];
562 });
563 }
564 > stream.ts
565 > export interface IStreamListener<T> {
566 >
567 > /**
568 > * The 'data' event is emitted whenever the stream is
569 > * relinquishing ownership of a chunk of data to a consumer.
570 > */
571 > onData(data: T): void;
572 >
573 > /**
574 > * Emitted when any error occurs.
575 > */
576 > onError(err: Error): void;
577 >
578 > /**
579 > * The 'end' event is emitted when there is no more data
580 > * to be consumed from the stream. The 'end' event will
581 > * not be emitted unless the data is completely consumed.
582 > */
583 > onEnd(): void;
584 > }
585 >
586 > /**
587 > * Helper to listen to all events of a T stream in proper order.
588 > */
589 > export function listenStream<T>(stream: ReadableStreamEvents<T>, listener: IStreamListener<T>, token?: CancellationToken): void {
590
591 stream.on('error', error => {
610 });
611 }
612 > stream.ts
613 > /**
614 > * Helper to peek up to `maxChunks` into a stream. The return type signals if
615 > * the stream has ended or not. If not, caller needs to add a `data` listener
616 > * to continue reading.
617 > */
618 > export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Promise<ReadableBufferedStream<T>> {
619 return new Promise((resolve, reject) => {
620 const streamListeners = new DisposableStore();
666 });
667 }
668 > stream.ts
669 > /**
670 > * Helper to create a readable stream from an existing T.
671 > */
672 > export function toStream<T>(t: T, reducer: IReducer<T>): ReadableStream<T> {
673 const stream = newWriteableStream<T>(reducer);
674
677 return stream;
678 }
679 > stream.ts
680 > /**
681 > * Helper to create an empty stream
682 > */
683 > export function emptyStream(): ReadableStream<never> {
684 const stream = newWriteableStream<never>(() => { throw new Error('not supported'); });
685 stream.end();
687 return stream;
688 }
689 > stream.ts
690 > /**
691 > * Helper to convert a T into a Readable<T>.
692 > */
693 > export function toReadable<T>(t: T): Readable<T> {
694 let consumed = false;
695
706 };
707 }
708 > stream.ts
709 > /**
710 > * Helper to transform a readable stream into another stream.
711 > */
712 > export function transform<Original, Transformed>(stream: ReadableStreamEvents<Original>, transformer: ITransformer<Original, Transformed>, reducer: IReducer<Transformed>): ReadableStream<Transformed> {
713 const target = newWriteableStream<Transformed>(reducer);
714
721 return target;
722 }
723 > stream.ts
724 > /**
725 > * Helper to take an existing readable that will
726 > * have a prefix injected to the beginning.
727 > */
728 > export function prefixedReadable<T>(prefix: T, readable: Readable<T>, reducer: IReducer<T>): Readable<T> {
729 let prefixHandled = false;
730
751 };
752 }
753 > stream.ts
754 > /**
755 > * Helper to take an existing stream that will
756 > * have a prefix injected to the beginning.
757 > */
758 > export function prefixedStream<T>(prefix: T, stream: ReadableStream<T>, reducer: IReducer<T>): ReadableStream<T> {
759 let prefixHandled = false;
760
src/vs/platform/agentHost/common/state/protocol/channels-session/commands.ts 323 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- commands.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from '../common/state.js';
10 > import type { BaseParams } from '../common/commands.js';
11 > import type { SessionActiveClient } from './state.js';
12 > import type { MessageAttachment } from '../channels-chat/state.js';
13 >
14 > // ─── createSession ───────────────────────────────────────────────────────────
15 >
16 > /**
17 > * Creates a new session with the specified agent provider.
18 > *
19 > * If the session URI already exists, the server MUST return an error with code
20 > * `-32003` (`SessionAlreadyExists`).
21 > *
22 > * After creation, the client should subscribe to the session URI to receive state
23 > * updates. The server also broadcasts a `root/sessionAdded` notification to all
24 > * clients.
25 > *
26 > * @category Commands
27 > * @method createSession
28 > * @direction Client → Server
29 > * @messageType Request
30 > * @version 1
31 > * @example
32 > * ```jsonc
33 > * // Client → Server
34 > * { "jsonrpc": "2.0", "id": 2, "method": "createSession",
35 > * "params": { "channel": "ahp-session:/<uuid>", "provider": "copilot" } }
36 > *
37 > * // Server → Client (success)
38 > * { "jsonrpc": "2.0", "id": 2, "result": null }
39 > *
40 > * // Server → Client (failure — provider not found)
41 > * { "jsonrpc": "2.0", "id": 2, "error": { "code": -32002, "message": "No agent for provider" } }
42 > *
43 > * // Server → Client (failure — session already exists)
44 > * { "jsonrpc": "2.0", "id": 2, "error": { "code": -32003, "message": "Session already exists" } }
45 > * ```
46 > */
47 > /**
48 > * Identifies a source session and turn to fork from.
49 > *
50 > * When provided in `createSession`, the server populates the new session with
51 > * content from the source session up to and including the response of the
52 > * specified turn.
53 > */
54 > export interface SessionForkSource {
55 > /** URI of the existing session to fork from */
56 > session: URI;
57 > /** Turn ID in the source session; content up to and including this turn's response is copied */
58 > turnId: string;
59 > }
60 >
61 > export interface CreateSessionParams extends BaseParams {
62 > /** Session URI (client-chosen, e.g. `ahp-session:/<uuid>`) */
63 > channel: URI;
64 > /** Agent provider ID */
65 > provider?: string;
66 > /**
67 > * The working directories the session's agent is granted tool access to.
68 > * A session may span multiple directories; they are equal peers except when
69 > * the agent advertises
70 > * {@link MultipleWorkingDirectoriesCapability.requiresPrimary}, in which case
71 > * one of them should be designated the primary via
72 > * {@link primaryWorkingDirectory}.
73 > *
74 > * A client MUST NOT supply more than one entry unless the agent advertises
75 > * {@link AgentCapabilities.multipleWorkingDirectories}; a server without that
76 > * capability treats only the first entry as the session's working directory
77 > * and ignores the rest. Dispatch `session/workingDirectorySet` /
78 > * `session/workingDirectoryRemoved` to change the set after the session has
79 > * started.
80 > *
81 > * Ignored for forked sessions — a fork inherits its working directories
82 > * from the source session identified by `fork`.
83 > */
84 > workingDirectories?: URI[];
85 > /**
86 > * The primary working directory for the session's **default chat**.
87 > *
88 > * A session has no primary of its own — primary is a per-chat notion (see
89 > * {@link ChatState.primaryWorkingDirectory}). But `createSession` implicitly
90 > * creates the session's default chat, and there is no separate `createChat`
91 > * call to carry that chat's create-time fields. This field is therefore the
92 > * only place a client can designate the **default chat's** primary at birth;
93 > * it is copied into that chat's read-only `primaryWorkingDirectory`. For any
94 > * non-default chat, pass {@link CreateChatParams.primaryWorkingDirectory}
95 > * instead.
96 > *
97 > * When set, it MUST be one of {@link workingDirectories}. A client SHOULD
98 > * supply this when the agent advertises
99 > * {@link MultipleWorkingDirectoriesCapability.requiresPrimary}; a host MAY
100 > * reject creation that omits it, or fall back to the first entry of
101 > * `workingDirectories`. Ignored for forked sessions (a fork inherits the
102 > * source session's chats and their primaries).
103 > */
104 > primaryWorkingDirectory?: URI;
105 > /**
106 > * Fork from an existing session. The new session is populated with content
107 > * from the source session up to and including the specified turn's response.
108 > */
109 > fork?: SessionForkSource;
110 > /**
111 > * Agent-specific configuration values collected via `resolveSessionConfig`.
112 > * Keys and values correspond to the schema returned by the server.
113 > */
114 > config?: Record<string, unknown>;
115 > /**
116 > * Eagerly claim an active client role for the new session.
117 > *
118 > * When provided, the server initializes the session with this client as an
119 > * active client, equivalent to dispatching a `session/activeClientSet`
120 > * action immediately after creation. The `clientId` MUST match the
121 > * `clientId` the creating client supplied in `initialize`.
122 > */
123 > activeClient?: SessionActiveClient;
124 > /**
125 > * Opt-in progress token. When set, the client is offering to receive
126 > * `progress` notifications (see `ProgressParams`) for any long-running work
127 > * the server does to bring this session up — most notably the lazy,
128 > * first-use download of the provider's native SDK. The server echoes this
129 > * exact token on every `progress` frame so the client can correlate it to
130 > * this `createSession` call (and the UI awaiting it).
131 > *
132 > * The token MUST be unique across the client's active requests. The server
133 > * MAY ignore it (e.g. when nothing long-running is needed), in which case no
134 > * `progress` notifications are emitted.
135 > */
136 > progressToken?: string;
137 > }
138 >
139 > // ─── disposeSession ──────────────────────────────────────────────────────────
140 >
141 > /**
142 > * Disposes a session and cleans up server-side resources.
143 > *
144 > * The server broadcasts a `root/sessionRemoved` notification to all clients.
145 > *
146 > * @category Commands
147 > * @method disposeSession
148 > * @direction Client → Server
149 > * @messageType Request
150 > * @version 1
151 > */
152 > export interface DisposeSessionParams extends BaseParams { }
153 >
154 > // ─── fetchTurns ──────────────────────────────────────────────────────────────
155 >
156 > /**
157 > * Requests that the host load older historical turns into a chat state.
158 > *
159 > * The command result does not carry turns. Instead, before responding, the host
160 > * MUST dispatch `chat/turnsLoaded` to insert any loaded turns into the chat
161 > * channel's `turns` state, ahead of the already-loaded window, and update or
162 > * clear `turnsNextCursor`.
163 > *
164 > * Before applying any operation that references a turn outside the currently
165 > * loaded window, the host MUST eagerly load enough older turns into state for
166 > * that operation to reduce against valid state.
167 > *
168 > * @category Commands
169 > * @method fetchTurns
170 > * @direction Client → Server
171 > * @messageType Request
172 > * @version 1
173 > * @example
174 > * ```jsonc
175 > * // Client → Server (load the next page indicated by ChatState.turnsNextCursor)
176 > * { "jsonrpc": "2.0", "id": 8, "method": "fetchTurns",
177 > * "params": { "channel": "ahp-chat:/<uuid>", "cursor": "opaque-cursor" } }
178 > *
179 > * // Server updates chat state, then responds
180 > * { "jsonrpc": "2.0", "id": 8, "result": {} }
181 > * ```
182 > */
183 > export interface FetchTurnsParams extends BaseParams {
184 > /** Chat URI */
185 > channel: URI;
186 > /**
187 > * Opaque cursor from `ChatState.turnsNextCursor`.
188 > *
189 > * The host MUST reject unrecognised cursors with `InvalidParams`. Omit only
190 > * when asking the host to opportunistically load its next older page for the
191 > * chat, if any.
192 > */
193 > cursor?: string;
194 > }
195 >
196 > /**
197 > * Result of the `fetchTurns` command.
198 > */
199 > export interface FetchTurnsResult { }
200 >
201 > // ─── completions ─────────────────────────────────────────────────────────────
202 >
203 > /**
204 > * The kind of completion items being requested.
205 > *
206 > * @category Commands
207 > */
208 > export const enum CompletionItemKind {
209 > /**
210 > * Completions for the text of a {@link Message} the user is composing.
211 > * Each returned item carries an attachment that gets associated with the
212 > * message when accepted.
213 > */
214 > UserMessage = 'userMessage',
215 > }
216 >
217 > /**
218 > * Requests completion items for a partially-typed input (e.g. a user message
219 > * the user is currently composing). Used to power `@`-mention pickers,
220 > * file/symbol references, and similar inline-completion experiences.
221 > *
222 > * Servers SHOULD treat this command as best-effort and return promptly. The
223 > * client SHOULD debounce calls to avoid flooding the server with requests on
224 > * every keystroke.
225 > *
226 > * @category Commands
227 > * @method completions
228 > * @direction Client → Server
229 > * @messageType Request
230 > * @version 1
231 > * @example
232 > * ```jsonc
233 > * // User has typed "look at @foo" and the cursor is just after "@foo".
234 > * // Client → Server
235 > * { "jsonrpc": "2.0", "id": 12, "method": "completions",
236 > * "params": { "kind": "userMessage", "channel": "ahp-chat:/<uuid>",
237 > * "text": "look at @foo", "offset": 12 } }
238 > *
239 > * // Server → Client
240 > * { "jsonrpc": "2.0", "id": 12, "result": {
241 > * "items": [
242 > * {
243 > * "insertText": "@foo.ts",
244 > * "rangeStart": 8,
245 > * "rangeEnd": 12,
246 > * "attachment": {
247 > * "type": "resource",
248 > * "label": "foo.ts",
249 > * "displayKind": "document",
250 > * "uri": "file:///workspace/foo.ts"
251 > * }
252 > * }
253 > * ]
254 > * }}
255 > * ```
256 > */
257 > export interface CompletionsParams extends BaseParams {
258 > /** What kind of completion is being requested. */
259 > kind: CompletionItemKind;
260 > /** The chat URI the completion is being requested for. */
261 > channel: URI;
262 > /**
263 > * The complete text of the input being completed (e.g. the full user
264 > * message text typed so far).
265 > */
266 > text: string;
267 > /**
268 > * The character offset within `text` at which the completion is requested,
269 > * measured in UTF-16 code units. MUST satisfy `0 <= offset <= text.length`.
270 > */
271 > offset: number;
272 > }
273 >
274 > /**
275 > * A single completion item returned by the `completions` command.
276 > *
277 > * When the user accepts an item, the client SHOULD:
278 > * 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText`
279 > * (or insert `insertText` at the cursor when the range is omitted).
280 > * 2. Associate the item's `attachment` with the resulting {@link Message}.
281 > *
282 > * @category Commands
283 > */
284 > export interface CompletionItem {
285 > /**
286 > * The text inserted into the input when this item is accepted.
287 > */
288 > insertText: string;
289 >
290 > /**
291 > * If defined, the start of the range in the input's `text` that is replaced
292 > * by `insertText`. The range is the half-open interval
293 > * `[rangeStart, rangeEnd)` of character offsets, measured in UTF-16 code
294 > * units.
295 > *
296 > * When omitted, the client SHOULD insert `insertText` at the cursor.
297 > *
298 > * Note: this range refers to positions in the *current* input. The
299 > * attachment's own `rangeStart`/`rangeEnd` (when present) refer to
300 > * positions in the final {@link Message.text} after the item is
301 > * accepted.
302 > */
303 > rangeStart?: number;
304 >
305 > /**
306 > * The end of the range in the input's `text` that is replaced by
307 > * `insertText`. See {@link rangeStart}.
308 > */
309 > rangeEnd?: number;
310 >
311 > /**
312 > * The attachment associated with this completion item.
313 > */
314 > attachment: MessageAttachment;
315 > }
316 >
317 > /**
318 > * Result of the `completions` command.
319 > */
320 > export interface CompletionsResult {
321 > /** The completion items, in the order the server suggests displaying them. */
322 > items: CompletionItem[];
323 > }
src/vs/base/common/map.ts 319 covered LOC · 98 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- map.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { URI } from './uri.js';
7 >
8 > export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
9 let result = map.get(key);
10 if (result === undefined) {
15 return result;
16 }
17 > map.ts
18 > export function mapToString<K, V>(map: Map<K, V>): string {
19 const entries: string[] = [];
20 map.forEach((value, key) => {
24 return `Map(${map.size}) {${entries.join(', ')}}`;
25 }
26 > map.ts
27 > export function setToString<K>(set: Set<K>): string {
28 const entries: K[] = [];
29 set.forEach(value => {
33 return `Set(${set.size}) {${entries.join(', ')}}`;
34 }
35 > map.ts
36 > interface ResourceMapKeyFn {
37 > (resource: URI): string;
38 > }
39 >
40 > class ResourceMapEntry<T> {
41 > constructor(readonly uri: URI, readonly value: T) { }
42 > }
43 >
44 function isEntries<T>(arg: ResourceMap<T> | ResourceMapKeyFn | readonly (readonly [URI, T])[] | undefined): arg is readonly (readonly [URI, T])[] {
45 return Array.isArray(arg);
46 }
47 > map.ts
48 > export class ResourceMap<T> implements Map<URI, T> {
49 >
50 > private static readonly defaultToKey = (resource: URI) => resource.toString();
51 >
52 > readonly [Symbol.toStringTag] = 'ResourceMap';
53 >
54 > private readonly map: Map<string, ResourceMapEntry<T>>;
55 > private readonly toKey: ResourceMapKeyFn;
56 >
57 > /**
58 > *
59 > * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
60 > */
61 > constructor(toKey?: ResourceMapKeyFn);
62 >
63 > /**
64 > *
65 > * @param other Another resource which this maps is created from
66 > * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
67 > */
68 > constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
69 >
70 > /**
71 > *
72 > * @param other Another resource which this maps is created from
73 > * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
74 > */
75 > constructor(entries?: readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn);
76 >
77 > constructor(arg?: ResourceMap<T> | ResourceMapKeyFn | readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn) {
78 if (arg instanceof ResourceMap) {
79 this.map = new Map(arg.map);
91 }
92 }
93 > map.ts
94 > set(resource: URI, value: T): this {
95 this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value));
96 return this;
97 }
98 > map.ts
99 > get(resource: URI): T | undefined {
100 return this.map.get(this.toKey(resource))?.value;
101 }
102 > map.ts
103 > has(resource: URI): boolean {
104 return this.map.has(this.toKey(resource));
105 }
106 > map.ts
107 > get size(): number {
108 return this.map.size;
109 }
110 > map.ts
111 > clear(): void {
112 this.map.clear();
113 }
114 > map.ts
115 > delete(resource: URI): boolean {
116 return this.map.delete(this.toKey(resource));
117 }
118 > map.ts
119 > forEach(clb: (value: T, key: URI, map: Map<URI, T>) => void, thisArg?: object): void {
120 if (typeof thisArg !== 'undefined') {
121 clb = clb.bind(thisArg);
125 }
126 }
127 > map.ts
128 > *values(): MapIterator<T> {
129 for (const entry of this.map.values()) {
130 yield entry.value;
131 }
132 }
133 > map.ts
134 > *keys(): MapIterator<URI> {
135 for (const entry of this.map.values()) {
136 yield entry.uri;
137 }
138 }
139 > map.ts
140 > *entries(): MapIterator<[URI, T]> {
141 for (const entry of this.map.values()) {
142 yield [entry.uri, entry.value];
143 }
144 }
145 > map.ts
146 > *[Symbol.iterator](): MapIterator<[URI, T]> {
147 for (const [, entry] of this.map) {
148 yield [entry.uri, entry.value];
149 }
150 }
151 > } map.ts
152 >
153 > export class ResourceSet implements Set<URI> {
154 >
155 > readonly [Symbol.toStringTag]: string = 'ResourceSet';
156 >
157 > private readonly _map: ResourceMap<URI>;
158 >
159 > constructor(toKey?: ResourceMapKeyFn);
160 > constructor(entries: readonly URI[], toKey?: ResourceMapKeyFn);
161 > constructor(entriesOrKey?: readonly URI[] | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
162 if (!entriesOrKey || typeof entriesOrKey === 'function') {
163 this._map = new ResourceMap(entriesOrKey);
167 }
168 }
169 > map.ts
170 >
171 > get size(): number {
172 return this._map.size;
173 }
174 > map.ts
175 > add(value: URI): this {
176 this._map.set(value, value);
177 return this;
178 }
179 > map.ts
180 > clear(): void {
181 this._map.clear();
182 }
183 > map.ts
184 > delete(value: URI): boolean {
185 return this._map.delete(value);
186 }
187 > map.ts
188 > forEach(callbackfn: (value: URI, value2: URI, set: Set<URI>) => void, thisArg?: unknown): void {
189 this._map.forEach((_value, key) => callbackfn.call(thisArg, key, key, this));
190 }
191 > map.ts
192 > has(value: URI): boolean {
193 return this._map.has(value);
194 }
195 > map.ts
196 > entries(): SetIterator<[URI, URI]> {
197 return this._map.entries() as unknown as SetIterator<[URI, URI]>;
198 }
199 > map.ts
200 > keys(): SetIterator<URI> {
201 return this._map.keys() as unknown as SetIterator<URI>;
202 }
203 > map.ts
204 > values(): SetIterator<URI> {
205 return this._map.keys() as unknown as SetIterator<URI>;
206 }
207 > map.ts
208 > [Symbol.iterator](): SetIterator<URI> {
209 return this.keys();
210 }
211 > } map.ts
212 >
213 >
214 > interface Item<K, V> {
215 > previous: Item<K, V> | undefined;
216 > next: Item<K, V> | undefined;
217 > key: K;
218 > value: V;
219 > }
220 >
221 > export const enum Touch {
222 > None = 0,
223 > AsOld = 1,
224 > AsNew = 2
225 > }
226 >
227 > export class LinkedMap<K, V> implements Map<K, V> {
228 >
229 > readonly [Symbol.toStringTag] = 'LinkedMap';
230 >
231 > private _map: Map<K, Item<K, V>>;
232 > private _head: Item<K, V> | undefined;
233 > private _tail: Item<K, V> | undefined;
234 > private _size: number;
235 >
236 > private _state: number;
237 >
238 > constructor() {
239 > this._map = new Map<K, Item<K, V>>(); map.ts
240 > this._head = undefined;
241 > this._tail = undefined;
242 > this._size = 0;
243 > this._state = 0;
244 > }
245 > map.ts
246 > clear(): void {
247 this._map.clear();
248 this._head = undefined;
251 this._state++;
252 }
253 > map.ts
254 > isEmpty(): boolean {
255 return !this._head && !this._tail;
256 }
257 > map.ts
258 > get size(): number {
259 return this._size;
260 }
261 > map.ts
262 > get first(): V | undefined {
263 return this._head?.value;
264 }
265 > map.ts
266 > get last(): V | undefined {
267 return this._tail?.value;
268 }
269 > map.ts
270 > has(key: K): boolean {
271 return this._map.has(key);
272 }
273 > map.ts
274 > get(key: K, touch: Touch = Touch.None): V | undefined {
275 const item = this._map.get(key);
276 if (!item) {
282 return item.value;
283 }
284 > map.ts
285 > set(key: K, value: V, touch: Touch = Touch.None): this {
286 let item = this._map.get(key);
287 if (item) {
311 return this;
312 }
313 > map.ts
314 > delete(key: K): boolean {
315 return !!this.remove(key);
316 }
317 > map.ts
318 > remove(key: K): V | undefined {
319 const item = this._map.get(key);
320 if (!item) {
326 return item.value;
327 }
328 > map.ts
329 > shift(): V | undefined {
330 if (!this._head && !this._tail) {
331 return undefined;
340 return item.value;
341 }
342 > map.ts
343 > forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: unknown): void {
344 const state = this._state;
345 let current = this._head;
356 }
357 }
358 > map.ts
359 > keys(): MapIterator<K> {
360 const map = this;
361 const state = this._state;
381 return iterator;
382 }
383 > map.ts
384 > values(): MapIterator<V> {
385 const map = this;
386 const state = this._state;
406 return iterator;
407 }
408 > map.ts
409 > entries(): MapIterator<[K, V]> {
410 const map = this;
411 const state = this._state;
431 return iterator;
432 }
433 > map.ts
434 > [Symbol.iterator](): MapIterator<[K, V]> {
435 return this.entries();
436 }
437 > map.ts
438 > protected trimOld(newSize: number) {
439 if (newSize >= this.size) {
440 return;
458 this._state++;
459 }
460 > map.ts
461 > protected trimNew(newSize: number) {
462 if (newSize >= this.size) {
463 return;
481 this._state++;
482 }
483 > map.ts
484 > private addItemFirst(item: Item<K, V>): void {
485 // First time Insert
486 if (!this._head && !this._tail) {
495 this._state++;
496 }
497 > map.ts
498 > private addItemLast(item: Item<K, V>): void {
499 // First time Insert
500 if (!this._head && !this._tail) {
509 this._state++;
510 }
511 > map.ts
512 > private removeItem(item: Item<K, V>): void {
513 if (item === this._head && item === this._tail) {
514 this._head = undefined;
546 this._state++;
547 }
548 > map.ts
549 > private touch(item: Item<K, V>, touch: Touch): void {
550 if (!this._head || !this._tail) {
551 throw new Error('Invalid list');
608 }
609 }
610 > map.ts
611 > toJSON(): [K, V][] {
612 const data: [K, V][] = [];
613
618 return data;
619 }
620 > map.ts
621 > fromJSON(data: [K, V][]): void {
622 this.clear();
623
626 }
627 }
628 > } map.ts
629 >
630 > abstract class Cache<K, V> extends LinkedMap<K, V> {
631 >
632 > protected _limit: number;
633 > protected _ratio: number;
634 >
635 > constructor(limit: number, ratio: number = 1) {
636 super();
637 this._limit = limit;
638 this._ratio = Math.min(Math.max(0, ratio), 1);
639 }
640 > map.ts
641 > get limit(): number {
642 return this._limit;
643 }
644 > map.ts
645 > set limit(limit: number) {
646 this._limit = limit;
647 this.checkTrim();
648 }
649 > map.ts
650 > get ratio(): number {
651 return this._ratio;
652 }
653 > map.ts
654 > set ratio(ratio: number) {
655 this._ratio = Math.min(Math.max(0, ratio), 1);
656 this.checkTrim();
657 }
658 > map.ts
659 > override get(key: K, touch: Touch = Touch.AsNew): V | undefined {
660 return super.get(key, touch);
661 }
662 > map.ts
663 > peek(key: K): V | undefined {
664 return super.get(key, Touch.None);
665 }
666 > map.ts
667 > override set(key: K, value: V): this {
668 super.set(key, value, Touch.AsNew);
669 return this;
670 }
671 > map.ts
672 > protected checkTrim() {
673 if (this.size > this._limit) {
674 this.trim(Math.round(this._limit * this._ratio));
675 }
676 }
677 > map.ts
678 > protected abstract trim(newSize: number): void;
679 > }
680 >
681 > export class LRUCache<K, V> extends Cache<K, V> {
682 >
683 > constructor(limit: number, ratio: number = 1) {
684 super(limit, ratio);
685 }
686 > map.ts
687 > protected override trim(newSize: number) {
688 this.trimOld(newSize);
689 }
690 > map.ts
691 > override set(key: K, value: V): this {
692 super.set(key, value);
693 this.checkTrim();
694 return this;
695 }
696 > } map.ts
697 >
698 > export class MRUCache<K, V> extends Cache<K, V> {
699 >
700 > constructor(limit: number, ratio: number = 1) {
701 super(limit, ratio);
702 }
703 > map.ts
704 > protected override trim(newSize: number) {
705 this.trimNew(newSize);
706 }
707 > map.ts
708 > override set(key: K, value: V): this {
709 if (this._limit <= this.size && !this.has(key)) {
710 this.trim(Math.round(this._limit * this._ratio) - 1);
714 return this;
715 }
716 > } map.ts
717 >
718 > export class CounterSet<T> {
719
720 private map = new Map<T, number>();
721 > map.ts
722 > add(value: T): CounterSet<T> {
723 this.map.set(value, (this.map.get(value) || 0) + 1);
724 return this;
725 }
726 > map.ts
727 > delete(value: T): boolean {
728 let counter = this.map.get(value) || 0;
729
742 return true;
743 }
744 > map.ts
745 > has(value: T): boolean {
746 return this.map.has(value);
747 }
748 > } map.ts
749 >
750 > /**
751 > * A map that allows access both by keys and values.
752 > * **NOTE**: values need to be unique.
753 > */
754 > export class BidirectionalMap<K, V> {
755 >
756 > private readonly _m1 = new Map<K, V>();
757 > private readonly _m2 = new Map<V, K>();
758 >
759 > constructor(entries?: readonly (readonly [K, V])[]) {
760 if (entries) {
761 for (const [key, value] of entries) {
764 }
765 }
766 > map.ts
767 > clear(): void {
768 this._m1.clear();
769 this._m2.clear();
770 }
771 > map.ts
772 > set(key: K, value: V): void {
773 this._m1.set(key, value);
774 this._m2.set(value, key);
775 }
776 > map.ts
777 > get(key: K): V | undefined {
778 return this._m1.get(key);
779 }
780 > map.ts
781 > getKey(value: V): K | undefined {
782 return this._m2.get(value);
783 }
784 > map.ts
785 > delete(key: K): boolean {
786 const value = this._m1.get(key);
787 if (value === undefined) {
792 return true;
793 }
794 > map.ts
795 > forEach(callbackfn: (value: V, key: K, map: BidirectionalMap<K, V>) => void, thisArg?: unknown): void {
796 this._m1.forEach((value, key) => {
797 callbackfn.call(thisArg, value, key, this);
798 });
799 }
800 > map.ts
801 > keys(): IterableIterator<K> {
802 return this._m1.keys();
803 }
804 > map.ts
805 > values(): IterableIterator<V> {
806 return this._m1.values();
807 }
808 > } map.ts
809 >
810 > export class SetMap<K, V> {
811
812 private map = new Map<K, Set<V>>();
813 > map.ts
814 > add(key: K, value: V): void {
815 let values = this.map.get(key);
816
822 values.add(value);
823 }
824 > map.ts
825 > delete(key: K, value: V): void {
826 const values = this.map.get(key);
827
836 }
837 }
838 > map.ts
839 > forEach(key: K, fn: (value: V) => void): void {
840 const values = this.map.get(key);
841
846 values.forEach(fn);
847 }
848 > map.ts
849 > get(key: K): ReadonlySet<V> {
850 const values = this.map.get(key);
851 if (!values) {
854 return values;
855 }
856 > } map.ts
857 >
858 > export function mapsStrictEqualIgnoreOrder(a: Map<unknown, unknown>, b: Map<unknown, unknown>): boolean {
859 if (a === b) {
860 return true;
879 return true;
880 }
881 > map.ts
882 > /**
883 > * A map that is addressable with an arbitrary number of keys. This is useful in high performance
884 > * scenarios where creating a composite key whenever the data is accessed is too expensive. For
885 > * example for a very hot function, constructing a string like `first-second-third` for every call
886 > * will cause a significant hit to performance.
887 > */
888 > export class NKeyMap<TValue, TKeys extends (string | boolean | number)[]> {
889 private _data: Map<any, any> = new Map();
890 > map.ts
891 > /**
892 > * Sets a value on the map. Note that unlike a standard `Map`, the first argument is the value.
893 > * This is because the spread operator is used for the keys and must be last..
894 > * @param value The value to set.
895 > * @param keys The keys for the value.
896 > */
897 > public set(value: TValue, ...keys: [...TKeys]): void {
898 let currentMap = this._data;
899 for (let i = 0; i < keys.length - 1; i++) {
907 currentMap.set(keys[keys.length - 1], value);
908 }
909 > map.ts
910 > public get(...keys: [...TKeys]): TValue | undefined {
911 let currentMap = this._data;
912 for (let i = 0; i < keys.length - 1; i++) {
919 return currentMap.get(keys[keys.length - 1]);
920 }
921 > map.ts
922 > public delete(...keys: [...TKeys]): boolean {
923 const maps: Map<any, any>[] = [this._data];
924 let currentMap = this._data;
939 return deleted;
940 }
941 > map.ts
942 > public deleteAll(...keys: Partial<TKeys>): boolean {
943 if (keys.length === 0) {
944 const hadData = this._data.size > 0;
964 return deleted;
965 }
966 > map.ts
967 > public clear(): void {
968 this._data.clear();
969 }
970 > map.ts
971 > public *getAll(...keys: Partial<TKeys>): IterableIterator<TValue> {
972 let currentMap = this._data;
973 for (const key of keys) {
980 yield* this._values(currentMap);
981 }
982 > map.ts
983 > public *values(): IterableIterator<TValue> {
984 yield* this._values(this._data);
985 }
986 > map.ts
987 > private *_values(map: Map<any, any>): IterableIterator<TValue> {
988 for (const value of map.values()) {
989 if (value instanceof Map) {
994 }
995 }
996 > map.ts
997 > /**
998 > * Get a textual representation of the map for debugging purposes.
999 > */
1000 > public toString(): string {
1001 const printMap = (map: Map<any, any>, depth: number): string => {
1002 let result = '';
1014 return printMap(this._data, 0);
1015 }
1016 > } map.ts
src/vs/base/common/network.ts 292 covered LOC · 20 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- network.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as errors from './errors.js';
7 > import * as platform from './platform.js';
8 > import { equalsIgnoreCase, startsWithIgnoreCase } from './strings.js';
9 > import { URI } from './uri.js';
10 > import * as paths from './path.js';
11 >
12 > export namespace Schemas {
13 >
14 > /**
15 > * A schema that is used for models that exist in memory
16 > * only and that have no correspondence on a server or such.
17 > */
18 > export const inMemory = 'inmemory';
19 >
20 > /**
21 > * A schema that is used for setting files
22 > */
23 > export const vscode = 'vscode';
24 >
25 > /**
26 > * A schema that is used for internal private files
27 > */
28 > export const internal = 'private';
29 >
30 > /**
31 > * A walk-through document.
32 > */
33 > export const walkThrough = 'walkThrough';
34 >
35 > /**
36 > * An embedded code snippet.
37 > */
38 > export const walkThroughSnippet = 'walkThroughSnippet';
39 >
40 > export const http = 'http';
41 >
42 > export const https = 'https';
43 >
44 > export const file = 'file';
45 >
46 > export const mailto = 'mailto';
47 >
48 > export const untitled = 'untitled';
49 >
50 > export const data = 'data';
51 >
52 > export const command = 'command';
53 >
54 > export const vscodeRemote = 'vscode-remote';
55 >
56 > export const vscodeRemoteResource = 'vscode-remote-resource';
57 >
58 > export const vscodeManagedRemoteResource = 'vscode-managed-remote-resource';
59 >
60 > export const vscodeUserData = 'vscode-userdata';
61 >
62 > export const vscodeCustomEditor = 'vscode-custom-editor';
63 >
64 > export const vscodeNotebookCell = 'vscode-notebook-cell';
65 > export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata';
66 > export const vscodeNotebookCellMetadataDiff = 'vscode-notebook-cell-metadata-diff';
67 > export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output';
68 > export const vscodeNotebookCellOutputDiff = 'vscode-notebook-cell-output-diff';
69 > export const vscodeNotebookMetadata = 'vscode-notebook-metadata';
70 > export const vscodeInteractiveInput = 'vscode-interactive-input';
71 >
72 > export const vscodeSettings = 'vscode-settings';
73 >
74 > export const vscodeWorkspaceTrust = 'vscode-workspace-trust';
75 >
76 > export const vscodeTerminal = 'vscode-terminal';
77 >
78 > /** Scheme used for the image carousel editor. */
79 > export const vscodeImageCarousel = 'vscode-image-carousel';
80 >
81 > /** Scheme used for code blocks in chat. */
82 > export const vscodeChatCodeBlock = 'vscode-chat-code-block';
83 >
84 > /** Scheme used for LHS of code compare (aka diff) blocks in chat. */
85 > export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block';
86 >
87 > /** Scheme used for the chat input editor. */
88 > export const vscodeChatEditor = 'vscode-chat-editor';
89 >
90 > /** Scheme used for the chat input part */
91 > export const vscodeChatInput = 'chatSessionInput';
92 >
93 > /** Scheme used for local chat session content */
94 > export const vscodeLocalChatSession = 'vscode-chat-session';
95 >
96 > /**
97 > * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors)
98 > */
99 > export const webviewPanel = 'webview-panel';
100 >
101 > /**
102 > * Scheme used for loading the wrapper html and script in webviews.
103 > */
104 > export const vscodeWebview = 'vscode-webview';
105 >
106 > /**
107 > * Scheme used for integrated browser tabs using WebContentsView.
108 > */
109 > export const vscodeBrowser = 'vscode-browser';
110 >
111 > /**
112 > * Scheme used for extension pages
113 > */
114 > export const extension = 'extension';
115 >
116 > /**
117 > * Scheme used as a replacement of `file` scheme to load
118 > * files with our custom protocol handler (desktop only).
119 > */
120 > export const vscodeFileResource = 'vscode-file';
121 >
122 > /**
123 > * Scheme used for temporary resources
124 > */
125 > export const tmp = 'tmp';
126 >
127 > /**
128 > * Scheme used vs live share
129 > */
130 > export const vsls = 'vsls';
131 >
132 > /**
133 > * Scheme used for the Source Control commit input's text document
134 > */
135 > export const vscodeSourceControl = 'vscode-scm';
136 >
137 > /**
138 > * Scheme used for input box for creating comments.
139 > */
140 > export const commentsInput = 'comment';
141 >
142 > /**
143 > * Scheme used for special rendering of settings in the release notes
144 > */
145 > export const codeSetting = 'code-setting';
146 >
147 > /**
148 > * Scheme used for output panel resources
149 > */
150 > export const outputChannel = 'output';
151 >
152 > /**
153 > * Scheme used for the accessible view
154 > */
155 > export const accessibleView = 'accessible-view';
156 >
157 > /**
158 > * Used for snapshots of chat edits
159 > */
160 > export const chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model';
161 > export const chatEditingModel = 'chat-editing-text-model';
162 >
163 > /**
164 > * Used for rendering multidiffs in copilot agent sessions
165 > */
166 > export const copilotPr = 'copilot-pr';
167 > }
168 >
169 > export function matchesScheme(target: URI | string, scheme: string): boolean {
170 if (URI.isUri(target)) {
171 return equalsIgnoreCase(target.scheme, scheme);
174 }
175 }
176 > network.ts
177 > export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean {
178 return schemes.some(scheme => matchesScheme(target, scheme));
179 }
180 > network.ts
181 > export const connectionTokenCookieName = 'vscode-tkn';
182 > export const connectionTokenQueryName = 'tkn';
183 >
184 > class RemoteAuthoritiesImpl {
185 > private readonly _hosts: { [authority: string]: string | undefined } = Object.create(null);
186 > private readonly _ports: { [authority: string]: number | undefined } = Object.create(null);
187 > private readonly _connectionTokens: { [authority: string]: string | undefined } = Object.create(null);
188 > private _preferredWebSchema: 'http' | 'https' = 'http';
189 > private _delegate: ((uri: URI) => URI) | null = null;
190 > private _serverRootPath: string = '/';
191 >
192 > setPreferredWebSchema(schema: 'http' | 'https') {
193 this._preferredWebSchema = schema;
194 }
195 > network.ts
196 > setDelegate(delegate: (uri: URI) => URI): void {
197 this._delegate = delegate;
198 }
199 > network.ts
200 > setServerRootPath(product: { quality?: string; commit?: string }, serverBasePath: string | undefined): void {
201 this._serverRootPath = paths.posix.join(serverBasePath ?? '/', getServerProductSegment(product));
202 }
203 > network.ts
204 > getServerRootPath(): string {
205 return this._serverRootPath;
206 }
207 > network.ts
208 > private get _remoteResourcesPath(): string {
209 return paths.posix.join(this._serverRootPath, Schemas.vscodeRemoteResource);
210 }
211 > network.ts
212 > set(authority: string, host: string, port: number): void {
213 this._hosts[authority] = host;
214 this._ports[authority] = port;
215 }
216 > network.ts
217 > setConnectionToken(authority: string, connectionToken: string): void {
218 this._connectionTokens[authority] = connectionToken;
219 }
220 > network.ts
221 > getPreferredWebSchema(): 'http' | 'https' {
222 return this._preferredWebSchema;
223 }
224 > network.ts
225 > rewrite(uri: URI): URI {
226 if (this._delegate) {
227 try {
250 });
251 }
252 > } network.ts
253 >
254 > export const RemoteAuthorities = new RemoteAuthoritiesImpl();
255 >
256 > export function getServerProductSegment(product: { quality?: string; commit?: string }) {
257 return `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`;
258 }
259 > network.ts
260 > /**
261 > * A string pointing to a path inside the app. It should not begin with ./ or ../
262 > */
263 > export type AppResourcePath = (
264 > `a${string}` | `b${string}` | `c${string}` | `d${string}` | `e${string}` | `f${string}`
265 > | `g${string}` | `h${string}` | `i${string}` | `j${string}` | `k${string}` | `l${string}`
266 > | `m${string}` | `n${string}` | `o${string}` | `p${string}` | `q${string}` | `r${string}`
267 > | `s${string}` | `t${string}` | `u${string}` | `v${string}` | `w${string}` | `x${string}`
268 > | `y${string}` | `z${string}`
269 > );
270 >
271 > export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions';
272 > export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules';
273 > export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar';
274 > export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked';
275 >
276 > export const VSCODE_AUTHORITY = 'vscode-app';
277 >
278 > class FileAccessImpl {
279 >
280 > private static readonly FALLBACK_AUTHORITY = VSCODE_AUTHORITY;
281 >
282 > /**
283 > * Returns a URI to use in contexts where the browser is responsible
284 > * for loading (e.g. fetch()) or when used within the DOM.
285 > *
286 > * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
287 > */
288 > asBrowserUri(resourcePath: AppResourcePath | ''): URI {
289 const uri = this.toUri(resourcePath);
290 return this.uriToBrowserUri(uri);
291 }
292 > network.ts
293 > /**
294 > * Returns a URI to use in contexts where the browser is responsible
295 > * for loading (e.g. fetch()) or when used within the DOM.
296 > *
297 > * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
298 > */
299 > uriToBrowserUri(uri: URI): URI {
300 // Handle remote URIs via `RemoteAuthorities`
301 if (uri.scheme === Schemas.vscodeRemote) {
328 return uri;
329 }
330 > network.ts
331 > /**
332 > * Returns the `file` URI to use in contexts where node.js
333 > * is responsible for loading.
334 > */
335 > asFileUri(resourcePath: AppResourcePath | ''): URI {
336 const uri = this.toUri(resourcePath);
337 return this.uriToFileUri(uri);
338 }
339 > network.ts
340 > /**
341 > * Returns the `file` URI to use in contexts where node.js
342 > * is responsible for loading.
343 > */
344 > uriToFileUri(uri: URI): URI {
345 // Only convert the URI if it is `vscode-file:` scheme
346 if (uri.scheme === Schemas.vscodeFileResource) {
358 return uri;
359 }
360 > network.ts
361 > private toUri(uriOrModule: URI | string): URI {
362 if (URI.isUri(uriOrModule)) {
363 return uriOrModule;
379 throw new Error('Cannot determine URI for module id!');
380 }
381 > } network.ts
382 >
383 > export const FileAccess = new FileAccessImpl();
384 >
385 > export const CacheControlheaders: Record<string, string> = Object.freeze({
386 > 'Cache-Control': 'no-cache, no-store'
387 > });
388 >
389 > export const DocumentPolicyheaders: Record<string, string> = Object.freeze({
390 > 'Document-Policy': 'include-js-call-stacks-in-crash-reports'
391 > });
392 >
393 > export namespace COI {
394 >
395 > const coiHeaders = new Map<'3' | '2' | '1' | string, Record<string, string>>([
396 > ['1', { 'Cross-Origin-Opener-Policy': 'same-origin' }],
397 > ['2', { 'Cross-Origin-Embedder-Policy': 'require-corp' }],
398 > ['3', { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }],
399 > ]);
400 >
401 > export const CoopAndCoep = Object.freeze(coiHeaders.get('3'));
402 >
403 > const coiSearchParamName = 'vscode-coi';
404 >
405 > /**
406 > * Extract desired headers from `vscode-coi` invocation
407 > */
408 > export function getHeadersFromQuery(url: string | URI | URL): Record<string, string> | undefined {
409 let params: URLSearchParams | undefined;
410 if (typeof url === 'string') {
421 return coiHeaders.get(value);
422 }
423 > network.ts
424 > /**
425 > * Add the `vscode-coi` query attribute based on wanting `COOP` and `COEP`. Will be a noop when `crossOriginIsolated`
426 > * isn't enabled the current context
427 > */
428 > export function addSearchParam(urlOrSearch: URLSearchParams | Record<string, string>, coop: boolean, coep: boolean): void {
429 if (!(globalThis as typeof globalThis & { crossOriginIsolated?: boolean }).crossOriginIsolated) {
430 // depends on the current context being COI
src/vs/base/common/types.ts 284 covered LOC · 24 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- types.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { assert } from './assert.js';
7 >
8 > /**
9 > * @returns whether the provided parameter is a JavaScript String or not.
10 > */
11 > export function isString(str: unknown): str is string {
12 return (typeof str === 'string');
13 }
14 > types.ts
15 > /**
16 > * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
17 > */
18 > export function isStringArray(value: unknown): value is string[] {
19 return isArrayOf(value, isString);
20 }
21 > types.ts
22 > /**
23 > * @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard.
24 > */
25 > export function isArrayOf<T>(value: unknown, check: (item: unknown) => item is T): value is T[] {
26 return Array.isArray(value) && value.every(check);
27 }
28 > types.ts
29 > /**
30 > * @returns whether the provided parameter is of type `object` but **not**
31 > * `null`, an `array`, a `regexp`, nor a `date`.
32 > */
33 > export function isObject(obj: unknown): obj is Object {
34 // The method can't do a type cast since there are type (like strings) which
35 // are subclasses of any put not positvely matched by the function. Hence type
41 && !(obj instanceof Date);
42 }
43 > types.ts
44 > /**
45 > * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type
46 > */
47 > export function isTypedArray(obj: unknown): obj is Object {
48 const TypedArray = Object.getPrototypeOf(Uint8Array);
49 return typeof obj === 'object'
50 && obj instanceof TypedArray;
51 }
52 > types.ts
53 > /**
54 > * In **contrast** to just checking `typeof` this will return `false` for `NaN`.
55 > * @returns whether the provided parameter is a JavaScript Number or not.
56 > */
57 > export function isNumber(obj: unknown): obj is number {
58 return (typeof obj === 'number' && !isNaN(obj));
59 }
60 > types.ts
61 > /**
62 > * @returns whether the provided parameter is an Iterable, casting to the given generic
63 > */
64 > export function isIterable<T>(obj: unknown): obj is Iterable<T> {
65 // eslint-disable-next-line local/code-no-any-casts
66 return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
67 }
68 > types.ts
69 > /**
70 > * @returns whether the provided parameter is an Iterable, casting to the given generic
71 > */
72 > export function isAsyncIterable<T>(obj: unknown): obj is AsyncIterable<T> {
73 // eslint-disable-next-line local/code-no-any-casts
74 return !!obj && typeof (obj as any)[Symbol.asyncIterator] === 'function';
75 }
76 > types.ts
77 > /**
78 > * @returns whether the provided parameter is a JavaScript Boolean or not.
79 > */
80 > export function isBoolean(obj: unknown): obj is boolean {
81 return (obj === true || obj === false);
82 }
83 > types.ts
84 > /**
85 > * @returns whether the provided parameter is undefined.
86 > */
87 > export function isUndefined(obj: unknown): obj is undefined {
88 return (typeof obj === 'undefined');
89 }
90 > types.ts
91 > /**
92 > * @returns whether the provided parameter is defined.
93 > */
94 > export function isDefined<T>(arg: T | null | undefined): arg is T {
95 return !isUndefinedOrNull(arg);
96 }
97 > types.ts
98 > /**
99 > * @returns whether the provided parameter is undefined or null.
100 > */
101 > export function isUndefinedOrNull(obj: unknown): obj is undefined | null {
102 return (isUndefined(obj) || obj === null);
103 }
104 > types.ts
105 >
106 > export function assertType(condition: unknown, type?: string): asserts condition {
107 if (!condition) {
108 throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
109 }
110 }
111 > types.ts
112 > /**
113 > * Asserts that the argument passed in is neither undefined nor null.
114 > *
115 > * @see {@link assertDefined} for a similar utility that leverages TS assertion functions to narrow down the type of `arg` to be non-nullable.
116 > */
117 > export function assertReturnsDefined<T>(arg: T | null | undefined): NonNullable<T> {
118 assert(
119 arg !== null && arg !== undefined,
123 return arg;
124 }
125 > types.ts
126 > /**
127 > * Asserts that a provided `value` is `defined` - not `null` or `undefined`,
128 > * throwing an error with the provided error or error message, while also
129 > * narrowing down the type of the `value` to be `NonNullable` using TS
130 > * assertion functions.
131 > *
132 > * @throws if the provided `value` is `null` or `undefined`.
133 > *
134 > * ## Examples
135 > *
136 > * ```typescript
137 > * // an assert with an error message
138 > * assertDefined('some value', 'String constant is not defined o_O.');
139 > *
140 > * // `throws!` the provided error
141 > * assertDefined(null, new Error('Should throw this error.'));
142 > *
143 > * // narrows down the type of `someValue` to be non-nullable
144 > * const someValue: string | undefined | null = blackbox();
145 > * assertDefined(someValue, 'Some value must be defined.');
146 > * console.log(someValue.length); // now type of `someValue` is `string`
147 > * ```
148 > *
149 > * @see {@link assertReturnsDefined} for a similar utility but without assertion.
150 > * @see {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions typescript-3-7.html#assertion-functions}
151 > */
152 > export function assertDefined<T>(value: T, error: string | NonNullable<Error>): asserts value is NonNullable<T> {
153 if (value === null || value === undefined) {
154 const errorToThrow = typeof error === 'string' ? new Error(error) : error;
157 }
158 }
159 > types.ts
160 > /**
161 > * Asserts that each argument passed in is neither undefined nor null.
162 > */
163 > export function assertReturnsAllDefined<T1, T2>(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2];
164 > export function assertReturnsAllDefined<T1, T2, T3>(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3];
165 > export function assertReturnsAllDefined<T1, T2, T3, T4>(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4];
166 > export function assertReturnsAllDefined(...args: (unknown | null | undefined)[]): unknown[] {
167 const result = [];
168
179 return result;
180 }
181 > types.ts
182 > /**
183 > * Checks if the provided value is one of the vales in the provided list.
184 > *
185 > * ## Examples
186 > *
187 > * ```typescript
188 > * // note! item type is a `subset of string`
189 > * type TItem = ':' | '.' | '/';
190 > *
191 > * // note! item is type of `string` here
192 > * const item: string = ':';
193 > * // list of the items to check against
194 > * const list: TItem[] = [':', '.'];
195 > *
196 > * // ok
197 > * assert(
198 > * isOneOf(item, list),
199 > * 'Must succeed.',
200 > * );
201 > *
202 > * // `item` is of `TItem` type now
203 > * ```
204 > */
205 > export const isOneOf = <TType, TSubtype extends TType>(
206 value: TType,
207 validValues: readonly TSubtype[],
211 return validValues.includes(<TSubtype>value);
212 };
213 > types.ts
214 > /**
215 > * Compile-time type check of a variable.
216 > */
217 > export function typeCheck<T = never>(_thing: NoInfer<T>): void { }
218 >
219 > const hasOwnProperty = Object.prototype.hasOwnProperty;
220 >
221 > /**
222 > * @returns whether the provided parameter is an empty JavaScript Object or not.
223 > */
224 > export function isEmptyObject(obj: unknown): obj is object {
225 if (!isObject(obj)) {
226 return false;
235 return true;
236 }
237 > types.ts
238 > /**
239 > * @returns whether the provided parameter is a JavaScript Function or not.
240 > */
241 > export function isFunction(obj: unknown): obj is Function {
242 return (typeof obj === 'function');
243 }
244 > types.ts
245 > /**
246 > * @returns whether the provided parameters is are JavaScript Function or not.
247 > */
248 > export function areFunctions(...objects: unknown[]): boolean {
249 return objects.length > 0 && objects.every(isFunction);
250 }
251 > types.ts
252 > export type TypeConstraint = string | Function;
253 >
254 > export function validateConstraints(args: unknown[], constraints: Array<TypeConstraint | undefined>): void {
255 const len = Math.min(args.length, constraints.length);
256 for (let i = 0; i < len; i++) {
258 }
259 }
260 > types.ts
261 > export function validateConstraint(arg: unknown, constraint: TypeConstraint | undefined): void {
262
263 if (isString(constraint)) {
283 }
284 }
285 > types.ts
286 > /**
287 > * Helper type assertion that safely upcasts a type to a supertype.
288 > *
289 > * This can be used to make sure the argument correctly conforms to the subtype while still being able to pass it
290 > * to contexts that expects the supertype.
291 > */
292 > export function upcast<Base, Sub extends Base = Base>(x: Sub): Base {
293 return x;
294 }
295 > types.ts
296 > type AddFirstParameterToFunction<T, TargetFunctionsReturnType, FirstParameter> = T extends (...args: any[]) => TargetFunctionsReturnType ?
297 > // Function: add param to function
298 > (firstArg: FirstParameter, ...args: Parameters<T>) => ReturnType<T> :
299 >
300 > // Else: just leave as is
301 > T;
302 >
303 > /**
304 > * Allows to add a first parameter to functions of a type.
305 > */
306 > export type AddFirstParameterToFunctions<Target, TargetFunctionsReturnType, FirstParameter> = {
307 > // For every property
308 > [K in keyof Target]: AddFirstParameterToFunction<Target[K], TargetFunctionsReturnType, FirstParameter>;
309 > };
310 >
311 > /**
312 > * Given an object with all optional properties, requires at least one to be defined.
313 > * i.e. AtLeastOne<MyObject>;
314 > */
315 > export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
316 >
317 > /**
318 > * Only picks the non-optional properties of a type.
319 > */
320 > export type OmitOptional<T> = { [K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K] };
321 >
322 > /**
323 > * A type that removed readonly-less from all properties of `T`
324 > */
325 > export type Mutable<T> = {
326 > -readonly [P in keyof T]: T[P]
327 > };
328 >
329 > /**
330 > * A type that adds readonly to all properties of T, recursively.
331 > */
332 > export type DeepImmutable<T> = T extends (infer U)[]
333 > ? ReadonlyArray<DeepImmutable<U>>
334 > : T extends ReadonlyArray<infer U>
335 > ? ReadonlyArray<DeepImmutable<U>>
336 > : T extends Map<infer K, infer V>
337 > ? ReadonlyMap<K, DeepImmutable<V>>
338 > : T extends Set<infer U>
339 > ? ReadonlySet<DeepImmutable<U>>
340 > : T extends object
341 > ? {
342 > readonly [K in keyof T]: DeepImmutable<T[K]>;
343 > }
344 > : T;
345 >
346 > /**
347 > * A single object or an array of the objects.
348 > */
349 > export type SingleOrMany<T> = T | T[];
350 >
351 > /**
352 > * Given a `type X = { foo?: string }` checking that an object `satisfies X`
353 > * will ensure each property was explicitly defined, ensuring no properties
354 > * are omitted or forgotten.
355 > */
356 > export type WithDefinedProps<T> = { [K in keyof Required<T>]: T[K] };
357 >
358 >
359 > /**
360 > * A type that recursively makes all properties of `T` required
361 > */
362 > export type DeepRequiredNonNullable<T> = {
363 > [P in keyof T]-?: T[P] extends object ? DeepRequiredNonNullable<T[P]> : Required<NonNullable<T[P]>>;
364 > };
365 >
366 >
367 > /**
368 > * Represents a type that is a partial version of a given type `T`, where all properties are optional and can be deeply nested.
369 > */
370 > export type DeepPartial<T> = {
371 > [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : Partial<T[P]>;
372 > };
373 >
374 > /**
375 > * Represents a type that is a partial version of a given type `T`, except a subset.
376 > */
377 > export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>;
378 >
379 >
380 > type KeysOfUnionType<T> = T extends T ? keyof T : never;
381 > type FilterType<T, TTest> = T extends TTest ? T : never;
382 > type MakeOptionalAndTrue<T extends object> = { [K in keyof T]?: true };
383 >
384 > /**
385 > * Type guard that checks if an object has specific keys and narrows the type accordingly.
386 > *
387 > * @param x - The object to check
388 > * @param key - An object with boolean values indicating which keys to check for
389 > * @returns true if all specified keys exist in the object, false otherwise
390 > *
391 > * @example
392 > * ```typescript
393 > * type A = { a: string };
394 > * type B = { b: number };
395 > * const obj: A | B = getObject();
396 > *
397 > * if (hasKey(obj, { a: true })) {
398 > * // obj is now narrowed to type A
399 > * console.log(obj.a);
400 > * }
401 > * ```
402 > */
403 > export function hasKey<T extends object, TKeys extends MakeOptionalAndTrue<T>>(x: T, key: TKeys): x is FilterType<T, { [K in KeysOfUnionType<T> & keyof TKeys]: unknown }> {
404 for (const k in key) {
405 if (!(k in x)) {
src/vs/platform/agentHost/common/state/protocol/channels-changeset/state.ts 277 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { StringOrMarkdown, FileEdit, ErrorInfo } from '../common/state.js';
10 >
11 > // ─── Changesets ──────────────────────────────────────────────────────────────
12 >
13 > /**
14 > * Catalogue entry describing one changeset the server can produce for a
15 > * session.
16 > *
17 > * Catalogue entries are intentionally lightweight — just enough to render a
18 > * chip or list row without subscribing. Full per-changeset detail
19 > * ({@link ChangesetState}) lives on the subscribable URI obtained by
20 > * expanding {@link uriTemplate}.
21 > *
22 > * @category Changesets
23 > */
24 > export interface Changeset {
25 > /** Human-readable label, e.g. `"Uncommitted Changes"`. */
26 > label: string;
27 > /**
28 > * RFC 6570 URI template. Clients parse the variables directly out of the
29 > * template using the standard `{name}` syntax — they are not redeclared
30 > * here.
31 > *
32 > * Only the following template shapes are defined by this protocol; any
33 > * other variable name MUST be ignored by clients (there is no
34 > * protocol-defined way to obtain values for unknown variables):
35 > *
36 > * | Variables in template | Meaning |
37 > * | ------------------------------------------- | ------------------------------------------------------------------------------------ |
38 > * | _(none)_ | A static, session-wide changeset. The template is itself a subscribable URI. |
39 > * | `{turnId}` | Per-turn slice. Expand with a `Turn.id` from the session. |
40 > * | `{originalTurnId}` and `{modifiedTurnId}` | Diff between two turns. Both variables MUST be present. |
41 > *
42 > * Future protocol versions MAY add new well-known variables.
43 > */
44 > uriTemplate: string;
45 > /** Optional longer description. */
46 > description?: string;
47 > /**
48 > * Advisory hint describing what kind of changeset this is, so clients can
49 > * group, sort, or render an appropriate icon without parsing
50 > * {@link uriTemplate}. Recognized values include:
51 > *
52 > * - `'session'`: a static, session-wide changeset covering all changes the
53 > * agent has produced in this session.
54 > * - `'branch'`: changes relative to a base branch (e.g. a feature branch
55 > * diffed against `main`).
56 > * - `'uncommitted'`: the workspace's current uncommitted changes.
57 > * - `'turn'`: changes produced by a single turn. Typically paired with a
58 > * `{turnId}` variable in {@link uriTemplate}.
59 > * - `'compare-turns'`: a diff between two turns. Typically paired with
60 > * `{originalTurnId}` and `{modifiedTurnId}` variables in
61 > * {@link uriTemplate}.
62 > *
63 > * Implementations MAY provide additional values; clients SHOULD fall back
64 > * to a reasonable default when an unknown value is encountered.
65 > */
66 > changeKind: string;
67 > /**
68 > * Optional capability declarations for this changeset. Absent (or an empty
69 > * object) means the changeset advertises no optional capabilities.
70 > *
71 > * Because the catalogue entry is delivered up-front on
72 > * {@link ChangesetState | the session's changeset list}, clients can decide
73 > * whether to surface capability-gated UI (such as review checkboxes) without
74 > * first subscribing to the changeset URI. Mirrors the presence-flag
75 > * convention of `ClientCapabilities`.
76 > */
77 > capabilities?: ChangesetCapabilities;
78 > }
79 >
80 > /**
81 > * Optional capabilities a changeset advertises on its catalogue
82 > * {@link Changeset} entry.
83 > *
84 > * Each field is a presence flag: an empty object `{}` means "supported",
85 > * absence means "not supported". Sub-fields on individual capabilities are
86 > * reserved for future per-capability options.
87 > *
88 > * @category Changesets
89 > */
90 > export interface ChangesetCapabilities {
91 > /**
92 > * The changeset supports the per-file **review** workflow. When declared,
93 > * clients MAY surface a GitHub-style "Viewed" toggle per file and dispatch
94 > * {@link ChangesetFilesReviewChangedAction | `changeset/filesReviewChanged`} to
95 > * set each file's {@link ChangesetFile.reviewed} flag. Clients that omit
96 > * handling MUST treat the changeset as non-reviewable.
97 > */
98 > review?: Record<string, never>;
99 > }
100 >
101 > /**
102 > * Computation lifecycle of a {@link ChangesetState}.
103 > *
104 > * @category Changesets
105 > */
106 > export const enum ChangesetStatus {
107 > /** The server is still computing the contents of this changeset. */
108 > Computing = 'computing',
109 > /** The changeset has been fully computed and is up-to-date. */
110 > Ready = 'ready',
111 > /**
112 > * Computation failed. The cause is described by
113 > * {@link ChangesetState.error}.
114 > */
115 > Error = 'error',
116 > }
117 >
118 > /**
119 > * Full state for a single changeset, returned when a client subscribes to
120 > * an expanded changeset URI.
121 > *
122 > * The client already knows the URI it subscribed to, so this state does
123 > * not redundantly carry it (or the catalogue's `id`, `label`, etc.).
124 > * Aggregate counts (`additions`, `deletions`, `files`) are likewise
125 > * omitted: clients trivially compute them from `files[].edit.diff`.
126 > *
127 > * @category Changesets
128 > */
129 > export interface ChangesetState {
130 > /** Computation lifecycle. */
131 > status: ChangesetStatus;
132 > /** Present iff `status === ChangesetStatus.Error`. */
133 > error?: ErrorInfo;
134 > /** Files in this changeset, keyed by {@link ChangesetFile.id}. */
135 > files: ChangesetFile[];
136 > /**
137 > * Operations the client may invoke against this changeset. Omit when no
138 > * operations are available.
139 > */
140 > operations?: ChangesetOperation[];
141 > }
142 >
143 > /**
144 > * One file entry within a {@link ChangesetState}.
145 > *
146 > * @category Changesets
147 > */
148 > export interface ChangesetFile {
149 > /**
150 > * Stable identifier within the changeset. Typically `after.uri`
151 > * (or `before.uri` for deletions).
152 > */
153 > id: string;
154 > /**
155 > * Reuses the existing {@link FileEdit} shape. Clients derive line
156 > * additions, deletions, and rename/create/delete semantics from this.
157 > */
158 > edit: FileEdit;
159 > /**
160 > * Whether a reviewer has marked this file as reviewed (the GitHub-style
161 > * "Viewed" checkbox). Absent is equivalent to `false` — clients MUST treat
162 > * a missing value as not-yet-reviewed.
163 > *
164 > * Requires the changeset to advertise {@link ChangesetCapabilities.review}.
165 > * Clients toggle it by dispatching
166 > * {@link ChangesetFilesReviewChangedAction | `changeset/filesReviewChanged`};
167 > * the server MAY also originate it (e.g. an agent self-reviewing its own
168 > * output).
169 > *
170 > * There is no content version in the protocol, so review is **not** reset
171 > * automatically when a file's contents change under a stable id. The server,
172 > * which is the authority on what changed, resets review explicitly — either
173 > * by re-emitting the file (via {@link ChangesetFileSetAction} or
174 > * {@link ChangesetContentChangedAction}) without `reviewed: true`, or by
175 > * dispatching `changeset/filesReviewChanged` with `reviewed: false`.
176 > */
177 > reviewed?: boolean;
178 > /**
179 > * Server-defined opaque metadata, surfaced to operations and tooling
180 > * but not interpreted by the protocol.
181 > */
182 > _meta?: Record<string, unknown>;
183 > }
184 >
185 > /**
186 > * Execution lifecycle of a {@link ChangesetOperation}.
187 > *
188 > * An operation is invoked imperatively via `invokeChangesetOperation`, but
189 > * its progress and outcome are reflected back into changeset state so that
190 > * every subscriber observes a consistent view (e.g. a spinner on a "Create
191 > * Pull Request" button, or an inline error after a failed "revert").
192 > *
193 > * @category Changesets
194 > */
195 > export const enum ChangesetOperationStatus {
196 > /**
197 > * The operation is ready to be invoked. This is the default when
198 > * {@link ChangesetOperation.status} is omitted.
199 > */
200 > Idle = 'idle',
201 > /** An invocation of this operation is currently in flight. */
202 > Running = 'running',
203 > /**
204 > * The most recent invocation failed. The cause is described by
205 > * {@link ChangesetOperation.error}.
206 > */
207 > Error = 'error',
208 > /**
209 > * The operation is currently disabled and cannot be invoked.
210 > */
211 > Disabled = 'disabled',
212 > }
213 >
214 > /**
215 > * Where a {@link ChangesetOperation} can be invoked.
216 > *
217 > * @category Changesets
218 > */
219 > export const enum ChangesetOperationScope {
220 > /** Applies to the whole changeset. */
221 > Changeset = 'changeset',
222 > /** Applies to a single file within the changeset. */
223 > Resource = 'resource',
224 > /** Applies to a line range within a single file. */
225 > Range = 'range',
226 > }
227 >
228 > /**
229 > * A server-declared invokable verb the client can run against a
230 > * changeset, a file, or a range — `"stage"`, `"revert"`, `"create-pr"`,
231 > * and so on.
232 > *
233 > * The term "operation" is used deliberately to avoid colliding with the
234 > * protocol-level [Actions](/guide/actions) that mutate state.
235 > *
236 > * @category Changesets
237 > */
238 > export interface ChangesetOperation {
239 > /** Stable identifier, unique within this changeset. */
240 > id: string;
241 > /** Human-readable button/menu label. */
242 > label: string;
243 > /** Optional longer description shown on hover or in tooltips. */
244 > description?: string;
245 > /** Where this operation can be invoked. */
246 > scopes: ChangesetOperationScope[];
247 > /**
248 > * Optional confirmation prompt to show before invoking. When present,
249 > * the client MUST display this message to the user (typically in a
250 > * confirmation dialog) and only invoke the operation after the user
251 > * accepts. The presence of this field also signals that the operation
252 > * is destructive — clients SHOULD style the affirmative button
253 > * accordingly (e.g. with a warning colour).
254 > */
255 > confirmation?: StringOrMarkdown;
256 > /** Optional generic icon hint, e.g. `"check"`, `"trash"`. */
257 > icon?: string;
258 > /** Optional group identifier, used to group related operations together. */
259 > group?: string;
260 > /**
261 > * Current execution status. The server sets
262 > * {@link ChangesetOperationStatus.Running | Running} while an invocation
263 > * is in flight, {@link ChangesetOperationStatus.Error | Error} when the
264 > * most recent invocation failed, and
265 > * {@link ChangesetOperationStatus.Idle | Idle} otherwise.
266 > *
267 > * Clients SHOULD reflect this state in the UI — e.g. disabling the
268 > * control or showing a spinner while `Running`, and surfacing
269 > * {@link error} while `Error`.
270 > */
271 > status: ChangesetOperationStatus;
272 > /**
273 > * Cause of failure. Present iff
274 > * `status === ChangesetOperationStatus.Error`; otherwise omitted.
275 > */
276 > error?: ErrorInfo;
277 > }
src/vs/base/common/buffer.ts 253 covered LOC · 65 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- buffer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Lazy } from './lazy.js';
7 > import * as streams from './stream.js';
8 >
9 > interface NodeBuffer {
10 > allocUnsafe(size: number): Uint8Array;
11 > isBuffer(obj: unknown): obj is NodeBuffer;
12 > from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array;
13 > from(data: string): Uint8Array;
14 > }
15 >
16 > declare const Buffer: NodeBuffer;
17 >
18 > const hasBuffer = (typeof Buffer !== 'undefined');
19 > const indexOfTable = new Lazy(() => new Uint8Array(256));
20 >
21 > let textEncoder: { encode: (input: string) => Uint8Array } | null;
22 > let textDecoder: { decode: (input: Uint8Array) => string } | null;
23 >
24 > export class VSBuffer {
25 >
26 > /**
27 > * When running in a nodejs context, the backing store for the returned `VSBuffer` instance
28 > * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable.
29 > */
30 > static alloc(byteLength: number): VSBuffer {
31 if (hasBuffer) {
32 return new VSBuffer(Buffer.allocUnsafe(byteLength));
35 }
36 }
37 > buffer.ts
38 > /**
39 > * When running in a nodejs context, if `actual` is not a nodejs Buffer, the backing store for
40 > * the returned `VSBuffer` instance might use a nodejs Buffer allocated from node's Buffer pool,
41 > * which is not transferrable.
42 > */
43 > static wrap(actual: Uint8Array): VSBuffer {
44 > if (hasBuffer && !(Buffer.isBuffer(actual))) { buffer.ts
45 > // https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length buffer.ts
46 > // Create a zero-copy Buffer wrapper around the ArrayBuffer pointed to by the Uint8Array
47 > actual = Buffer.from(actual.buffer, actual.byteOffset, actual.byteLength);
48 > }
49 > return new VSBuffer(actual); buffer.ts
50 > }
51 > buffer.ts
52 > /**
53 > * When running in a nodejs context, the backing store for the returned `VSBuffer` instance
54 > * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable.
55 > */
56 > static fromString(source: string, options?: { dontUseNodeBuffer?: boolean }): VSBuffer {
57 > const dontUseNodeBuffer = options?.dontUseNodeBuffer || false; buffer.ts
58 > if (!dontUseNodeBuffer && hasBuffer) {
59 > return new VSBuffer(Buffer.from(source));
60 > } else {
61 if (!textEncoder) {
62 textEncoder = new TextEncoder();
64 return new VSBuffer(textEncoder.encode(source));
65 }
66 > } buffer.ts
67 > buffer.ts
68 > /**
69 > * When running in a nodejs context, the backing store for the returned `VSBuffer` instance
70 > * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable.
71 > */
72 > static fromByteArray(source: number[]): VSBuffer {
73 const result = VSBuffer.alloc(source.length);
74 for (let i = 0, len = source.length; i < len; i++) {
77 return result;
78 }
79 > buffer.ts
80 > /**
81 > * When running in a nodejs context, the backing store for the returned `VSBuffer` instance
82 > * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable.
83 > */
84 > static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
85 if (typeof totalLength === 'undefined') {
86 totalLength = 0;
100 return ret;
101 }
102 > buffer.ts
103 > static isNativeBuffer(buffer: unknown): boolean {
104 return hasBuffer && Buffer.isBuffer(buffer);
105 }
106 > buffer.ts
107 > readonly buffer: Uint8Array;
108 > readonly byteLength: number;
109 >
110 > private constructor(buffer: Uint8Array) {
111 > this.buffer = buffer; buffer.ts
112 > this.byteLength = this.buffer.byteLength;
113 > }
114 > buffer.ts
115 > /**
116 > * When running in a nodejs context, the backing store for the returned `VSBuffer` instance
117 > * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable.
118 > */
119 > clone(): VSBuffer {
120 const result = VSBuffer.alloc(this.byteLength);
121 result.set(this);
122 return result;
123 }
124 > buffer.ts
125 > toString(): string {
126 > if (hasBuffer) { buffer.ts
127 > return this.buffer.toString();
128 > } else {
129 if (!textDecoder) {
130 textDecoder = new TextDecoder(undefined, { ignoreBOM: true });
132 return textDecoder.decode(this.buffer);
133 }
134 > } buffer.ts
135 > buffer.ts
136 > slice(start?: number, end?: number): VSBuffer {
137 > // IMPORTANT: use subarray instead of slice because TypedArray#slice buffer.ts
138 > // creates shallow copy and NodeBuffer#slice doesn't. The use of subarray
139 > // ensures the same, performance, behaviour.
140 > return new VSBuffer(this.buffer.subarray(start, end));
141 > }
142 > buffer.ts
143 > set(array: VSBuffer, offset?: number): void;
144 > set(array: Uint8Array, offset?: number): void;
145 > set(array: ArrayBuffer, offset?: number): void;
146 > set(array: ArrayBufferView, offset?: number): void;
147 > set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void;
148 > set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void {
149 if (array instanceof VSBuffer) {
150 this.buffer.set(array.buffer, offset);
159 }
160 }
161 > buffer.ts
162 > readUInt32BE(offset: number): number {
163 return readUInt32BE(this.buffer, offset);
164 }
165 > buffer.ts
166 > writeUInt32BE(value: number, offset: number): void {
167 writeUInt32BE(this.buffer, value, offset);
168 }
169 > buffer.ts
170 > readUInt32LE(offset: number): number {
171 return readUInt32LE(this.buffer, offset);
172 }
173 > buffer.ts
174 > writeUInt32LE(value: number, offset: number): void {
175 writeUInt32LE(this.buffer, value, offset);
176 }
177 > buffer.ts
178 > readUInt8(offset: number): number {
179 return readUInt8(this.buffer, offset);
180 }
181 > buffer.ts
182 > writeUInt8(value: number, offset: number): void {
183 writeUInt8(this.buffer, value, offset);
184 }
185 > buffer.ts
186 > indexOf(subarray: VSBuffer | Uint8Array, offset = 0) {
187 return binaryIndexOf(this.buffer, subarray instanceof VSBuffer ? subarray.buffer : subarray, offset);
188 }
189 > buffer.ts
190 > equals(other: VSBuffer): boolean {
191 if (this === other) {
192 return true;
199 return this.buffer.every((value, index) => value === other.buffer[index]);
200 }
201 > } buffer.ts
202 >
203 > /**
204 > * Like String.indexOf, but works on Uint8Arrays.
205 > * Uses the boyer-moore-horspool algorithm to be reasonably speedy.
206 > */
207 > export function binaryIndexOf(haystack: Uint8Array, needle: Uint8Array, offset = 0): number {
208 const needleLen = needle.byteLength;
209 const haystackLen = haystack.byteLength;
248 return result;
249 }
250 > buffer.ts
251 > export function readUInt16LE(source: Uint8Array, offset: number): number {
252 return (
253 ((source[offset + 0] << 0) >>> 0) |
255 );
256 }
257 > buffer.ts
258 > export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void {
259 destination[offset + 0] = (value & 0b11111111);
260 value = value >>> 8;
261 destination[offset + 1] = (value & 0b11111111);
262 }
263 > buffer.ts
264 > export function readUInt32BE(source: Uint8Array, offset: number): number {
265 return (
266 source[offset] * 2 ** 24
270 );
271 }
272 > buffer.ts
273 > export function writeUInt32BE(destination: Uint8Array, value: number, offset: number): void {
274 destination[offset + 3] = value;
275 value = value >>> 8;
280 destination[offset] = value;
281 }
282 > buffer.ts
283 > export function readUInt32LE(source: Uint8Array, offset: number): number {
284 return (
285 ((source[offset + 0] << 0) >>> 0) |
289 );
290 }
291 > buffer.ts
292 > export function writeUInt32LE(destination: Uint8Array, value: number, offset: number): void {
293 destination[offset + 0] = (value & 0b11111111);
294 value = value >>> 8;
299 destination[offset + 3] = (value & 0b11111111);
300 }
301 > buffer.ts
302 > export function readUInt8(source: Uint8Array, offset: number): number {
303 return source[offset];
304 }
305 > buffer.ts
306 > export function writeUInt8(destination: Uint8Array, value: number, offset: number): void {
307 destination[offset] = value;
308 }
309 > buffer.ts
310 > export interface VSBufferReadable extends streams.Readable<VSBuffer> { }
311 >
312 > export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer> { }
313 >
314 > export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
315 >
316 > export interface VSBufferReadableBufferedStream extends streams.ReadableBufferedStream<VSBuffer> { }
317 >
318 > export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
319 return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
320 }
321 > buffer.ts
322 > export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
323 return streams.toReadable<VSBuffer>(buffer);
324 }
325 > buffer.ts
326 > export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promise<VSBuffer> {
327 return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
328 }
329 > buffer.ts
330 export async function bufferedStreamToBuffer(bufferedStream: streams.ReadableBufferedStream<VSBuffer>): Promise<VSBuffer> {
331 if (bufferedStream.ended) {
342 ]);
343 }
344 > buffer.ts
345 > export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
346 return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
347 }
348 > buffer.ts
349 > export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents<Uint8Array | string>): streams.ReadableStream<VSBuffer> {
350 return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
351 }
352 > buffer.ts
353 > export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream<VSBuffer> {
354 return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks), options);
355 }
356 > buffer.ts
357 > export function prefixedBufferReadable(prefix: VSBuffer, readable: VSBufferReadable): VSBufferReadable {
358 return streams.prefixedReadable(prefix, readable, chunks => VSBuffer.concat(chunks));
359 }
360 > buffer.ts
361 > export function prefixedBufferStream(prefix: VSBuffer, stream: VSBufferReadableStream): VSBufferReadableStream {
362 return streams.prefixedStream(prefix, stream, chunks => VSBuffer.concat(chunks));
363 }
364 > buffer.ts
365 > /** Decodes base64 to a uint8 array. URL-encoded and unpadded base64 is allowed. */
366 > export function decodeBase64(encoded: string) {
367 > let building = 0; buffer.ts
368 > let remainder = 0;
369 > let bufi = 0;
370 >
371 > // The simpler way to do this is `Uint8Array.from(atob(str), c => c.charCodeAt(0))`,
372 > // but that's about 10-20x slower than this function in current Chromium versions.
373 >
374 > const buffer = new Uint8Array(Math.floor(encoded.length / 4 * 3));
375 > const append = (value: number) => {
376 > switch (remainder) {
377 > case 3:
378 > buffer[bufi++] = building | value;
379 > remainder = 0;
380 > break;
381 > case 2:
382 > buffer[bufi++] = building | (value >>> 2);
383 > building = value << 6;
384 > remainder = 3;
385 > break;
386 > case 1:
387 > buffer[bufi++] = building | (value >>> 4);
388 > building = value << 4;
389 > remainder = 2;
390 > break;
391 > default:
392 > building = value << 2;
393 > remainder = 1;
394 > }
395 > };
396 >
397 > for (let i = 0; i < encoded.length; i++) {
398 > const code = encoded.charCodeAt(i);
399 > // See https://datatracker.ietf.org/doc/html/rfc4648#section-4
400 > // This branchy code is about 3x faster than an indexOf on a base64 char string.
401 > if (code >= 65 && code <= 90) {
402 > append(code - 65); // A-Z starts ranges from char code 65 to 90 buffer.ts
403 > } else if (code >= 97 && code <= 122) { buffer.ts
404 > append(code - 97 + 26); // a-z starts ranges from char code 97 to 122, starting at byte 26 buffer.ts
405 > } else if (code >= 48 && code <= 57) {
406 > append(code - 48 + 52); // 0-9 starts ranges from char code 48 to 58, starting at byte 52 buffer.ts
407 > } else if (code === 43 || code === 45) { buffer.ts
408 append(62); // "+" or "-" for URLS
409 } else if (code === 47 || code === 95) {
414 throw new SyntaxError(`Unexpected base64 character ${encoded[i]}`);
415 }
416 > } buffer.ts
417 > buffer.ts
418 > const unpadded = bufi;
419 > while (remainder > 0) {
420 append(0);
421 }
422 > buffer.ts
423 > // slice is needed to account for overestimation due to padding
424 > return VSBuffer.wrap(buffer).slice(0, unpadded);
425 > }
426 > buffer.ts
427 > const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
428 > const base64UrlSafeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
429 >
430 > /** Encodes a buffer to a base64 string. */
431 > export function encodeBase64({ buffer }: VSBuffer, padded = true, urlSafe = false) {
432 > const dictionary = urlSafe ? base64UrlSafeAlphabet : base64Alphabet; buffer.ts
433 > let output = '';
434 >
435 > const remainder = buffer.byteLength % 3;
436 >
437 > let i = 0;
438 > for (; i < buffer.byteLength - remainder; i += 3) {
439 > const a = buffer[i + 0]; buffer.ts
440 > const b = buffer[i + 1];
441 > const c = buffer[i + 2];
442 >
443 > output += dictionary[a >>> 2];
444 > output += dictionary[(a << 4 | b >>> 4) & 0b111111];
445 > output += dictionary[(b << 2 | c >>> 6) & 0b111111];
446 > output += dictionary[c & 0b111111];
447 > }
448 > buffer.ts
449 > if (remainder === 1) {
450 const a = buffer[i + 0];
451 output += dictionary[a >>> 2];
452 output += dictionary[(a << 4) & 0b111111];
453 if (padded) { output += '=='; }
454 > } else if (remainder === 2) { buffer.ts
455 const a = buffer[i + 0];
456 const b = buffer[i + 1];
460 if (padded) { output += '='; }
461 }
462 > buffer.ts
463 > return output;
464 > }
465 > buffer.ts
466 > const hexChars = '0123456789abcdef';
467 > export function encodeHex({ buffer }: VSBuffer): string {
468 let result = '';
469 for (let i = 0; i < buffer.length; i++) {
474 return result;
475 }
476 > buffer.ts
477 > export function decodeHex(hex: string): VSBuffer {
478 if (hex.length % 2 !== 0) {
479 throw new SyntaxError('Hex string must have an even length');
485 return VSBuffer.wrap(out);
486 }
487 > buffer.ts
488 function decodeHexChar(str: string, position: number) {
489 const s = str.charCodeAt(position);
src/vs/base/common/resources.ts 253 covered LOC · 23 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- resources.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from './charCode.js';
7 > import * as extpath from './extpath.js';
8 > import { Schemas } from './network.js';
9 > import * as paths from './path.js';
10 > import { isLinux, isWindows } from './platform.js';
11 > import { compare as strCompare, equalsIgnoreCase } from './strings.js';
12 > import { URI, uriToFsPath } from './uri.js';
13 >
14 > export function originalFSPath(uri: URI): string {
15 return uriToFsPath(uri, true);
16 }
18 > //#region IExtUri
19 >
20 > export interface IExtUri {
21 >
22 > // --- identity
23 >
24 > /**
25 > * Compares two uris.
26 > *
27 > * @param uri1 Uri
28 > * @param uri2 Uri
29 > * @param ignoreFragment Ignore the fragment (defaults to `false`)
30 > */
31 > compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
32 >
33 > /**
34 > * Tests whether two uris are equal
35 > *
36 > * @param uri1 Uri
37 > * @param uri2 Uri
38 > * @param ignoreFragment Ignore the fragment (defaults to `false`)
39 > */
40 > isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
41 >
42 > /**
43 > * Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
44 > *
45 > * @param base A uri which is "longer" or at least same length as `parentCandidate`
46 > * @param parentCandidate A uri which is "shorter" or up to same length as `base`
47 > * @param ignoreFragment Ignore the fragment (defaults to `false`)
48 > */
49 > isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
50 >
51 > /**
52 > * Creates a key from a resource URI to be used to resource comparison and for resource maps.
53 > * @see {@link ResourceMap}
54 > * @param uri Uri
55 > * @param ignoreFragment Ignore the fragment (defaults to `false`)
56 > */
57 > getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
58 >
59 > /**
60 > * Whether the casing of the path-component of the uri should be ignored.
61 > */
62 > ignorePathCasing(uri: URI): boolean;
63 >
64 > // --- path math
65 >
66 > basenameOrAuthority(resource: URI): string;
67 >
68 > /**
69 > * Returns the basename of the path component of an uri.
70 > * @param resource
71 > */
72 > basename(resource: URI): string;
73 >
74 > /**
75 > * Returns the extension of the path component of an uri.
76 > * @param resource
77 > */
78 > extname(resource: URI): string;
79 > /**
80 > * Return a URI representing the directory of a URI path.
81 > *
82 > * @param resource The input URI.
83 > * @returns The URI representing the directory of the input URI.
84 > */
85 > dirname(resource: URI): URI;
86 > /**
87 > * Join a URI path with path fragments and normalizes the resulting path.
88 > *
89 > * @param resource The input URI.
90 > * @param pathFragment The path fragment to add to the URI path.
91 > * @returns The resulting URI.
92 > */
93 > joinPath(resource: URI, ...pathFragment: string[]): URI;
94 > /**
95 > * Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
96 > *
97 > * @param resource The URI to normalize the path.
98 > * @returns The URI with the normalized path.
99 > */
100 > normalizePath(resource: URI): URI;
101 > /**
102 > *
103 > * @param from
104 > * @param to
105 > */
106 > relativePath(from: URI, to: URI): string | undefined;
107 > /**
108 > * Resolves an absolute or relative path against a base URI.
109 > * The path can be relative or absolute posix or a Windows path
110 > */
111 > resolvePath(base: URI, path: string): URI;
112 >
113 > // --- misc
114 >
115 > /**
116 > * Returns true if the URI path is absolute.
117 > */
118 > isAbsolutePath(resource: URI): boolean;
119 > /**
120 > * Tests whether the two authorities are the same
121 > */
122 > isEqualAuthority(a1: string, a2: string): boolean;
123 > /**
124 > * Returns true if the URI path has a trailing path separator
125 > */
126 > hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
127 > /**
128 > * Removes a trailing path separator, if there's one.
129 > * Important: Doesn't remove the first slash, it would make the URI invalid
130 > */
131 > removeTrailingPathSeparator(resource: URI, sep?: string): URI;
132 > /**
133 > * Adds a trailing path separator to the URI if there isn't one already.
134 > * For example, c:\ would be unchanged, but c:\users would become c:\users\
135 > */
136 > addTrailingPathSeparator(resource: URI, sep?: string): URI;
137 > }
138 >
139 > export class ExtUri implements IExtUri {
140 >
141 > constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
142 >
143 > compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
144 if (uri1 === uri2) {
145 return 0;
147 return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
148 }
149 > resources.ts
150 > isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
151 if (uri1 === uri2) {
152 return true;
157 return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
158 }
159 > resources.ts
160 > getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
161 return uri.with({
162 path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
164 }).toString();
165 }
166 > resources.ts
167 > ignorePathCasing(uri: URI): boolean {
168 return this._ignorePathCasing(uri);
169 }
170 > resources.ts
171 > isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
172 if (base.scheme === parentCandidate.scheme) {
173 if (base.scheme === Schemas.file) {
180 return false;
181 }
182 > resources.ts
183 > // --- path math
184 >
185 > joinPath(resource: URI, ...pathFragment: string[]): URI {
186 return URI.joinPath(resource, ...pathFragment);
187 }
188 > resources.ts
189 > basenameOrAuthority(resource: URI): string {
190 return basename(resource) || resource.authority;
191 }
192 > resources.ts
193 > basename(resource: URI, suffix?: string): string {
194 return paths.posix.basename(resource.path, suffix);
195 }
196 > resources.ts
197 > extname(resource: URI): string {
198 return paths.posix.extname(resource.path);
199 }
200 > resources.ts
201 > dirname(resource: URI): URI {
202 if (resource.path.length === 0) {
203 return resource;
217 });
218 }
219 > resources.ts
220 > normalizePath(resource: URI): URI {
221 if (!resource.path.length) {
222 return resource;
232 });
233 }
234 > resources.ts
235 > relativePath(from: URI, to: URI): string | undefined {
236 if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
237 return undefined;
257 return paths.posix.relative(fromPath, toPath);
258 }
259 > resources.ts
260 > resolvePath(base: URI, path: string): URI {
261 if (base.scheme === Schemas.file) {
262 const newURI = URI.file(paths.resolve(originalFSPath(base), path));
271 });
272 }
273 > resources.ts
274 > // --- misc
275 >
276 > isAbsolutePath(resource: URI): boolean {
277 return !!resource.path && resource.path[0] === '/';
278 }
279 > resources.ts
280 > isEqualAuthority(a1: string | undefined, a2: string | undefined) {
281 return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
282 }
283 > resources.ts
284 > hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
285 if (resource.scheme === Schemas.file) {
286 const fsp = originalFSPath(resource);
291 }
292 }
293 > resources.ts
294 > removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
295 // Make sure that the path isn't a drive letter. A trailing separator there is not removable.
296 if (hasTrailingPathSeparator(resource, sep)) {
299 return resource;
300 }
301 > resources.ts
302 > addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
303 let isRootSep: boolean = false;
304 if (resource.scheme === Schemas.file) {
315 return resource;
316 }
317 > } resources.ts
318 >
319 >
320 > /**
321 > * Unbiased utility that takes uris "as they are". This means it can be interchanged with
322 > * uri#toString() usages. The following is true
323 > * ```
324 > * assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
325 > * ```
326 > */
327 > export const extUri = new ExtUri(() => false);
328 >
329 > /**
330 > * BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
331 > * understand what you are doing.
332 > *
333 > * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
334 > *
335 > * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
336 > * because those uris come from a "trustworthy source". When creating unknown uris it's always
337 > * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
338 > * casing matters.
339 > */
340 > export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
341 // A file scheme resource is in the same platform as code, so ignore case for non linux platforms
342 // Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
343 return uri.scheme === Schemas.file ? !isLinux : true;
344 });
345 > resources.ts
346 >
347 > /**
348 > * BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
349 > * understand what you are doing.
350 > *
351 > * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
352 > *
353 > * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
354 > * because those uris come from a "trustworthy source". When creating unknown uris it's always
355 > * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
356 > * casing matters.
357 > */
358 > export const extUriIgnorePathCase = new ExtUri(_ => true);
359 >
360 > export const isEqual = extUri.isEqual.bind(extUri);
361 > export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
362 > export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
363 > export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
364 > export const basename = extUri.basename.bind(extUri);
365 > export const extname = extUri.extname.bind(extUri);
366 > export const dirname = extUri.dirname.bind(extUri);
367 > export const joinPath = extUri.joinPath.bind(extUri);
368 > export const normalizePath = extUri.normalizePath.bind(extUri);
369 > export const relativePath = extUri.relativePath.bind(extUri);
370 > export const resolvePath = extUri.resolvePath.bind(extUri);
371 > export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
372 > export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
373 > export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
374 > export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
375 > export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
376 >
377 > //#endregion
378 >
379 > export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
380 const distinctParents: T[] = [];
381 for (let i = 0; i < items.length; i++) {
396 return distinctParents;
397 }
398 > resources.ts
399 > /**
400 > * Data URI related helpers.
401 > */
402 > export namespace DataUri {
403 >
404 > export const META_DATA_LABEL = 'label';
405 > export const META_DATA_DESCRIPTION = 'description';
406 > export const META_DATA_SIZE = 'size';
407 > export const META_DATA_MIME = 'mime';
408 >
409 > export function parseMetaData(dataUri: URI): Map<string, string> {
410 const metadata = new Map<string, string>();
411
429 return metadata;
430 }
431 > } resources.ts
432 >
433 > export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI {
434 if (authority) {
435 let path = resource.path;
src/vs/platform/agentHost/common/state/protocol/channels-root/state.ts 244 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { ConfigSchema, JsonPrimitive, ProtectedResourceMetadata } from '../common/state.js';
10 > import type { TerminalInfo } from '../channels-terminal/state.js';
11 > import type { Customization } from '../channels-session/state.js';
12 >
13 > // ─── Root State ──────────────────────────────────────────────────────────────
14 >
15 > /**
16 > * Policy configuration state for a model.
17 > *
18 > * @category Root State
19 > */
20 > export const enum PolicyState {
21 > Enabled = 'enabled',
22 > Disabled = 'disabled',
23 > Unconfigured = 'unconfigured',
24 > }
25 >
26 > /**
27 > * Global state shared with every client subscribed to `ahp-root://`.
28 > *
29 > * @category Root State
30 > */
31 > export interface RootState {
32 > /** Available agent backends and their models */
33 > agents: AgentInfo[];
34 > /** Number of active (non-disposed) sessions on the server */
35 > activeSessions?: number;
36 > /** Known terminals on the server. Subscribe to individual terminal URIs for full state. */
37 > terminals?: TerminalInfo[];
38 > /** Agent host configuration schema and current values */
39 > config?: RootConfigState;
40 > /**
41 > * Additional implementation-defined metadata about the agent host itself.
42 > *
43 > * Clients MAY look for well-known keys here to provide enhanced UI.
44 > */
45 > _meta?: Record<string, unknown>;
46 > }
47 >
48 > /**
49 > * @category Root State
50 > */
51 > export interface AgentInfo {
52 > /** Agent provider ID (e.g. `'copilot'`) */
53 > provider: string;
54 > /** Human-readable name */
55 > displayName: string;
56 > /** Description string */
57 > description: string;
58 > /** Available models for this agent */
59 > models: SessionModelInfo[];
60 > /**
61 > * Protected resources this agent requires authentication for.
62 > *
63 > * Each entry describes an OAuth 2.0 protected resource using
64 > * [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) semantics.
65 > * Clients should obtain tokens from the declared `authorization_servers`
66 > * and push them via the `authenticate` command before creating sessions
67 > * with this agent.
68 > *
69 > * @see {@link /specification/authentication | Authentication}
70 > */
71 > protectedResources?: ProtectedResourceMetadata[];
72 > /**
73 > * Customizations associated with this agent.
74 > *
75 > * Either container customizations —
76 > * {@link PluginCustomization | `PluginCustomization`} entries the agent
77 > * bundles, plus {@link DirectoryCustomization | `DirectoryCustomization`}
78 > * entries it watches in any workspace it's used with — or top-level
79 > * {@link McpServerCustomization | `McpServerCustomization`} entries
80 > * the agent host declares directly. When a session is created with
81 > * this agent, these entries are augmented (e.g. directory URIs are
82 > * resolved against the workspace, children are parsed) and propagated
83 > * into the session's `customizations` list.
84 > */
85 > customizations?: Customization[];
86 > /**
87 > * Static capabilities the agent advertises about itself. Clients use these
88 > * to gate features (multi-chat, fork) instead of switching on the provider
89 > * id.
90 > */
91 > capabilities?: AgentCapabilities;
92 > }
93 >
94 > /**
95 > * Static capabilities an {@link AgentInfo} advertises. Modelled after MCP
96 > * capabilities: each field is opt-in and its presence (an empty object `{}`)
97 > * signals support, while absence means the feature is unsupported and the
98 > * corresponding client commands MUST NOT be used. Sub-fields carry
99 > * per-capability options.
100 > *
101 > * @category Root State
102 > */
103 > export interface AgentCapabilities {
104 > /**
105 > * The agent can host more than one concurrent chat per session. When absent,
106 > * clients MUST NOT call `createChat` to open chats beyond the default one the
107 > * session starts with. An empty object `{}` advertises multi-chat without
108 > * source-based creation; set {@link MultipleChatsCapability.fork} or
109 > * {@link MultipleChatsCapability.sideChat} to allow the corresponding mode.
110 > */
111 > multipleChats?: MultipleChatsCapability;
112 > /**
113 > * The session's agent can be granted tool access to more than one working
114 > * directory. The directories are treated as equal peers except where the
115 > * agent advertises {@link MultipleWorkingDirectoriesCapability.requiresPrimary}
116 > * (some backends need one directory designated as a primary root).
117 > *
118 > * When absent, clients MUST NOT mutate a session's or chat's working-directory
119 > * set and MUST NOT set more than one entry in
120 > * {@link CreateSessionParams.workingDirectories}.
121 > */
122 > multipleWorkingDirectories?: MultipleWorkingDirectoriesCapability;
123 > }
124 >
125 > /**
126 > * Options for the {@link AgentCapabilities.multipleChats} capability.
127 > *
128 > * @category Root State
129 > */
130 > export interface MultipleChatsCapability {
131 > /**
132 > * The agent can fork a chat from a specific turn. When absent or `false`,
133 > * clients MUST NOT pass a {@link ChatSource} with `kind: "fork"` to
134 > * `createChat`.
135 > * Forking always implies multi-chat support.
136 > */
137 > fork?: boolean;
138 > /**
139 > * The agent can create a side chat from a specific turn. When absent or
140 > * `false`, clients MUST NOT pass a {@link ChatSource} with
141 > * `kind: "sideChat"` to `createChat`.
142 > *
143 > * A side chat receives the source turn as context without copying the source
144 > * transcript into its own visible history. The source is identified by a
145 > * stable `turnId`, which the host resolves against the source chat's current
146 > * `activeTurn` or retained history. When it names the current active turn,
147 > * the host snapshots the available partial assistant response at creation
148 > * time. Side-chat support always implies multi-chat support.
149 > */
150 > sideChat?: boolean;
151 > }
152 >
153 > /**
154 > * Options for the {@link AgentCapabilities.multipleWorkingDirectories} capability.
155 > *
156 > * @category Root State
157 > */
158 > export interface MultipleWorkingDirectoriesCapability {
159 > /**
160 > * The agent requires each chat to designate one of its working directories as
161 > * the **primary** — a distinguished root the chat is centered on (e.g. the
162 > * agent's process root for that chat, the default location for relative
163 > * paths). Primary is a **per-chat** notion, fixed at chat creation. When
164 > * `true`, a client SHOULD supply {@link CreateChatParams.primaryWorkingDirectory}
165 > * (and {@link CreateSessionParams.primaryWorkingDirectory}, which seeds the
166 > * session's default chat); a host MAY reject creation that omits it, or fall
167 > * back to the first entry of the chat's working directories. The chosen
168 > * primary is reported (read-only) on {@link ChatState.primaryWorkingDirectory}.
169 > *
170 > * When absent or `false`, the agent has no primary — all directories are
171 > * equal peers and clients need not designate one.
172 > */
173 > requiresPrimary?: boolean;
174 > }
175 >
176 > /**
177 > * @category Root State
178 > */
179 > export interface SessionModelInfo {
180 > /** Model identifier */
181 > id: string;
182 > /** Provider this model belongs to */
183 > provider: string;
184 > /** Human-readable model name */
185 > name: string;
186 > /** Maximum context window size */
187 > maxContextWindow?: number;
188 > /** Maximum number of output tokens the model can generate */
189 > maxOutputTokens?: number;
190 > /** Maximum number of prompt (input) tokens the model accepts */
191 > maxPromptTokens?: number;
192 > /** Whether the model supports vision */
193 > supportsVision?: boolean;
194 > /** Policy configuration state */
195 > policyState?: PolicyState;
196 > /**
197 > * Configuration schema describing model-specific options (e.g. thinking
198 > * level). Clients present this as a form and pass the resolved values in
199 > * {@link ModelSelection.config} when creating or changing sessions.
200 > */
201 > configSchema?: ConfigSchema;
202 > /**
203 > * Additional provider-specific metadata for this model.
204 > *
205 > * Clients MAY look for well-known keys here to provide enhanced UI.
206 > * For example, a `pricing` key may carry model pricing metadata.
207 > */
208 > _meta?: Record<string, unknown>;
209 > }
210 >
211 > /**
212 > * A model selection: the chosen model ID together with any model-specific
213 > * configuration values whose keys correspond to the model's
214 > * {@link SessionModelInfo.configSchema}.
215 > *
216 > * @category Root State
217 > */
218 > export interface ModelSelection {
219 > /** Model identifier */
220 > id: string;
221 > /**
222 > * Model-specific configuration values. Values are JSON primitives: most
223 > * pickers produce strings, but some (e.g. a numeric context-size picker)
224 > * produce numbers or booleans, which are carried through as-is.
225 > */
226 > config?: Record<string, JsonPrimitive>;
227 > }
228 >
229 > // ─── Root Config Types ───────────────────────────────────────────────────────
230 >
231 > /**
232 > * Live agent-host configuration metadata.
233 > *
234 > * The schema describes the available configuration properties and the values
235 > * contain the current value for each resolved property.
236 > *
237 > * @category Root State
238 > */
239 > export interface RootConfigState {
240 > /** JSON Schema describing available configuration properties */
241 > schema: ConfigSchema;
242 > /** Current configuration values */
243 > values: Record<string, unknown>;
244 > }
src/vs/platform/agentHost/common/state/protocol/common/actions.ts 238 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- actions.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from './state.js';
10 >
11 > import type { RootAgentsChangedAction, RootActiveSessionsChangedAction, RootTerminalsChangedAction, RootConfigChangedAction } from '../channels-root/actions.js';
12 >
13 > import type { SessionReadyAction, SessionCreationFailedAction, SessionChatAddedAction, SessionChatRemovedAction, SessionChatUpdatedAction, SessionDefaultChatChangedAction, SessionTitleChangedAction, SessionServerToolsChangedAction, SessionActiveClientSetAction, SessionActiveClientRemovedAction, SessionWorkingDirectorySetAction, SessionWorkingDirectoryRemovedAction, SessionInputNeededSetAction, SessionInputNeededRemovedAction, SessionCustomizationsChangedAction, SessionCustomizationToggledAction, SessionCustomizationUpdatedAction, SessionCustomizationRemovedAction, SessionMcpServerStateChangedAction, SessionMcpServerStartRequestedAction, SessionMcpServerStopRequestedAction, SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction } from '../channels-session/actions.js';
14 >
15 > import type { ChatTurnStartedAction, ChatDeltaAction, ChatResponsePartAction, ChatToolCallStartAction, ChatToolCallDeltaAction, ChatToolCallReadyAction, ChatToolCallConfirmedAction, ChatToolCallCompleteAction, ChatToolCallResultConfirmedAction, ChatToolCallContentChangedAction, ChatToolCallAuthRequiredAction, ChatToolCallAuthResolvedAction, ChatTurnCompleteAction, ChatTurnCancelledAction, ChatErrorAction, ChatActivityChangedAction, ChatWorkingDirectorySetAction, ChatWorkingDirectoryRemovedAction, ChatUsageAction, ChatReasoningAction, ChatPendingMessageSetAction, ChatPendingMessageRemovedAction, ChatQueuedMessagesReorderedAction, ChatDraftChangedAction, ChatInputRequestedAction, ChatInputAnswerChangedAction, ChatInputCompletedAction, ChatTruncatedAction, ChatTurnsLoadedAction } from '../channels-chat/actions.js';
16 >
17 > import type { ChangesetStatusChangedAction, ChangesetFileSetAction, ChangesetFileRemovedAction, ChangesetFilesReviewChangedAction, ChangesetContentChangedAction, ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction } from '../channels-changeset/actions.js';
18 >
19 > import type { AnnotationsSetAction, AnnotationsUpdatedAction, AnnotationsRemovedAction, AnnotationsEntrySetAction, AnnotationsEntryRemovedAction } from '../channels-annotations/actions.js';
20 >
21 > import type { TerminalDataAction, TerminalInputAction, TerminalResizedAction, TerminalClaimedAction, TerminalTitleChangedAction, TerminalCwdChangedAction, TerminalExitedAction, TerminalClearedAction, TerminalCommandDetectionAvailableAction, TerminalCommandExecutedAction, TerminalCommandFinishedAction } from '../channels-terminal/actions.js';
22 >
23 > import type { ResourceWatchChangedAction } from '../channels-resource-watch/actions.js';
24 >
25 > // ─── Action Type Enum ────────────────────────────────────────────────────────
26 >
27 > /**
28 > * Discriminant values for all state actions.
29 > *
30 > * @category Actions
31 > */
32 > export const enum ActionType {
33 > RootAgentsChanged = 'root/agentsChanged',
34 > RootActiveSessionsChanged = 'root/activeSessionsChanged',
35 > SessionReady = 'session/ready',
36 > SessionCreationFailed = 'session/creationFailed',
37 > SessionChatAdded = 'session/chatAdded',
38 > SessionChatRemoved = 'session/chatRemoved',
39 > SessionChatUpdated = 'session/chatUpdated',
40 > SessionDefaultChatChanged = 'session/defaultChatChanged',
41 > ChatTurnStarted = 'chat/turnStarted',
42 > ChatDelta = 'chat/delta',
43 > ChatResponsePart = 'chat/responsePart',
44 > ChatToolCallStart = 'chat/toolCallStart',
45 > ChatToolCallDelta = 'chat/toolCallDelta',
46 > ChatToolCallReady = 'chat/toolCallReady',
47 > ChatToolCallConfirmed = 'chat/toolCallConfirmed',
48 > ChatToolCallComplete = 'chat/toolCallComplete',
49 > ChatToolCallResultConfirmed = 'chat/toolCallResultConfirmed',
50 > ChatToolCallContentChanged = 'chat/toolCallContentChanged',
51 > ChatToolCallAuthRequired = 'chat/toolCallAuthRequired',
52 > ChatToolCallAuthResolved = 'chat/toolCallAuthResolved',
53 > ChatTurnComplete = 'chat/turnComplete',
54 > ChatTurnCancelled = 'chat/turnCancelled',
55 > ChatError = 'chat/error',
56 > ChatActivityChanged = 'chat/activityChanged',
57 > ChatWorkingDirectorySet = 'chat/workingDirectorySet',
58 > ChatWorkingDirectoryRemoved = 'chat/workingDirectoryRemoved',
59 > SessionTitleChanged = 'session/titleChanged',
60 > ChatUsage = 'chat/usage',
61 > ChatReasoning = 'chat/reasoning',
62 > SessionServerToolsChanged = 'session/serverToolsChanged',
63 > SessionActiveClientSet = 'session/activeClientSet',
64 > SessionActiveClientRemoved = 'session/activeClientRemoved',
65 > SessionWorkingDirectorySet = 'session/workingDirectorySet',
66 > SessionWorkingDirectoryRemoved = 'session/workingDirectoryRemoved',
67 > SessionInputNeededSet = 'session/inputNeededSet',
68 > SessionInputNeededRemoved = 'session/inputNeededRemoved',
69 > ChatPendingMessageSet = 'chat/pendingMessageSet',
70 > ChatPendingMessageRemoved = 'chat/pendingMessageRemoved',
71 > ChatQueuedMessagesReordered = 'chat/queuedMessagesReordered',
72 > ChatDraftChanged = 'chat/draftChanged',
73 > ChatInputRequested = 'chat/inputRequested',
74 > ChatInputAnswerChanged = 'chat/inputAnswerChanged',
75 > ChatInputCompleted = 'chat/inputCompleted',
76 > SessionCustomizationsChanged = 'session/customizationsChanged',
77 > SessionCustomizationToggled = 'session/customizationToggled',
78 > SessionCustomizationUpdated = 'session/customizationUpdated',
79 > SessionCustomizationRemoved = 'session/customizationRemoved',
80 > SessionMcpServerStateChanged = 'session/mcpServerStateChanged',
81 > SessionMcpServerStartRequested = 'session/mcpServerStartRequested',
82 > SessionMcpServerStopRequested = 'session/mcpServerStopRequested',
83 > ChatTruncated = 'chat/truncated',
84 > ChatTurnsLoaded = 'chat/turnsLoaded',
85 > SessionIsReadChanged = 'session/isReadChanged',
86 > SessionIsArchivedChanged = 'session/isArchivedChanged',
87 > SessionActivityChanged = 'session/activityChanged',
88 > SessionChangesetsChanged = 'session/changesetsChanged',
89 > SessionConfigChanged = 'session/configChanged',
90 > SessionMetaChanged = 'session/metaChanged',
91 > ChangesetStatusChanged = 'changeset/statusChanged',
92 > ChangesetFileSet = 'changeset/fileSet',
93 > ChangesetFileRemoved = 'changeset/fileRemoved',
94 > ChangesetFilesReviewChanged = 'changeset/filesReviewChanged',
95 > ChangesetContentChanged = 'changeset/contentChanged',
96 > ChangesetOperationsChanged = 'changeset/operationsChanged',
97 > ChangesetOperationStatusChanged = 'changeset/operationStatusChanged',
98 > ChangesetCleared = 'changeset/cleared',
99 > AnnotationsSet = 'annotations/set',
100 > AnnotationsUpdated = 'annotations/updated',
101 > AnnotationsRemoved = 'annotations/removed',
102 > AnnotationsEntrySet = 'annotations/entrySet',
103 > AnnotationsEntryRemoved = 'annotations/entryRemoved',
104 > RootTerminalsChanged = 'root/terminalsChanged',
105 > RootConfigChanged = 'root/configChanged',
106 > TerminalData = 'terminal/data',
107 > TerminalInput = 'terminal/input',
108 > TerminalResized = 'terminal/resized',
109 > TerminalClaimed = 'terminal/claimed',
110 > TerminalTitleChanged = 'terminal/titleChanged',
111 > TerminalCwdChanged = 'terminal/cwdChanged',
112 > TerminalExited = 'terminal/exited',
113 > TerminalCleared = 'terminal/cleared',
114 > TerminalCommandDetectionAvailable = 'terminal/commandDetectionAvailable',
115 > TerminalCommandExecuted = 'terminal/commandExecuted',
116 > TerminalCommandFinished = 'terminal/commandFinished',
117 > ResourceWatchChanged = 'resourceWatch/changed',
118 > }
119 >
120 > // ─── Action Envelope ─────────────────────────────────────────────────────────
121 >
122 > /**
123 > * Identifies the client that originally dispatched an action.
124 > */
125 > export interface ActionOrigin {
126 > clientId: string;
127 > clientSeq: number;
128 > }
129 >
130 > /**
131 > * Every action is wrapped in an `ActionEnvelope`.
132 > *
133 > * The envelope identifies the channel the action belongs to (e.g.
134 > * `ahp-root://` for root actions, the session URI for session actions, the
135 > * terminal URI for terminal actions). Individual action payloads carry only
136 > * fields that are intrinsic to the action; the channel comes from the
137 > * envelope so that any subscribable resource can route its actions uniformly.
138 > */
139 > export interface ActionEnvelope {
140 > /** Channel URI this action belongs to. */
141 > readonly channel: URI;
142 > readonly action: StateAction;
143 > readonly serverSeq: number;
144 > readonly origin: ActionOrigin | undefined;
145 > readonly rejectionReason?: string;
146 > }
147 >
148 > // ─── Discriminated Union ─────────────────────────────────────────────────────
149 >
150 > /**
151 > * Discriminated union of all state actions.
152 > */
153 > export type StateAction =
154 > | RootAgentsChangedAction
155 > | RootActiveSessionsChangedAction
156 > | RootTerminalsChangedAction
157 > | RootConfigChangedAction
158 > | SessionReadyAction
159 > | SessionCreationFailedAction
160 > | SessionChatAddedAction
161 > | SessionChatRemovedAction
162 > | SessionChatUpdatedAction
163 > | SessionDefaultChatChangedAction
164 > | SessionTitleChangedAction
165 > | SessionServerToolsChangedAction
166 > | SessionActiveClientSetAction
167 > | SessionActiveClientRemovedAction
168 > | SessionWorkingDirectorySetAction
169 > | SessionWorkingDirectoryRemovedAction
170 > | SessionInputNeededSetAction
171 > | SessionInputNeededRemovedAction
172 > | SessionCustomizationsChangedAction
173 > | SessionCustomizationToggledAction
174 > | SessionCustomizationUpdatedAction
175 > | SessionCustomizationRemovedAction
176 > | SessionMcpServerStateChangedAction
177 > | SessionMcpServerStartRequestedAction
178 > | SessionMcpServerStopRequestedAction
179 > | SessionIsReadChangedAction
180 > | SessionIsArchivedChangedAction
181 > | SessionActivityChangedAction
182 > | SessionChangesetsChangedAction
183 > | SessionConfigChangedAction
184 > | SessionMetaChangedAction
185 > | ChatTurnStartedAction
186 > | ChatDeltaAction
187 > | ChatResponsePartAction
188 > | ChatToolCallStartAction
189 > | ChatToolCallDeltaAction
190 > | ChatToolCallReadyAction
191 > | ChatToolCallConfirmedAction
192 > | ChatToolCallCompleteAction
193 > | ChatToolCallResultConfirmedAction
194 > | ChatToolCallContentChangedAction
195 > | ChatToolCallAuthRequiredAction
196 > | ChatToolCallAuthResolvedAction
197 > | ChatTurnCompleteAction
198 > | ChatTurnCancelledAction
199 > | ChatErrorAction
200 > | ChatActivityChangedAction
201 > | ChatWorkingDirectorySetAction
202 > | ChatWorkingDirectoryRemovedAction
203 > | ChatUsageAction
204 > | ChatReasoningAction
205 > | ChatPendingMessageSetAction
206 > | ChatPendingMessageRemovedAction
207 > | ChatQueuedMessagesReorderedAction
208 > | ChatDraftChangedAction
209 > | ChatInputRequestedAction
210 > | ChatInputAnswerChangedAction
211 > | ChatInputCompletedAction
212 > | ChatTruncatedAction
213 > | ChatTurnsLoadedAction
214 > | ChangesetStatusChangedAction
215 > | ChangesetFileSetAction
216 > | ChangesetFileRemovedAction
217 > | ChangesetFilesReviewChangedAction
218 > | ChangesetContentChangedAction
219 > | ChangesetOperationsChangedAction
220 > | ChangesetOperationStatusChangedAction
221 > | ChangesetClearedAction
222 > | AnnotationsSetAction
223 > | AnnotationsUpdatedAction
224 > | AnnotationsRemovedAction
225 > | AnnotationsEntrySetAction
226 > | AnnotationsEntryRemovedAction
227 > | TerminalDataAction
228 > | TerminalInputAction
229 > | TerminalResizedAction
230 > | TerminalClaimedAction
231 > | TerminalTitleChangedAction
232 > | TerminalCwdChangedAction
233 > | TerminalExitedAction
234 > | TerminalClearedAction
235 > | TerminalCommandDetectionAvailableAction
236 > | TerminalCommandExecutedAction
237 > | TerminalCommandFinishedAction
238 > | ResourceWatchChangedAction;
src/vs/platform/agentHost/common/state/sessionActions.ts 220 covered LOC · 11 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionActions.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Action and notification types for the sessions process protocol.
7 > // Re-exports from the auto-generated protocol layer with local aliases.
8 > //
9 > // VS Code-specific additions:
10 > // - IToolCallStartAction extends protocol with `toolKind` and `language`
11 > // - isRootAction / isSessionAction type guards
12 > // - INotification alias for ProtocolNotification
13 >
14 > // ---- Re-exports from protocol -----------------------------------------------
15 >
16 > export {
17 > ActionType,
18 > type ActionEnvelope,
19 > type ActionOrigin,
20 > type RootAgentsChangedAction,
21 > type RootActiveSessionsChangedAction,
22 > type SessionCreationFailedAction,
23 > type SessionChatAddedAction,
24 > type SessionChatRemovedAction,
25 > type SessionChatUpdatedAction,
26 > type SessionDefaultChatChangedAction,
27 > type ChatDeltaAction,
28 > type ChatErrorAction,
29 > type SessionReadyAction,
30 > type ChatReasoningAction,
31 > type ChatResponsePartAction,
32 > type ChatToolCallCompleteAction,
33 > type ChatToolCallConfirmedAction,
34 > type ChatToolCallApprovedAction,
35 > type ChatToolCallDeniedAction,
36 > type ChatToolCallDeltaAction,
37 > type ChatToolCallReadyAction,
38 > type ChatToolCallResultConfirmedAction,
39 > type ChatToolCallStartAction,
40 > type SessionTitleChangedAction,
41 > type ChatTurnCancelledAction,
42 > type ChatTurnCompleteAction,
43 > type ChatTurnStartedAction,
44 > type ChatUsageAction,
45 > type SessionServerToolsChangedAction,
46 > type SessionActiveClientSetAction,
47 > type SessionActiveClientRemovedAction,
48 > type SessionCustomizationsChangedAction,
49 > type SessionCustomizationToggledAction,
50 > type ChatPendingMessageSetAction,
51 > type ChatPendingMessageRemovedAction,
52 > type ChatQueuedMessagesReorderedAction,
53 > type ChatInputRequestedAction,
54 > type ChatInputCompletedAction,
55 > type ChatInputAnswerChangedAction,
56 > type SessionIsReadChangedAction,
57 > type SessionIsArchivedChangedAction,
58 > type ChatToolCallContentChangedAction,
59 > type ChatTruncatedAction,
60 > type ChangesetStatusChangedAction,
61 > type ChangesetFileSetAction,
62 > type ChangesetFileRemovedAction,
63 > type ChangesetContentChangedAction,
64 > type ChangesetOperationsChangedAction,
65 > type ChangesetClearedAction,
66 > type AnnotationsSetAction,
67 > type AnnotationsUpdatedAction,
68 > type AnnotationsRemovedAction,
69 > type AnnotationsEntrySetAction,
70 > type AnnotationsEntryRemovedAction,
71 > type ResourceWatchChangedAction,
72 > type StateAction,
73 > } from './protocol/actions.js';
74 >
75 > export {
76 > AuthRequiredReason,
77 > type SessionAddedParams,
78 > type SessionRemovedParams,
79 > type SessionSummaryChangedParams,
80 > type ProgressParams,
81 > type AuthRequiredParams,
82 > } from './protocol/notifications.js';
83 >
84 > /**
85 > * String discriminants for the protocol notification methods that previously
86 > * lived inside a `notification` wrapper. These values are the JSON-RPC method
87 > * names sent over the wire by a channels-era server; they are also the `type`
88 > * discriminant on {@link ProtocolNotification} variants.
89 > */
90 > export const NotificationType = {
91 > SessionAdded: 'root/sessionAdded',
92 > SessionRemoved: 'root/sessionRemoved',
93 > SessionSummaryChanged: 'root/sessionSummaryChanged',
94 > Progress: 'root/progress',
95 > AuthRequired: 'auth/required',
96 > } as const;
97 > export type NotificationType = typeof NotificationType[keyof typeof NotificationType];
98 >
99 > // ---- Local aliases for short names ------------------------------------------
100 > // Consumers use these shorter names; they're type-only aliases.
101 >
102 > import type {
103 > RootAgentsChangedAction,
104 > RootActiveSessionsChangedAction,
105 > ChatDeltaAction,
106 > ChatReasoningAction,
107 > ChatResponsePartAction,
108 > ChatToolCallApprovedAction,
109 > ChatToolCallCompleteAction,
110 > ChatToolCallConfirmedAction,
111 > ChatToolCallDeniedAction,
112 > ChatToolCallDeltaAction,
113 > ChatToolCallReadyAction,
114 > ChatToolCallResultConfirmedAction,
115 > ChatToolCallStartAction,
116 > SessionTitleChangedAction,
117 > ChatTurnCancelledAction,
118 > ChatTurnCompleteAction,
119 > ChatTurnStartedAction,
120 > ChatErrorAction,
121 > ChatUsageAction,
122 > ChatToolCallContentChangedAction,
123 > StateAction,
124 > ChatPendingMessageSetAction,
125 > ChatPendingMessageRemovedAction,
126 > ChatQueuedMessagesReorderedAction,
127 > SessionIsReadChangedAction,
128 > SessionIsArchivedChangedAction,
129 > RootConfigChangedAction,
130 > } from './protocol/actions.js';
131 >
132 > import type { SessionAddedParams, SessionRemovedParams, SessionSummaryChangedParams, ProgressParams, AuthRequiredParams } from './protocol/notifications.js';
133 > import type { RootAction as IRootAction_, SessionAction as ISessionAction_, ChatAction as IChatAction_, ClientSessionAction as IClientSessionAction_, ServerSessionAction as IServerSessionAction_, ClientChatAction as IClientChatAction_, ServerChatAction as IServerChatAction_, TerminalAction as ITerminalAction_, ClientTerminalAction as IClientTerminalAction_, ChangesetAction as IChangesetAction_, ClientChangesetAction as IClientChangesetAction_, AnnotationsAction as IAnnotationsAction_, ClientAnnotationsAction as IClientAnnotationsAction_ } from './protocol/action-origin.generated.js';
134 >
135 > /**
136 > * Discriminated union of all server→client protocol notifications other than
137 > * the action envelope. Each variant carries its protocol `method` so callers
138 > * can switch on `type` the same way they did against the old `NotificationType`
139 > * enum.
140 > */
141 > export type ProtocolNotification =
142 > | ({ type: 'root/sessionAdded' } & SessionAddedParams)
143 > | ({ type: 'root/sessionRemoved' } & SessionRemovedParams)
144 > | ({ type: 'root/sessionSummaryChanged' } & SessionSummaryChangedParams)
145 > | ({ type: 'root/progress' } & ProgressParams)
146 > | ({ type: 'auth/required' } & AuthRequiredParams);
147 >
148 > export type RootAction = IRootAction_;
149 > export type SessionAction = ISessionAction_;
150 > export type ChatAction = IChatAction_;
151 > export type ClientSessionAction = IClientSessionAction_;
152 > export type ServerSessionAction = IServerSessionAction_;
153 > export type ClientChatAction = IClientChatAction_;
154 > export type ServerChatAction = IServerChatAction_;
155 > export type TerminalAction = ITerminalAction_;
156 > export type ClientTerminalAction = IClientTerminalAction_;
157 > export type ChangesetAction = IChangesetAction_;
158 > export type ClientChangesetAction = IClientChangesetAction_;
159 > export type AnnotationsAction = IAnnotationsAction_;
160 > export type ClientAnnotationsAction = IClientAnnotationsAction_;
161 >
162 > // Root actions
163 > export type IAgentsChangedAction = RootAgentsChangedAction;
164 > export type IActiveSessionsChangedAction = RootActiveSessionsChangedAction;
165 > export type IRootConfigChangedAction = RootConfigChangedAction;
166 >
167 > // Chat/turn actions — short aliases (turns now live on the chat channel)
168 > export type ITurnStartedAction = ChatTurnStartedAction;
169 > export type IDeltaAction = ChatDeltaAction;
170 > export type IResponsePartAction = ChatResponsePartAction;
171 > export type IToolCallStartAction = ChatToolCallStartAction;
172 > export type IToolCallDeltaAction = ChatToolCallDeltaAction;
173 > export type IToolCallReadyAction = ChatToolCallReadyAction;
174 > export type IToolCallApprovedAction = ChatToolCallApprovedAction;
175 > export type IToolCallDeniedAction = ChatToolCallDeniedAction;
176 > export type IToolCallConfirmedAction = ChatToolCallConfirmedAction;
177 > export type IToolCallCompleteAction = ChatToolCallCompleteAction;
178 > export type IToolCallResultConfirmedAction = ChatToolCallResultConfirmedAction;
179 > export type ITurnCompleteAction = ChatTurnCompleteAction;
180 > export type ITurnCancelledAction = ChatTurnCancelledAction;
181 > export type ITitleChangedAction = SessionTitleChangedAction;
182 > export type IUsageAction = ChatUsageAction;
183 > export type IReasoningAction = ChatReasoningAction;
184 > export type IErrorAction = ChatErrorAction;
185 > export type IToolCallContentChangedAction = ChatToolCallContentChangedAction;
186 > export type ICustomizationsChangedAction = import('./protocol/actions.js').SessionCustomizationsChangedAction;
187 > export type ICustomizationToggledAction = import('./protocol/actions.js').SessionCustomizationToggledAction;
188 >
189 > export type IPendingMessageSetAction = ChatPendingMessageSetAction;
190 > export type IPendingMessageRemovedAction = ChatPendingMessageRemovedAction;
191 > export type IQueuedMessagesReorderedAction = ChatQueuedMessagesReorderedAction;
192 > export type IIsReadChangedAction = SessionIsReadChangedAction;
193 > export type IIsArchivedChangedAction = SessionIsArchivedChangedAction;
194 >
195 > // Notifications
196 > export type INotification = ProtocolNotification;
197 >
198 > // ---- Type guards ------------------------------------------------------------
199 >
200 > export function isRootAction(action: StateAction): action is RootAction {
201 > return action.type.startsWith('root/'); sessionActions.ts
202 > }
204 > export function isSessionAction(action: StateAction): action is SessionAction {
205 > return action.type.startsWith('session/'); sessionActions.ts
206 > }
208 > export function isChatAction(action: StateAction): action is ChatAction {
209 > return action.type.startsWith('chat/'); sessionActions.ts
210 > }
212 > export function isTerminalAction(action: StateAction): action is TerminalAction {
213 return action.type.startsWith('terminal/');
214 }
216 > export function isChangesetAction(action: StateAction): action is ChangesetAction {
217 > return action.type.startsWith('changeset/'); sessionActions.ts
218 > }
220 > export function isAnnotationsAction(action: StateAction): action is AnnotationsAction {
221 > return action.type.startsWith('annotations/'); sessionActions.ts
222 > }
src/vs/base/test/common/virtualScheduling/processor.ts 215 covered LOC · 19 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- processor.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken } from '../../../common/cancellation.js';
7 > import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js';
8 > import { Embedding, nextMacrotask } from './embedding.js';
9 > import { TimeApi } from './timeApi.js';
10 > import { ROOT_TRACE, TraceContext } from './trace.js';
11 > import { EventSource, VirtualClock, VirtualEvent, VirtualTime } from './virtualClock.js';
12 >
13 > // ============================================================================
14 > // Termination policy
15 > // ============================================================================
16 >
17 > /**
18 > * When a {@link Run} should terminate.
19 > *
20 > * Greenfield design choice: termination is *always* explicit. There is no
21 > * "bare run()" that terminates on first empty queue, because that creates a
22 > * race with the caller's microtask chain (the run can resolve before the
23 > * caller's `.then` has had a chance to schedule).
24 > */
25 > export type TerminationPolicy =
26 > /** Resolve as soon as the virtual queue is empty. */
27 > | { readonly kind: 'idle' }
28 > /** Resolve when the token is cancelled AND the queue is empty. */
29 > | { readonly kind: 'token'; readonly token: CancellationToken }
30 > /** Resolve when virtual time has reached `time` and all events scheduled
31 > * at or before `time` have been processed. A sentinel event at `time`
32 > * is scheduled by the processor so virtual time always reaches it. */
33 > | { readonly kind: 'time'; readonly time: VirtualTime };
34 >
35 > export const untilIdle: TerminationPolicy = { kind: 'idle' };
36 > export function untilToken(token: CancellationToken): TerminationPolicy { return { kind: 'token', token }; }
37 > export function untilTime(time: VirtualTime): TerminationPolicy { return { kind: 'time', time }; }
38 >
39 > export interface RunOptions {
40 > readonly until: TerminationPolicy;
41 > /** Maximum number of virtual events this run will execute. Default: 100. */
42 > readonly maxEvents?: number;
43 > /** Maximum causal-trace depth this run will tolerate. Useful for catching
44 > * runaway self-rescheduling timers. */
45 > readonly maxTraceDepth?: number;
46 > }
47 >
48 > // ============================================================================
49 > // Run — internal state for a single processor.run() invocation
50 > // ============================================================================
51 >
52 > type RunStatus = 'continue' | 'done' | { readonly error: Error };
53 >
54 > class Run {
55 > private static _idCounter = 0;
56 > public readonly id = ++Run._idCounter;
57 >
58 > public readonly promise: Promise<void>;
59 > private _resolve!: () => void;
60 > private _reject!: (e: Error) => void;
61 > private _settled = false;
62 > public get settled(): boolean { return this._settled; }
63 >
64 > constructor(
65 public readonly options: RunOptions,
66 public readonly executedAtStart: number,
69 this.promise = new Promise<void>((res, rej) => { this._resolve = res; this._reject = rej; });
70 }
72 > settle(error?: Error): void {
73 if (this._settled) { return; }
74 this._settled = true;
75 if (error) { this._reject(error); } else { this._resolve(); }
76 }
78 > evaluate(clock: VirtualClock, executedTotal: number, makeOverflow: () => Error): RunStatus {
79 const local = executedTotal - this.executedAtStart;
80 if (local >= this.maxEvents && clock.hasEvents) {
98 }
99 }
100 > } processor.ts
101 >
102 > // ============================================================================
103 > // Step outcome — what the pure state machine tells the trampoline
104 > // ============================================================================
105 >
106 > type StepOutcome =
107 > /** Either a virtual event was executed, or a run was rejected for a
108 > * bookkeeping reason (depth/event overflow). The trampoline should let
109 > * the embedding decide how to reach the next step. */
110 > | 'progress'
111 > /** No actionable event under any active deadline. The trampoline should
112 > * park until something wakes the processor. */
113 > | 'park'
114 > /** No active runs. The trampoline should stop driving. */
115 > | 'quiesce';
116 >
117 > // ============================================================================
118 > // VirtualTimeProcessor
119 > // ============================================================================
120 >
121 > export interface VirtualTimeProcessorOptions {
122 > readonly defaultMaxEvents?: number;
123 > }
124 >
125 > /**
126 > * # VirtualTimeProcessor
127 > *
128 > * Drives a {@link VirtualClock} from the host event loop. This is the
129 > * **embedding** of a small virtual event loop into the host event loop.
130 > *
131 > * ## Responsibilities, separated
132 > *
133 > * - {@link _step} is a *pure* state-machine advance. It reads the clock,
134 > * decides what to do, optionally executes one virtual event, and returns
135 > * a {@link StepOutcome}. It never touches host time.
136 > *
137 > * - {@link _drive} is the *trampoline*. It calls `_step` and lets the
138 > * {@link Embedding} decide whether to loop in place (`'continueSync'`)
139 > * or schedule the next iteration on the host (`'cbScheduled'`). It is
140 > * the only code that touches host time.
141 > *
142 > * - {@link Run} carries the user's termination predicate. Runs are pure
143 > * over `_step`'s observations; they never schedule.
144 > *
145 > * ## Invariants
146 > *
147 > * 1. **Single driver.** At any moment at most one `_drive` invocation is
148 > * active per processor (the `_inDrive` guard).
149 > *
150 > * 2. **Step is pure w.r.t. host time.** `_step` only reads the clock,
151 > * mutates the run set via settling, and synchronously runs at most one
152 > * virtual event. It never calls into a host time API.
153 > *
154 > * 3. **Embedding chooses the host primitive.** Whether the next step runs
155 > * inline, after a microtask drain, or on a paint frame is entirely the
156 > * embedding's decision — *per event*.
157 > *
158 > * 4. **Park is breakable.** While parked, the processor wakes on
159 > * {@link VirtualClock.onEventScheduled}, on a token cancellation, and
160 > * on a new run being added.
161 > *
162 > * 5. **Disposal is terminal.** After dispose, all runs are rejected and
163 > * `_step`/`_drive` short-circuit to `'quiesce'`.
164 > *
165 > * ## On the trace-reset sink
166 > *
167 > * The trace context's deferred reset (see {@link TraceContext.runAsHandler})
168 > * needs a "fire after the microtask closure" primitive. The processor passes
169 > * its *own* {@link nextMacrotask} as that sink, so the reset goes through
170 > * the same primitive the embedding uses for its own host hops. This removes
171 > * any race between the processor's hops and the trace-reset timer.
172 > */
173 > export class VirtualTimeProcessor extends Disposable {
174 >
175 > private readonly _runs = new Map<Run, IDisposable>();
176 > private readonly _history: VirtualEvent[] = [];
177 > private _executedTotal = 0;
178 > private _disposed = false;
179 >
180 > private _inDrive = false;
181 > private _parkCleanup: IDisposable | undefined;
182 >
183 > private readonly _defaultMaxEvents: number;
184 >
185 > public get history(): readonly VirtualEvent[] { return this._history; }
186 > public get executedTotal(): number { return this._executedTotal; }
187 >
188 > constructor(
189 private readonly _clock: VirtualClock,
190 private readonly _embedding: Embedding,
196 this._register({ dispose: () => this._onDispose() });
197 }
198 > processor.ts
199 > // ---- Public API -----------------------------------------------------
200 >
201 > /** Start a run with the given termination policy. */
202 > run(options: RunOptions): Promise<void> {
203 const run = new Run(options, this._executedTotal, options.maxEvents ?? this._defaultMaxEvents);
204 const cleanup = new DisposableStore();
226 return run.promise;
227 }
228 > processor.ts
229 > // ---- The pure step --------------------------------------------------
230 >
231 > private _step(): StepOutcome {
232 if (this._disposed) { return 'quiesce'; }
233
254 return 'progress';
255 }
256 > processor.ts
257 > private _executeOne(event: VirtualEvent): void {
258 try {
259 TraceContext.instance.runAsHandler(
280 }
281 }
282 > processor.ts
283 > // ---- The trampoline -------------------------------------------------
284 >
285 > private readonly _drive = (): void => {
286 > if (this._inDrive) { return; }
287 > this._inDrive = true;
288 > try {
289 > while (true) {
290 > const outcome = this._step();
291 > if (outcome === 'quiesce') { return; }
292 > if (outcome === 'park') { this._park(); return; } processor.ts
293 >
294 > // 'progress': read the next event so the embedding can pick a
295 > // per-event primitive. If there is none, loop and let the next
296 > // `_step` decide between 'park' and 'quiesce'.
297 > const next = this._clock.peekNext();
298 > if (next === undefined) { continue; }
299 > processor.ts
300 > const choice = this._embedding(next, this._drive);
301 > if (choice === 'cbScheduled') { return; }
302 > // 'continueSync': loop in place. processor.ts
303 > }
304 > } finally {
305 > this._inDrive = false;
306 > }
307 > };
308 >
309 > // ---- Park & wake ----------------------------------------------------
310 >
311 > private _park(): void {
312 this._unpark();
313 const store = new DisposableStore();
315 this._parkCleanup = store;
316 }
317 > processor.ts
318 > private _unpark(): void {
319 this._parkCleanup?.dispose();
320 this._parkCleanup = undefined;
321 }
322 > processor.ts
323 > private _wake(): void {
324 if (this._disposed) { return; }
325 this._unpark();
337 nextMacrotask(this._realApi, this._drive);
338 }
339 > processor.ts
340 > // ---- Run lifecycle --------------------------------------------------
341 >
342 > private _settleFinishedRuns(): void {
343 for (const run of [...this._runs.keys()]) {
344 if (run.settled) { continue; }
351 }
352 }
353 > processor.ts
354 > private _settleRun(run: Run, error?: Error): void {
355 const cleanup = this._runs.get(run);
356 if (!cleanup) { return; }
359 run.settle(error);
360 }
361 > processor.ts
362 > private _buildOverflow(run: Run): Error {
363 const local = this._executedTotal - run.executedAtStart;
364 return new Error(
367 );
368 }
369 > processor.ts
370 > private _buildDepthOverflow(run: Run, depth: number): Error {
371 return new Error(
372 `[VirtualTimeProcessor] Run #${run.id} exceeded maxTraceDepth (${run.options.maxTraceDepth}) — ` +
375 );
376 }
377 > processor.ts
378 > private _onDispose(): void {
379 this._disposed = true;
380 this._unpark();
382 for (const run of [...this._runs.keys()]) { this._settleRun(run, err); }
383 }
384 > } processor.ts
src/vs/platform/agentHost/common/state/protocol/common/errors.ts 215 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- errors.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { ProtectedResourceMetadata } from './state.js';
10 > import type { ResourceRequestParams } from './commands.js';
11 >
12 > // ─── Standard JSON-RPC Codes ─────────────────────────────────────────────────
13 >
14 > /**
15 > * Standard JSON-RPC 2.0 error codes.
16 > *
17 > * @category Standard JSON-RPC Codes
18 > */
19 > export const JsonRpcErrorCodes = {
20 > /** Invalid JSON */
21 > ParseError: -32700,
22 > /** Not a valid JSON-RPC request */
23 > InvalidRequest: -32600,
24 > /** Unknown method name */
25 > MethodNotFound: -32601,
26 > /** Invalid method parameters */
27 > InvalidParams: -32602,
28 > /** Unspecified server error */
29 > InternalError: -32603,
30 > } as const;
31 >
32 > // ─── AHP Application Codes ──────────────────────────────────────────────────
33 >
34 > /**
35 > * AHP application-specific error codes.
36 > *
37 > * @category AHP Application Codes
38 > * @version 1
39 > */
40 > export const AhpErrorCodes = {
41 > /** The referenced session URI does not exist */
42 > SessionNotFound: -32001,
43 > /** The requested agent provider is not registered */
44 > ProviderNotFound: -32002,
45 > /** A session with the given URI already exists */
46 > SessionAlreadyExists: -32003,
47 > /** The operation requires no active turn, but one is in progress */
48 > TurnInProgress: -32004,
49 > /**
50 > * The server cannot speak any of the protocol versions offered by the
51 > * client in `InitializeParams.protocolVersions`. The `data` field of the
52 > * JSON-RPC error MAY be an `UnsupportedProtocolVersionErrorData` advertising
53 > * the protocol versions the server is willing to speak.
54 > */
55 > UnsupportedProtocolVersion: -32005,
56 > /** The requested content URI does not exist */
57 > ContentNotFound: -32006,
58 > /**
59 > * A command failed because the client has not authenticated for a required
60 > * protected resource. The `data` field of the JSON-RPC error MUST be an
61 > * `AuthRequiredErrorData` describing the resources that require
62 > * authentication.
63 > *
64 > * @see {@link /specification/authentication | Authentication}
65 > */
66 > AuthRequired: -32007,
67 > /** The requested file, folder, or URI does not exist */
68 > NotFound: -32008,
69 > /**
70 > * The client is not permitted to access the requested resource.
71 > *
72 > * Servers SHOULD return this when a client attempts to read or browse
73 > * a path outside the allowed set (e.g. outside the session's working
74 > * directory or workspace roots).
75 > *
76 > * The `data` field of the JSON-RPC error MAY be a
77 > * `PermissionDeniedErrorData` advertising a `resourceRequest` that, if
78 > * granted, would unlock the operation.
79 > */
80 > PermissionDenied: -32009,
81 > /**
82 > * The target resource already exists and the operation does not allow
83 > * overwriting (e.g. `resourceWrite` with `createOnly: true`).
84 > */
85 > AlreadyExists: -32010,
86 > /**
87 > * An optimistic-concurrency precondition failed.
88 > *
89 > * Returned when a request carries a precondition token that no longer
90 > * matches the receiver's current state — for example, `resourceWrite`
91 > * with an `ifMatch` etag that has been superseded by a concurrent
92 > * write. Callers SHOULD re-read the resource (e.g. via
93 > * `resourceResolve`) and decide whether to retry the operation with the
94 > * fresh token or surface the conflict to the user.
95 > */
96 > Conflict: -32011,
97 > } as const;
98 >
99 > /** Union type of all AHP application error codes. */
100 > export type AhpErrorCode = (typeof AhpErrorCodes)[keyof typeof AhpErrorCodes];
101 >
102 > /** Union type of all JSON-RPC error codes. */
103 > export type JsonRpcErrorCode = (typeof JsonRpcErrorCodes)[keyof typeof JsonRpcErrorCodes];
104 >
105 > // ─── Error Detail Types ──────────────────────────────────────────────────────
106 >
107 > /**
108 > * Details carried in the `data` field of an `AuthRequired` (-32007) error.
109 > *
110 > * Wraps the protected resource list in `{ resources: [...] }` rather than
111 > * returning a bare array, so additional fields can be added in future
112 > * versions without breaking the wire shape.
113 > *
114 > * @category Error Details
115 > * @version 1
116 > */
117 > export interface AuthRequiredErrorData {
118 > /** Protected resources that require authentication. */
119 > resources: ProtectedResourceMetadata[];
120 > }
121 >
122 > /**
123 > * Details carried in the `data` field of a `PermissionDenied` (-32009) error.
124 > *
125 > * The receiver MAY advertise a `resourceRequest` payload describing the
126 > * access that, if granted, would unlock the operation. The caller MAY then
127 > * issue `resourceRequest` with that payload to negotiate access.
128 > *
129 > * @category Error Details
130 > * @version 1
131 > */
132 > export interface PermissionDeniedErrorData {
133 > /**
134 > * The resource access that, if granted via `resourceRequest`, would unlock
135 > * the operation. Omitted when no specific access grant would resolve the
136 > * denial (for example, when the resource is fundamentally inaccessible).
137 > */
138 > request?: ResourceRequestParams;
139 > }
140 >
141 > /**
142 > * Details carried in the `data` field of an `UnsupportedProtocolVersion`
143 > * (-32005) error.
144 > *
145 > * @category Error Details
146 > * @version 1
147 > */
148 > export interface UnsupportedProtocolVersionErrorData {
149 > /**
150 > * Protocol versions the server is willing to speak.
151 > *
152 > * Each entry is either a [SemVer](https://semver.org) `MAJOR.MINOR.PATCH`
153 > * string (e.g. `"0.1.0"`) or a [SemVer range](https://semver.org/#spec-item-11)
154 > * constraint (e.g. `">=0.1.0 <0.3.0"` or `"^0.2.0"`).
155 > */
156 > supportedVersions: string[];
157 > }
158 >
159 > /**
160 > * Maps each AHP error code that carries structured `data` to the type of
161 > * that data.
162 > *
163 > * Error codes not present in this map either have no `data` payload or
164 > * carry an unspecified payload that callers SHOULD treat as `unknown`.
165 > *
166 > * @category Error Details
167 > * @version 1
168 > */
169 > export interface AhpErrorDetailsMap {
170 > [AhpErrorCodes.AuthRequired]: AuthRequiredErrorData;
171 > [AhpErrorCodes.PermissionDenied]: PermissionDeniedErrorData;
172 > [AhpErrorCodes.UnsupportedProtocolVersion]: UnsupportedProtocolVersionErrorData;
173 > }
174 >
175 > /** AHP error codes that carry a structured `data` payload. */
176 > export type AhpErrorCodeWithData = keyof AhpErrorDetailsMap;
177 >
178 > /**
179 > * A typed JSON-RPC error object whose `data` is narrowed by `code`.
180 > *
181 > * Distributes over the `AhpErrorCode` union so narrowing on `code` reveals
182 > * the precise `data` type. For codes listed in {@link AhpErrorDetailsMap}
183 > * `data` is required; for all other codes `data` is an optional `unknown`.
184 > *
185 > * ```ts
186 > * function handle(err: AhpError) {
187 > * if (err.code === AhpErrorCodes.PermissionDenied) {
188 > * err.data.request; // typed as ResourceRequestParams | undefined
189 > * }
190 > * }
191 > * ```
192 > *
193 > * @category Error Details
194 > * @version 1
195 > */
196 > export type AhpError<C extends AhpErrorCode = AhpErrorCode> =
197 > C extends AhpErrorCode
198 > ? C extends keyof AhpErrorDetailsMap
199 > ? {
200 > /** The error code. */
201 > readonly code: C;
202 > /** Human-readable error message. */
203 > readonly message: string;
204 > /** Structured detail payload mandated by `AhpErrorDetailsMap`. */
205 > readonly data: AhpErrorDetailsMap[C];
206 > }
207 > : {
208 > /** The error code. */
209 > readonly code: C;
210 > /** Human-readable error message. */
211 > readonly message: string;
212 > /** Optional, unspecified detail payload. */
213 > readonly data?: unknown;
214 > }
215 > : never;
src/vs/platform/configuration/common/configuration.ts 203 covered LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- configuration.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { assertNever } from '../../../base/common/assert.js';
7 > import { IStringDictionary } from '../../../base/common/collections.js';
8 > import { Event } from '../../../base/common/event.js';
9 > import * as types from '../../../base/common/types.js';
10 > import { URI, UriComponents } from '../../../base/common/uri.js';
11 > import { createDecorator } from '../../instantiation/common/instantiation.js';
12 > import { IWorkspaceFolder } from '../../workspace/common/workspace.js';
13 >
14 > export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
15 >
16 > export function isConfigurationOverrides(obj: unknown): obj is IConfigurationOverrides {
17 const thing = obj as IConfigurationOverrides;
18 return thing
21 && (!thing.resource || thing.resource instanceof URI);
22 }
24 > export interface IConfigurationOverrides {
25 > overrideIdentifier?: string | null;
26 > resource?: URI | null;
27 > }
28 >
29 > export function isConfigurationUpdateOverrides(obj: unknown): obj is IConfigurationUpdateOverrides {
30 const thing = obj as IConfigurationUpdateOverrides | IConfigurationOverrides;
31 return thing
35 && (!thing.resource || thing.resource instanceof URI);
36 }
38 > export type IConfigurationUpdateOverrides = Omit<IConfigurationOverrides, 'overrideIdentifier'> & { overrideIdentifiers?: string[] | null };
39 >
40 > export const enum ConfigurationTarget {
41 > APPLICATION = 1,
42 > USER,
43 > USER_LOCAL,
44 > USER_REMOTE,
45 > WORKSPACE,
46 > WORKSPACE_FOLDER,
47 > DEFAULT,
48 > MEMORY
49 > }
50 > export function ConfigurationTargetToString(configurationTarget: ConfigurationTarget) {
51 switch (configurationTarget) {
52 case ConfigurationTarget.APPLICATION: return 'APPLICATION';
60 }
61 }
63 > export interface IConfigurationChange {
64 > keys: string[];
65 > overrides: [string, string[]][];
66 > }
67 >
68 > export interface IConfigurationChangeEvent {
69 >
70 > readonly source: ConfigurationTarget;
71 > readonly affectedKeys: ReadonlySet<string>;
72 > readonly change: IConfigurationChange;
73 >
74 > affectsConfiguration(configuration: string, overrides?: IConfigurationOverrides): boolean;
75 > }
76 >
77 > export interface IInspectValue<T> {
78 > readonly value?: T;
79 > readonly override?: T;
80 > readonly overrides?: { readonly identifiers: string[]; readonly value: T }[];
81 > }
82 >
83 > export interface IConfigurationValue<T> {
84 >
85 > readonly defaultValue?: T;
86 > readonly applicationValue?: T;
87 > readonly userValue?: T;
88 > readonly userLocalValue?: T;
89 > readonly userRemoteValue?: T;
90 > readonly workspaceValue?: T;
91 > readonly workspaceFolderValue?: T;
92 > readonly memoryValue?: T;
93 > readonly policyValue?: T;
94 > readonly value?: T;
95 >
96 > readonly default?: IInspectValue<T>;
97 > readonly application?: IInspectValue<T>;
98 > readonly user?: IInspectValue<T>;
99 > readonly userLocal?: IInspectValue<T>;
100 > readonly userRemote?: IInspectValue<T>;
101 > readonly workspace?: IInspectValue<T>;
102 > readonly workspaceFolder?: IInspectValue<T>;
103 > readonly memory?: IInspectValue<T>;
104 > readonly policy?: { value?: T };
105 >
106 > readonly overrideIdentifiers?: string[];
107 > }
108 >
109 > export function getConfigValueInTarget<T>(configValue: IConfigurationValue<T>, scope: ConfigurationTarget): T | undefined {
110 switch (scope) {
111 case ConfigurationTarget.APPLICATION:
129 }
130 }
132 > export function isConfigured<T>(configValue: IConfigurationValue<T>): configValue is IConfigurationValue<T> & { value: T } {
133 return configValue.applicationValue !== undefined ||
134 configValue.userValue !== undefined ||
138 configValue.workspaceFolderValue !== undefined;
139 }
141 > export interface IConfigurationUpdateOptions {
142 > /**
143 > * If `true`, do not notifies the error to user by showing the message box. Default is `false`.
144 > */
145 > donotNotifyError?: boolean;
146 > /**
147 > * How to handle dirty file when updating the configuration.
148 > */
149 > handleDirtyFile?: 'save' | 'revert';
150 > }
151 >
152 > export interface IConfigurationService {
153 > readonly _serviceBrand: undefined;
154 >
155 > readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent>;
156 >
157 > getConfigurationData(): IConfigurationData | null;
158 >
159 > /**
160 > * Fetches the value of the section for the given overrides.
161 > * Value can be of native type or an object keyed off the section name.
162 > *
163 > * @param section - Section of the configuration. Can be `null` or `undefined`.
164 > * @param overrides - Overrides that has to be applied while fetching
165 > *
166 > */
167 > getValue<T>(): T;
168 > getValue<T>(section: string): T;
169 > getValue<T>(overrides: IConfigurationOverrides): T;
170 > getValue<T>(section: string, overrides: IConfigurationOverrides): T;
171 >
172 > /**
173 > * Update a configuration value.
174 > *
175 > * Use `target` to update the configuration in a specific `ConfigurationTarget`.
176 > *
177 > * Use `overrides` to update the configuration for a resource or for override identifiers or both.
178 > *
179 > * Passing a resource through overrides will update the configuration in the workspace folder containing that resource.
180 > *
181 > * *Note 1:* Updating configuration to a default value will remove the configuration from the requested target. If not target is passed, it will be removed from all writeable targets.
182 > *
183 > * *Note 2:* Use `undefined` value to remove the configuration from the given target. If not target is passed, it will be removed from all writeable targets.
184 > *
185 > * Use `donotNotifyError` and set it to `true` to surpresss errors.
186 > *
187 > * @param key setting to be updated
188 > * @param value The new value
189 > */
190 > updateValue(key: string, value: unknown): Promise<void>;
191 > updateValue(key: string, value: unknown, target: ConfigurationTarget): Promise<void>;
192 > updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise<void>;
193 > updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise<void>;
194 >
195 > inspect<T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<Readonly<T>>;
196 >
197 > reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise<void>;
198 >
199 > keys(): {
200 > default: string[];
201 > policy: string[];
202 > user: string[];
203 > workspace: string[];
204 > workspaceFolder: string[];
205 > memory?: string[];
206 > };
207 > }
208 >
209 > export interface IConfigurationModel {
210 > contents: IStringDictionary<unknown>;
211 > keys: string[];
212 > overrides: IOverrides[];
213 > raw?: ReadonlyArray<IStringDictionary<unknown>> | IStringDictionary<unknown>;
214 > }
215 >
216 > export interface IOverrides {
217 > keys: string[];
218 > contents: IStringDictionary<unknown>;
219 > identifiers: string[];
220 > }
221 >
222 > export interface IConfigurationData {
223 > defaults: IConfigurationModel;
224 > policy: IConfigurationModel;
225 > application: IConfigurationModel;
226 > userLocal: IConfigurationModel;
227 > userRemote: IConfigurationModel;
228 > workspace: IConfigurationModel;
229 > folders: [UriComponents, IConfigurationModel][];
230 > }
231 >
232 > export interface IConfigurationCompareResult {
233 > added: string[];
234 > removed: string[];
235 > updated: string[];
236 > overrides: [string, string[]][];
237 > }
238 >
239 > export function toValuesTree(properties: IStringDictionary<unknown>, conflictReporter: (message: string) => void): IStringDictionary<unknown> {
240 const root = Object.create(null);
241
246 return root;
247 }
249 > export function addToValueTree(settingsTreeRoot: IStringDictionary<unknown>, key: string, value: unknown, conflictReporter: (message: string) => void): void {
250 const segments = key.split('.');
251 const last = segments.pop()!;
282 }
283 }
285 > export function removeFromValueTree(valueTree: IStringDictionary<unknown>, key: string): void {
286 const segments = key.split('.');
287 doRemoveFromValueTree(valueTree, segments);
288 }
290 function doRemoveFromValueTree(valueTree: IStringDictionary<unknown> | unknown, segments: string[]): void {
291 if (!valueTree) {
311 }
312 }
314 > /**
315 > * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting)
316 > */
317 > export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string): T | undefined;
318 > export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string, defaultValue: T): T;
319 > export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string, defaultValue?: T): T | undefined {
320 function accessSetting(config: IStringDictionary<unknown>, path: string[]): unknown {
321 let current: unknown = config;
334 return typeof result === 'undefined' ? defaultValue : result as T;
335 }
337 > export function merge(base: IStringDictionary<unknown>, add: IStringDictionary<unknown>, overwrite: boolean): void {
338 Object.keys(add).forEach(key => {
339 if (key !== '__proto__') {
350 });
351 }
353 > export function getLanguageTagSettingPlainKey(settingKey: string) {
354 return settingKey
355 .replace(/^\[/, '')
src/vs/platform/contextkey/common/scanner.ts 203 covered LOC · 58 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- scanner.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from '../../../base/common/charCode.js';
7 > import { illegalState } from '../../../base/common/errors.js';
8 > import { localize } from '../../../nls.js';
9 >
10 > export const enum TokenType {
11 > LParen,
12 > RParen,
13 > Neg,
14 > Eq,
15 > NotEq,
16 > Lt,
17 > LtEq,
18 > Gt,
19 > GtEq,
20 > RegexOp,
21 > RegexStr,
22 > True,
23 > False,
24 > In,
25 > Not,
26 > And,
27 > Or,
28 > Str,
29 > QuotedStr,
30 > Error,
31 > EOF,
32 > }
33 >
34 > export type Token =
35 > | { type: TokenType.LParen; offset: number }
36 > | { type: TokenType.RParen; offset: number }
37 > | { type: TokenType.Neg; offset: number }
38 > | { type: TokenType.Eq; offset: number; isTripleEq: boolean }
39 > | { type: TokenType.NotEq; offset: number; isTripleEq: boolean }
40 > | { type: TokenType.Lt; offset: number }
41 > | { type: TokenType.LtEq; offset: number }
42 > | { type: TokenType.Gt; offset: number }
43 > | { type: TokenType.GtEq; offset: number }
44 > | { type: TokenType.RegexOp; offset: number }
45 > | { type: TokenType.RegexStr; offset: number; lexeme: string }
46 > | { type: TokenType.True; offset: number }
47 > | { type: TokenType.False; offset: number }
48 > | { type: TokenType.In; offset: number }
49 > | { type: TokenType.Not; offset: number }
50 > | { type: TokenType.And; offset: number }
51 > | { type: TokenType.Or; offset: number }
52 > | { type: TokenType.Str; offset: number; lexeme: string }
53 > | { type: TokenType.QuotedStr; offset: number; lexeme: string }
54 > | { type: TokenType.Error; offset: number; lexeme: string }
55 > | { type: TokenType.EOF; offset: number };
56 >
57 > type KeywordTokenType = TokenType.Not | TokenType.In | TokenType.False | TokenType.True;
58 > type TokenTypeWithoutLexeme =
59 > TokenType.LParen |
60 > TokenType.RParen |
61 > TokenType.Neg |
62 > TokenType.Lt |
63 > TokenType.LtEq |
64 > TokenType.Gt |
65 > TokenType.GtEq |
66 > TokenType.RegexOp |
67 > TokenType.True |
68 > TokenType.False |
69 > TokenType.In |
70 > TokenType.Not |
71 > TokenType.And |
72 > TokenType.Or |
73 > TokenType.EOF;
74 >
75 > /**
76 > * Example:
77 > * `foo == bar'` - note how single quote doesn't have a corresponding closing quote,
78 > * so it's reported as unexpected
79 > */
80 > export type LexingError = {
81 > offset: number; /** note that this doesn't take into account escape characters from the original encoding of the string, e.g., within an extension manifest file's JSON encoding */
82 > lexeme: string;
83 > additionalInfo?: string;
84 > };
85 >
86 function hintDidYouMean(...meant: string[]) {
87 switch (meant.length) {
96 }
97 }
98 > scanner.ts
99 > const hintDidYouForgetToOpenOrCloseQuote = localize('contextkey.scanner.hint.didYouForgetToOpenOrCloseQuote', "Did you forget to open or close the quote?");
100 > const hintDidYouForgetToEscapeSlash = localize('contextkey.scanner.hint.didYouForgetToEscapeSlash', "Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/\'.");
101 >
102 > /**
103 > * A simple scanner for context keys.
104 > *
105 > * Example:
106 > *
107 > * ```ts
108 > * const scanner = new Scanner().reset('resourceFileName =~ /docker/ && !config.docker.enabled');
109 > * const tokens = [...scanner];
110 > * if (scanner.errorTokens.length > 0) {
111 > * scanner.errorTokens.forEach(err => console.error(`Unexpected token at ${err.offset}: ${err.lexeme}\nHint: ${err.additional}`));
112 > * } else {
113 > * // process tokens
114 > * }
115 > * ```
116 > */
117 > export class Scanner {
118 >
119 > static getLexeme(token: Token): string {
120 > switch (token.type) {
121 > case TokenType.LParen:
122 > return '('; scanner.ts
123 > case TokenType.RParen: scanner.ts
124 > return ')'; scanner.ts
125 > case TokenType.Neg: scanner.ts
126 > return '!'; scanner.ts
127 > case TokenType.Eq: scanner.ts
128 > return token.isTripleEq ? '===' : '=='; scanner.ts
129 > case TokenType.NotEq: scanner.ts
130 > return token.isTripleEq ? '!==' : '!='; scanner.ts
131 > case TokenType.Lt: scanner.ts
132 > return '<'; scanner.ts
133 > case TokenType.LtEq: scanner.ts
134 > return '<='; scanner.ts
135 > case TokenType.Gt: scanner.ts
136 > return '>'; scanner.ts
137 > case TokenType.GtEq: scanner.ts
138 > return '>='; scanner.ts
139 > case TokenType.RegexOp: scanner.ts
140 > return '=~'; scanner.ts
141 > case TokenType.RegexStr: scanner.ts
142 > return token.lexeme; scanner.ts
143 > case TokenType.True: scanner.ts
144 > return 'true'; scanner.ts
145 > case TokenType.False: scanner.ts
146 > return 'false'; scanner.ts
147 > case TokenType.In: scanner.ts
148 > return 'in'; scanner.ts
149 > case TokenType.Not: scanner.ts
150 > return 'not'; scanner.ts
151 > case TokenType.And: scanner.ts
152 > return '&&'; scanner.ts
153 > case TokenType.Or: scanner.ts
154 > return '||'; scanner.ts
155 > case TokenType.Str: scanner.ts
156 > return token.lexeme; scanner.ts
157 > case TokenType.QuotedStr: scanner.ts
158 > return token.lexeme; scanner.ts
159 > case TokenType.Error: scanner.ts
160 > return token.lexeme; scanner.ts
161 > case TokenType.EOF: scanner.ts
162 > return 'EOF'; scanner.ts
163 > default: scanner.ts
164 > throw illegalState(`unhandled token type: ${JSON.stringify(token)}; have you forgotten to add a case?`); scanner.ts
165 > } scanner.ts
166 > }
167 >
168 > private static _regexFlags = new Set(['i', 'g', 's', 'm', 'y', 'u'].map(ch => ch.charCodeAt(0)));
169 >
170 > private static _keywords = new Map<string, KeywordTokenType>([
171 > ['not', TokenType.Not],
172 > ['in', TokenType.In],
173 > ['false', TokenType.False],
174 > ['true', TokenType.True],
175 > ]);
176 >
177 > private _input: string = '';
178 > private _start: number = 0;
179 > private _current: number = 0;
180 > private _tokens: Token[] = [];
181 > private _errors: LexingError[] = [];
182 >
183 > get errors(): Readonly<LexingError[]> {
184 return this._errors;
185 }
186 > scanner.ts
187 > reset(value: string) {
188 this._input = value;
189
195 return this;
196 }
197 > scanner.ts
198 > scan() {
199 while (!this._isAtEnd()) {
200
267 return Array.from(this._tokens);
268 }
269 > scanner.ts
270 > private _match(expected: number): boolean {
271 if (this._isAtEnd()) {
272 return false;
278 return true;
279 }
280 > scanner.ts
281 > private _advance(): number {
282 return this._input.charCodeAt(this._current++);
283 }
284 > scanner.ts
285 > private _peek(): number {
286 return this._isAtEnd() ? CharCode.Null : this._input.charCodeAt(this._current);
287 }
288 > scanner.ts
289 > private _addToken(type: TokenTypeWithoutLexeme) {
290 this._tokens.push({ type, offset: this._start });
291 }
292 > scanner.ts
293 > private _error(additional?: string) {
294 const offset = this._start;
295 const lexeme = this._input.substring(this._start, this._current);
298 this._tokens.push(errToken);
299 }
300 > scanner.ts
301 > // u - unicode, y - sticky // TODO@ulugbekna: we accept double quotes as part of the string rather than as a delimiter (to preserve old parser's behavior)
302 > private stringRe = /[a-zA-Z0-9_<>\-\./\\:\*\?\+\[\]\^,#@;"%\$\p{L}-]+/uy;
303 > private _string() {
304 this.stringRe.lastIndex = this._start;
305 const match = this.stringRe.exec(this._input);
315 }
316 }
317 > scanner.ts
318 > // captures the lexeme without the leading and trailing '
319 > private _quotedString() {
320 while (this._peek() !== CharCode.SingleQuote && !this._isAtEnd()) { // TODO@ulugbekna: add support for escaping ' ?
321 this._advance();
332 this._tokens.push({ type: TokenType.QuotedStr, lexeme: this._input.substring(this._start + 1, this._current - 1), offset: this._start + 1 });
333 }
334 > scanner.ts
335 > /*
336 > * Lexing a regex expression: /.../[igsmyu]*
337 > * Based on https://github.com/microsoft/TypeScript/blob/9247ef115e617805983740ba795d7a8164babf89/src/compiler/scanner.ts#L2129-L2181
338 > *
339 > * Note that we want slashes within a regex to be escaped, e.g., /file:\\/\\/\\// should match `file:///`
340 > */
341 > private _regex() {
342 let p = this._current;
343
378 this._tokens.push({ type: TokenType.RegexStr, lexeme, offset: this._start });
379 }
380 > scanner.ts
381 > private _isAtEnd() {
382 return this._current >= this._input.length;
383 }
384 > } scanner.ts
src/vs/nls.ts 201 covered LOC · 13 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- nls.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export function getNLSMessages(): string[] {
7 return globalThis._VSCODE_NLS_MESSAGES;
8 }
9 > nls.ts
10 > export function getNLSLanguage(): string | undefined {
11 > return globalThis._VSCODE_NLS_LANGUAGE;
12 > }
13 >
14 > declare const document: { location?: { hash?: string } } | undefined;
15 > const isPseudo = getNLSLanguage() === 'pseudo' || (typeof document !== 'undefined' && document.location && typeof document.location.hash === 'string' && document.location.hash.indexOf('pseudo=true') >= 0);
16 >
17 > export interface ILocalizeInfo {
18 > key: string;
19 > comment: string[];
20 > }
21 >
22 > export interface ILocalizedString {
23 > original: string;
24 > value: string;
25 > }
26 >
27 > function _format(message: string, args: (string | number | boolean | undefined | null)[]): string { nls.ts
28 > let result: string;
29 >
30 > if (args.length === 0) {
31 > result = message; nls.ts
32 > } else { nls.ts
33 result = message.replace(/\{(\d+)\}/g, (match, rest) => {
34 const index = rest[0];
43 });
44 }
45 > nls.ts
46 > if (isPseudo) {
47 // FF3B and FF3D is the Unicode zenkaku representation for [ and ]
48 result = '\uFF3B' + result.replace(/[aouei]/g, '$&$&') + '\uFF3D';
49 }
50 > nls.ts
51 > return result;
52 > }
53 > nls.ts
54 > /**
55 > * Marks a string to be localized. Returns the localized string.
56 > *
57 > * @param info The {@linkcode ILocalizeInfo} which describes the id and comments associated with the localized string.
58 > * @param message The string to localize
59 > * @param args The arguments to the string
60 > *
61 > * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
62 > * @example `localize({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)`
63 > *
64 > * @returns string The localized string.
65 > */
66 > export function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
67 >
68 > /**
69 > * Marks a string to be localized. Returns the localized string.
70 > *
71 > * @param key The key to use for localizing the string
72 > * @param message The string to localize
73 > * @param args The arguments to the string
74 > *
75 > * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
76 > * @example For example, `localize('sayHello', 'hello {0}', name)`
77 > *
78 > * @returns string The localized string.
79 > */
80 > export function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
81 >
82 > /**
83 > * @skipMangle
84 > */
85 > export function localize(data: ILocalizeInfo | string /* | number when built */, message: string /* | null when built */, ...args: (string | number | boolean | undefined | null)[]): string {
86 > if (typeof data === 'number') { nls.ts
87 return _format(lookupMessage(data, message), args);
88 }
89 > return _format(message, args); nls.ts
90 > }
91 > nls.ts
92 > /**
93 > * Only used when built: Looks up the message in the global NLS table.
94 > * This table is being made available as a global through bootstrapping
95 > * depending on the target context.
96 > */
97 function lookupMessage(index: number, fallback: string | null): string {
98 const message = getNLSMessages()?.[index];
105 return message;
106 }
107 > nls.ts
108 > /**
109 > * Marks a string to be localized. Returns an {@linkcode ILocalizedString}
110 > * which contains the localized string and the original string.
111 > *
112 > * @param info The {@linkcode ILocalizeInfo} which describes the id and comments associated with the localized string.
113 > * @param message The string to localize
114 > * @param args The arguments to the string
115 > *
116 > * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
117 > * @example `localize2({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)`
118 > *
119 > * @returns ILocalizedString which contains the localized string and the original string.
120 > */
121 > export function localize2(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString;
122 >
123 > /**
124 > * Marks a string to be localized. Returns an {@linkcode ILocalizedString}
125 > * which contains the localized string and the original string.
126 > *
127 > * @param key The key to use for localizing the string
128 > * @param message The string to localize
129 > * @param args The arguments to the string
130 > *
131 > * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
132 > * @example `localize('sayHello', 'hello {0}', name)`
133 > *
134 > * @returns ILocalizedString which contains the localized string and the original string.
135 > */
136 > export function localize2(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString;
137 >
138 > /**
139 > * @skipMangle
140 > */
141 > export function localize2(data: ILocalizeInfo | string /* | number when built */, originalMessage: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString {
142 let message: string;
143 if (typeof data === 'number') {
154 };
155 }
156 > nls.ts
157 > export interface INLSLanguagePackConfiguration {
158 >
159 > /**
160 > * The path to the translations config file that contains pointers to
161 > * all message bundles for `main` and extensions.
162 > */
163 > readonly translationsConfigFile: string;
164 >
165 > /**
166 > * The path to the file containing the translations for this language
167 > * pack as flat string array.
168 > */
169 > readonly messagesFile: string;
170 >
171 > /**
172 > * The path to the file that can be used to signal a corrupt language
173 > * pack, for example when reading the `messagesFile` fails. This will
174 > * instruct the application to re-create the cache on next startup.
175 > */
176 > readonly corruptMarkerFile: string;
177 > }
178 >
179 > export interface INLSConfiguration {
180 >
181 > /**
182 > * Locale as defined in `argv.json` or `app.getLocale()`.
183 > */
184 > readonly userLocale: string;
185 >
186 > /**
187 > * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`).
188 > */
189 > readonly osLocale: string;
190 >
191 > /**
192 > * The actual language of the UI that ends up being used considering `userLocale`
193 > * and `osLocale`.
194 > */
195 > readonly resolvedLanguage: string;
196 >
197 > /**
198 > * Defined if a language pack is used that is not the
199 > * default english language pack. This requires a language
200 > * pack to be installed as extension.
201 > */
202 > readonly languagePack?: INLSLanguagePackConfiguration;
203 >
204 > /**
205 > * The path to the file containing the default english messages
206 > * as flat string array. The file is only present in built
207 > * versions of the application.
208 > */
209 > readonly defaultMessagesFile: string;
210 >
211 > /**
212 > * Below properties are deprecated and only there to continue support
213 > * for `vscode-nls` module that depends on them.
214 > * Refs https://github.com/microsoft/vscode-nls/blob/main/src/node/main.ts#L36-L46
215 > */
216 > /** @deprecated */
217 > readonly locale: string;
218 > /** @deprecated */
219 > readonly availableLanguages: Record<string, string>;
220 > /** @deprecated */
221 > readonly _languagePackSupport?: boolean;
222 > /** @deprecated */
223 > readonly _languagePackId?: string;
224 > /** @deprecated */
225 > readonly _translationsConfigFile?: string;
226 > /** @deprecated */
227 > readonly _cacheRoot?: string;
228 > /** @deprecated */
229 > readonly _resolvedLanguagePackCoreLocation?: string;
230 > /** @deprecated */
231 > readonly _corruptedFile?: string;
232 > }
233 >
234 > export interface ILanguagePack {
235 > readonly hash: string;
236 > readonly label: string | undefined;
237 > readonly extensions: {
238 > readonly extensionIdentifier: { readonly id: string; readonly uuid?: string };
239 > readonly version: string;
240 > }[];
241 > readonly translations: Record<string, string | undefined>;
242 > }
243 >
244 > export type ILanguagePacks = Record<string, ILanguagePack | undefined>;
src/vs/platform/agentHost/common/state/protocol/channels-chat/reducer.ts 191 covered LOC · 57 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import { TurnState, ToolCallStatus, ToolCallConfirmationReason, ToolCallCancellationReason, ToolCallContributorKind, ResponsePartKind, PendingMessageKind, type ChatState, type ToolCallState, type ResponsePart, type ToolCallResponsePart, type InputRequestResponsePart, type Turn, type PendingMessage, type ConfirmationOption } from './state.js';
11 > import { SessionStatus } from '../channels-session/state.js';
12 > import type { ChatAction } from '../action-origin.generated.js';
13 > import { softAssertNever } from '../common/reducer-helpers.js';
14 >
15 > // ─── Helpers ─────────────────────────────────────────────────────────────────
16 >
17 > /** Extracts the common base fields shared by all tool call lifecycle states. */
18 function tcBase(tc: ToolCallState) {
19 return {
26 };
27 }
28 > reducer.ts
29 function tcBaseWithMeta(tc: ToolCallState, meta: Record<string, unknown> | undefined) {
30 return {
33 };
34 }
35 > reducer.ts
36 > /** Resolves a selected option from the confirmation options array by ID. */
37 function resolveSelectedOption(options: ConfirmationOption[] | undefined, id: string | undefined): ConfirmationOption | undefined {
38 if (!id || !options) {
41 return options.find(o => o.id === id);
42 }
43 > reducer.ts
44 > /**
45 > * Returns `true` if the active turn has any tool call blocking on something
46 > * external to the turn itself — a pending confirmation/result-confirmation,
47 > * or a tool call paused on MCP authentication.
48 > */
49 > function hasBlockingToolCall(state: ChatState): boolean { reducer.ts
50 > if (!state.activeTurn) {
51 return false;
52 }
53 > return state.activeTurn.responseParts.some(part => reducer.ts
54 part.kind === ResponsePartKind.ToolCall
55 && (part.toolCall.status === ToolCallStatus.PendingConfirmation
56 || part.toolCall.status === ToolCallStatus.PendingResultConfirmation
57 || part.toolCall.status === ToolCallStatus.AuthRequired),
58 > ); reducer.ts
59 > }
60 > reducer.ts
61 > /** Returns whether the active turn contains an input request awaiting submission. */
62 > function hasOpenInputRequest(state: ChatState): boolean { reducer.ts
63 > return state.activeTurn?.responseParts.some(part =>
64 part.kind === ResponsePartKind.InputRequest && part.response === undefined,
65 > ) ?? false; reducer.ts
66 > }
67 > reducer.ts
68 function findOpenInputRequestPart(
69 responseParts: readonly ResponsePart[],
81 return part.kind === ResponsePartKind.InputRequest ? { index, part } : undefined;
82 }
83 > reducer.ts
84 > /** Bitmask covering the mutually-exclusive activity bits (bits 0–4). */
85 > const STATUS_ACTIVITY_MASK = (1 << 5) - 1;
86 >
87 > /** Sets or clears a metadata flag on a status value. */
88 > function withStatusFlag(status: SessionStatus, flag: SessionStatus, set: boolean): SessionStatus { reducer.ts
89 > return set ? status | flag : status & ~flag;
90 > }
91 > reducer.ts
92 > /** Derives the summary status from live session work, preserving orthogonal flags. */
93 > function summaryStatus(state: ChatState, terminalStatus?: SessionStatus.Error): SessionStatus { reducer.ts
94 > let activity: SessionStatus;
95 > if (terminalStatus) {
96 activity = terminalStatus;
97 > } else if (hasOpenInputRequest(state) || hasBlockingToolCall(state)) { reducer.ts
98 activity = SessionStatus.InputNeeded;
99 > } else if (state.activeTurn) { reducer.ts
100 > activity = SessionStatus.InProgress;
101 > } else {
102 activity = SessionStatus.Idle;
103 }
104 > reducer.ts
105 > return state.status & ~STATUS_ACTIVITY_MASK | activity;
106 > }
107 > reducer.ts
108 > /**
109 > * Returns a state with `status` recomputed. Use this after reducers
110 > * that change data which feeds into {@link summaryStatus} (e.g. tool call
111 > * lifecycle transitions that may enter or leave a pending-confirmation state).
112 > */
113 function refreshSummaryStatus(state: ChatState): ChatState {
114 const status = summaryStatus(state);
118 return { ...state, status };
119 }
120 > reducer.ts
121 > /**
122 > * Ends the active turn, finalizing it into a completed turn record.
123 > *
124 > * Tool call parts with non-terminal states are forced to cancelled.
125 > * Pending permissions are stripped from tool call parts.
126 > */
127 function endTurn(
128 state: ChatState,
183 };
184 }
185 > reducer.ts
186 function upsertInputRequestPart(state: ChatState, request: InputRequestResponsePart['request']): ChatState {
187 const activeTurn = state.activeTurn;
213 return { ...next, status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false), modifiedAt: new Date(Date.now()).toISOString() };
214 }
215 > reducer.ts
216 > /**
217 > * Immutably updates the tool call inside a `ToolCall` response part in the
218 > * active turn's `responseParts` array. Returns `state` unchanged if the
219 > * active turn or tool call doesn't match.
220 > */
221 function updateToolCallInParts(
222 state: ChatState,
252 };
253 }
254 > reducer.ts
255 > /**
256 > * Immutably updates a response part by `partId` in the active turn.
257 > * For markdown/reasoning parts, matches on `id`. For tool call parts,
258 > * matches on `toolCall.toolCallId`.
259 > */
260 function updateResponsePart(
261 state: ChatState,
292 };
293 }
294 > reducer.ts
295 >
296 > // ─── Chat Reducer ────────────────────────────────────────────────────────────
297 >
298 > /**
299 > * Pure reducer for chat state. Handles all {@link ChatAction} variants.
300 > */
301 > export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: string) => void): ChatState {
302 > switch (action.type) { reducer.ts
303 > // ── Turn Lifecycle ────────────────────────────────────────────────────
304 >
305 > case ActionType.ChatTurnStarted: {
306 > let next: ChatState = { reducer.ts
307 > ...state,
308 > activeTurn: {
309 > id: action.turnId,
310 > startedAt: action.startedAt,
311 > message: action.message,
312 > responseParts: [],
313 > usage: undefined,
314 > },
315 > };
316 > next = {
317 > ...next,
318 > status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false),
319 > modifiedAt: new Date(Date.now()).toISOString(),
320 > };
321 >
322 > // If this turn was auto-started from a pending message, remove it
323 > if (action.queuedMessageId) {
324 if (next.steeringMessage?.id === action.queuedMessageId) {
325 next = { ...next, steeringMessage: undefined };
330 }
331 }
332 > reducer.ts
333 > return next;
334 > }
335 > reducer.ts
336 > case ActionType.ChatDelta:
337 return updateResponsePart(state, action.turnId, action.partId, part => {
338 if (part.kind === ResponsePartKind.Markdown) {
341 return part;
342 });
343 > reducer.ts
344 > case ActionType.ChatResponsePart:
345 if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
346 return state;
353 },
354 };
355 > reducer.ts
356 > case ActionType.ChatTurnComplete:
357 return endTurn(state, action.turnId, TurnState.Complete, action.duration);
358 > reducer.ts
359 > case ActionType.ChatTurnCancelled:
360 return endTurn(state, action.turnId, TurnState.Cancelled, action.duration);
361 > reducer.ts
362 > case ActionType.ChatError:
363 return endTurn(state, action.turnId, TurnState.Error, action.duration, SessionStatus.Error, action.error);
364 > reducer.ts
365 > case ActionType.ChatActivityChanged:
366 return { ...state, activity: action.activity };
367 > reducer.ts
368 > // ── Working Directories ───────────────────────────────────────────────
369 >
370 > case ActionType.ChatWorkingDirectorySet: {
371 const list = state.workingDirectories ?? [];
372 if (list.includes(action.directory)) {
375 return { ...state, workingDirectories: [...list, action.directory] };
376 }
377 > reducer.ts
378 > case ActionType.ChatWorkingDirectoryRemoved: {
379 const list = state.workingDirectories;
380 if (!list) {
389 return { ...state, workingDirectories: updated };
390 }
391 > reducer.ts
392 > // ── Tool Call State Machine ───────────────────────────────────────────
393 >
394 > case ActionType.ChatToolCallStart:
395 if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
396 return state;
417 },
418 };
419 > reducer.ts
420 > case ActionType.ChatToolCallDelta:
421 return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
422 if (tc.status !== ToolCallStatus.Streaming) {
430 };
431 });
432 > reducer.ts
433 > case ActionType.ChatToolCallReady:
434 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
435 if (
464 };
465 }));
466 > reducer.ts
467 > case ActionType.ChatToolCallConfirmed:
468 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
469 if (tc.status !== ToolCallStatus.PendingConfirmation) {
493 };
494 }));
495 > reducer.ts
496 > case ActionType.ChatToolCallComplete:
497 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
498 if (tc.status !== ToolCallStatus.Running && tc.status !== ToolCallStatus.PendingConfirmation && tc.status !== ToolCallStatus.AuthRequired) {
550 };
551 }));
552 > reducer.ts
553 > case ActionType.ChatToolCallResultConfirmed:
554 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
555 if (tc.status !== ToolCallStatus.PendingResultConfirmation) {
581 };
582 }));
583 > reducer.ts
584 > case ActionType.ChatToolCallContentChanged:
585 return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
586 if (tc.status !== ToolCallStatus.Running) {
593 };
594 });
595 > reducer.ts
596 > case ActionType.ChatToolCallAuthRequired:
597 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
598 if (tc.status !== ToolCallStatus.Running) {
616 };
617 }));
618 > reducer.ts
619 > case ActionType.ChatToolCallAuthResolved:
620 return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => {
621 if (tc.status !== ToolCallStatus.AuthRequired) {
633 };
634 }));
635 > reducer.ts
636 >
637 > case ActionType.ChatUsage:
638 if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
639 return state;
643 activeTurn: { ...state.activeTurn, usage: action.usage },
644 };
645 > reducer.ts
646 > case ActionType.ChatReasoning:
647 return updateResponsePart(state, action.turnId, action.partId, part => {
648 if (part.kind === ResponsePartKind.Reasoning) {
651 return part;
652 });
653 > reducer.ts
654 >
655 > // ── Truncation ────────────────────────────────────────────────────────
656 >
657 > case ActionType.ChatTruncated: {
658 let turns: typeof state.turns;
659 if (action.turnId === undefined) {
680 };
681 }
682 > reducer.ts
683 > case ActionType.ChatTurnsLoaded: {
684 const existingIds = new Set(state.turns.map(turn => turn.id));
685 const olderTurns = action.turns.filter(turn => !existingIds.has(turn.id));
690 };
691 }
692 > reducer.ts
693 > // ── Session Input Requests ─────────────────────────────────────────────
694 >
695 > case ActionType.ChatInputRequested:
696 return upsertInputRequestPart(state, action.request);
697 > reducer.ts
698 > case ActionType.ChatInputAnswerChanged: {
699 const activeTurn = state.activeTurn;
700 const existing = activeTurn
729 };
730 }
731 > reducer.ts
732 > case ActionType.ChatInputCompleted: {
733 const activeTurn = state.activeTurn;
734 const existing = activeTurn
762 };
763 }
764 > reducer.ts
765 > // ── Pending Messages ──────────────────────────────────────────────────
766 >
767 > case ActionType.ChatPendingMessageSet: {
768 const entry: PendingMessage = { id: action.id, message: action.message };
769 if (action.kind === PendingMessageKind.Steering) {
779 return { ...state, queuedMessages: [...existing, entry] };
780 }
781 > reducer.ts
782 > case ActionType.ChatPendingMessageRemoved: {
783 if (action.kind === PendingMessageKind.Steering) {
784 if (!state.steeringMessage || state.steeringMessage.id !== action.id) {
796 : { ...state, queuedMessages: filtered.length > 0 ? filtered : undefined };
797 }
798 > reducer.ts
799 > case ActionType.ChatQueuedMessagesReordered: {
800 const existing = state.queuedMessages;
801 if (!existing) {
821 return { ...state, queuedMessages: reordered };
822 }
823 > reducer.ts
824 > // ── Draft ─────────────────────────────────────────────────────────────
825 >
826 > case ActionType.ChatDraftChanged:
827 return { ...state, draft: action.draft };
828 > reducer.ts
829 > default:
830 softAssertNever(action, log);
831 return state;
832 > } reducer.ts
833 > }
src/vs/base/common/platform.ts 189 covered LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- platform.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as nls from '../../nls.js';
7 >
8 > export const LANGUAGE_DEFAULT = 'en';
9 >
10 > let _isWindows = false;
11 > let _isMacintosh = false;
12 > let _isLinux = false;
13 > let _isLinuxSnap = false;
14 > let _isNative = false;
15 > let _isWeb = false;
16 > let _isElectron = false;
17 > let _isIOS = false;
18 > let _isCI = false;
19 > let _isMobile = false;
20 > let _locale: string | undefined = undefined;
21 > let _language: string = LANGUAGE_DEFAULT;
22 > let _platformLocale: string = LANGUAGE_DEFAULT;
23 > let _translationsConfigFile: string | undefined = undefined;
24 > let _userAgent: string | undefined = undefined;
25 >
26 > export interface IProcessEnvironment {
27 > [key: string]: string | undefined;
28 > }
29 >
30 > /**
31 > * This interface is intentionally not identical to node.js
32 > * process because it also works in sandboxed environments
33 > * where the process object is implemented differently. We
34 > * define the properties here that we need for `platform`
35 > * to work and nothing else.
36 > */
37 > export interface INodeProcess {
38 > platform: string;
39 > arch: string;
40 > env: IProcessEnvironment;
41 > versions?: {
42 > node?: string;
43 > electron?: string;
44 > chrome?: string;
45 > };
46 > type?: string;
47 > cwd: () => string;
48 > }
49 >
50 > declare const process: INodeProcess;
51 >
52 > const $globalThis: any = globalThis;
53 >
54 > let nodeProcess: INodeProcess | undefined = undefined;
55 > if (typeof $globalThis.vscode !== 'undefined' && typeof $globalThis.vscode.process !== 'undefined') {
56 // Native environment (sandboxed)
57 nodeProcess = $globalThis.vscode.process;
58 > } else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') { platform.ts
59 > // Native environment (non-sandboxed)
60 > nodeProcess = process;
61 > }
62 >
63 > const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string';
64 > const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer';
65 >
66 > interface INavigator {
67 > userAgent: string;
68 > maxTouchPoints?: number;
69 > language: string;
70 > }
71 > declare const navigator: INavigator;
72 >
73 > // Native environment
74 > if (typeof nodeProcess === 'object') {
75 > _isWindows = (nodeProcess.platform === 'win32');
76 > _isMacintosh = (nodeProcess.platform === 'darwin');
77 > _isLinux = (nodeProcess.platform === 'linux');
78 > _isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
79 > _isElectron = isElectronProcess;
80 > _isCI = !!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] || !!nodeProcess.env['GITHUB_WORKSPACE'];
81 > _locale = LANGUAGE_DEFAULT;
82 > _language = LANGUAGE_DEFAULT;
83 > const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
84 > if (rawNlsConfig) {
85 try {
86 const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig);
113 console.error('Unable to resolve platform.');
114 }
115 > platform.ts
116 > export const enum Platform {
117 > Web,
118 > Mac,
119 > Linux,
120 > Windows
121 > }
122 > export type PlatformName = 'Web' | 'Windows' | 'Mac' | 'Linux';
123 >
124 > export function PlatformToString(platform: Platform): PlatformName {
125 switch (platform) {
126 case Platform.Web: return 'Web';
130 }
131 }
132 > platform.ts
133 > let _platform: Platform = Platform.Web;
134 > if (_isMacintosh) {
135 _platform = Platform.Mac;
136 > } else if (_isWindows) { platform.ts
137 _platform = Platform.Windows;
138 > } else if (_isLinux) { platform.ts
139 > _platform = Platform.Linux;
140 > }
141 >
142 > export const isWindows = _isWindows;
143 > export const isMacintosh = _isMacintosh;
144 > export const isLinux = _isLinux;
145 > export const isLinuxSnap = _isLinuxSnap;
146 > export const isNative = _isNative;
147 > export const isElectron = _isElectron;
148 > export const isWeb = _isWeb;
149 > export const isWebWorker = (_isWeb && typeof $globalThis.importScripts === 'function');
150 > export const webWorkerOrigin = isWebWorker ? $globalThis.origin : undefined;
151 > export const isIOS = _isIOS;
152 > export const isMobile = _isMobile;
153 > /**
154 > * Whether we run inside a CI environment, such as
155 > * GH actions or Azure Pipelines.
156 > */
157 > export const isCI = _isCI;
158 > export const platform = _platform;
159 > export const userAgent = _userAgent;
160 >
161 > /**
162 > * The language used for the user interface. The format of
163 > * the string is all lower case (e.g. zh-tw for Traditional
164 > * Chinese or de for German)
165 > */
166 > export const language = _language;
167 >
168 > export namespace Language {
169 >
170 > export function value(): string {
171 return language;
172 }
173 > platform.ts
174 > export function isDefaultVariant(): boolean {
175 if (language.length === 2) {
176 return language === 'en';
181 }
182 }
183 > platform.ts
184 > export function isDefault(): boolean {
185 return language === 'en';
186 }
187 > } platform.ts
188 >
189 > /**
190 > * Desktop: The OS locale or the locale specified by --locale or `argv.json`.
191 > * Web: matches `platformLocale`.
192 > *
193 > * The UI is not necessarily shown in the provided locale.
194 > */
195 > export const locale = _locale;
196 >
197 > /**
198 > * This will always be set to the OS/browser's locale regardless of
199 > * what was specified otherwise. The format of the string is all
200 > * lower case (e.g. zh-tw for Traditional Chinese). The UI is not
201 > * necessarily shown in the provided locale.
202 > */
203 > export const platformLocale = _platformLocale;
204 >
205 > /**
206 > * The translations that are available through language packs.
207 > */
208 > export const translationsConfigFile = _translationsConfigFile;
209 >
210 > export const setTimeout0IsFaster = (typeof $globalThis.postMessage === 'function' && !$globalThis.importScripts);
211 >
212 > /**
213 > * See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
214 > *
215 > * Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay
216 > * that browsers set when the nesting level is > 5.
217 > */
218 > export const setTimeout0 = (() => {
219 > if (setTimeout0IsFaster) {
220 interface IQueueElement {
221 id: number;
246 };
247 }
248 > return (callback: () => void) => setTimeout(callback); platform.ts
249 > })();
250 >
251 > export const enum OperatingSystem {
252 > Windows = 1,
253 > Macintosh = 2,
254 > Linux = 3
255 > }
256 > export const OS = (_isMacintosh || _isIOS ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
257 >
258 > let _isLittleEndian = true;
259 > let _isLittleEndianComputed = false;
260 > export function isLittleEndian(): boolean {
261 if (!_isLittleEndianComputed) {
262 _isLittleEndianComputed = true;
269 return _isLittleEndian;
270 }
271 > platform.ts
272 > export const isChrome = !!(userAgent && userAgent.indexOf('Chrome') >= 0);
273 > export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0);
274 > export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0));
275 > export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0);
276 > export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);
277 > export const hasElectronUserAgent = !!(userAgent && userAgent.indexOf('Electron') >= 0);
278 >
279 > export function isTahoeOrNewer(osVersion: string): boolean {
280 return parseFloat(osVersion) >= 25;
281 }
src/vs/platform/agentHost/common/changesetUri.ts 185 covered LOC · 16 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- changesetUri.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { localize } from '../../../nls.js';
7 > import { readSessionGitState, readSessionWorkspaceless, SessionLifecycle, type Changeset, type ISessionGitState, type ISessionWithDefaultChat, type URI } from './state/sessionState.js';
8 >
9 > /**
10 > * Helpers for building / parsing the URI clients subscribe to in order to
11 > * receive a {@link import('./state/protocol/state.js').ChangesetState}.
12 > *
13 > * Shapes recognised by this module:
14 > *
15 > * <sessionUri>/changeset/uncommitted
16 > * <sessionUri>/changeset/session
17 > * <sessionUri>/changeset/turn/<turnId>
18 > * <sessionUri>/changeset/compare/<originalTurnId>/<modifiedTurnId>
19 > *
20 > * Catalogue entries on `summary.changesets` may also advertise the
21 > * URI-template forms `<sessionUri>/changeset/turn/{turnId}` and
22 > * `<sessionUri>/changeset/compare/{originalTurnId}/{modifiedTurnId}`;
23 > * clients expand the template before subscribing.
24 > *
25 > * Keeping changeset URIs nested under the session URI namespace lets the
26 > * server cleanly tear down every changeset for a session when that session
27 > * is disposed (the reverse-lookup is just a string-prefix scan).
28 > */
29 >
30 > /** /** Stable id of the catalogue entry for the branch changeset. */
31 > const BRANCH_CHANGESET_ID = 'branch';
32 >
33 > /** Stable id of the catalogue entry for the uncommitted-changes changeset. */
34 > const UNCOMMITTED_CHANGESET_ID = 'uncommitted';
35 >
36 > /** Stable id of the catalogue entry for the session-wide changeset. */
37 > const SESSION_CHANGESET_ID = 'session';
38 >
39 > /** Path prefix used by per-turn changeset URIs (`turn/<turnId>`). */
40 > const TURN_CHANGESET_PREFIX = 'turn/';
41 >
42 > /** Template variable name used inside the per-turn URI template. */
43 > const TURN_TEMPLATE_VARIABLE = '{turnId}';
44 >
45 > /** Path prefix used by compare-turns changeset URIs (`compare/<originalTurnId>/<modifiedTurnId>`). */
46 > const COMPARE_CHANGESET_PREFIX = 'compare/';
47 >
48 > /** Template variable name for the original turn in the compare-turns URI template. */
49 > const COMPARE_ORIGINAL_TEMPLATE_VARIABLE = '{originalTurnId}';
50 >
51 > /** Template variable name for the modified turn in the compare-turns URI template. */
52 > const COMPARE_MODIFIED_TEMPLATE_VARIABLE = '{modifiedTurnId}';
53 >
54 > /** Localized human-readable label for the branch changeset entry. */
55 > export const branchChangesetLabel = (): string => localize('branchChangeset.label', "Branch Changes");
56 >
57 > /** Localized human-readable label for the session-wide changeset entry. */
58 > export const sessionChangesetLabel = (): string => localize('sessionChangeset.label', "All Changes");
59 >
60 > /** Localized human-readable description for the session-wide changeset entry. */
61 > export const sessionChangesetDescription = (): string => localize('sessionChangeset.description', "Show all changes made in this session");
62 >
63 > /** Localized human-readable label for the uncommitted-changes changeset entry. */
64 > export const uncommittedChangesetLabel = (): string => localize('uncommittedChangeset.label', "Uncommitted Changes");
65 >
66 > /** Localized human-readable description for the uncommitted-changes changeset entry. */
67 > export const uncommittedChangesetDescription = (): string => localize('uncommittedChangeset.description', "Show uncommitted changes in this session");
68 >
69 > /** Localized human-readable label for the per-turn changeset template entry. */
70 > export const thisTurnChangesetLabel = (): string => localize('thisTurnChangeset.label', "This Turn");
71 >
72 > /** Localized human-readable description for the per-turn changeset template entry. */
73 > export const thisTurnChangesetDescription = (): string => localize('thisTurnChangeset.description', "Show changes made in this turn");
74 >
75 > /** Localized human-readable label for the compare-turns changeset template entry. */
76 > export const compareTurnsChangesetLabel = (): string => localize('compareTurnsChangeset.label', "Compare Turns");
77 >
78 > /** Localized human-readable description for the compare-turns changeset template entry. */
79 > export const compareTurnsChangesetDescription = (): string => localize('compareTurnsChangeset.description', "Show changes made between different turns");
80 >
81 > /**
82 > * Returns the description shown next to the `Branch Changes` catalogue
83 > * entry. Prefers `${branchName} → ${baseBranchName}` when both values
84 > * are known (typical worktree-isolation case). If `baseBranchName` is
85 > * unknown, falls back to `${branchName} → ${upstreamBranchName}` when an
86 > * upstream is available. Finally falls back to `branchName` alone.
87 > * Returns `undefined` only when no branch name is known at all, so
88 > * callers can omit the description entirely.
89 > */
90 > export function formatBranchChangesetDescription(gitState: ISessionGitState): string | undefined {
91 const { baseBranchName, branchName, upstreamBranchName } = gitState;
92
103 return branchName;
104 }
106 > /** Marker injected into a changeset URI's path. */
107 > const CHANGESET_PATH_SEGMENT = '/changeset/';
108 >
109 > /** Discriminates the well-known changeset URI shapes. */
110 > export const enum ChangesetKind {
111 > Branch = 'branch',
112 > Uncommitted = 'uncommitted',
113 > Session = 'session',
114 > Turn = 'turn',
115 > Compare = 'compare-turns',
116 > /** Producer-defined id we don't recognise (single-segment only). */
117 > Unknown = 'unknown',
118 > }
119 >
120 > export function buildBranchChangesetUri(sessionUri: URI): URI {
121 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${BRANCH_CHANGESET_ID}`;
122 }
124 > /** Returns the subscribable URI for the session-wide changeset. */
125 > export function buildSessionChangesetUri(sessionUri: URI): URI {
126 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${SESSION_CHANGESET_ID}`;
127 }
129 > /** Returns the subscribable URI for the uncommitted-changes changeset. */
130 > export function buildUncommittedChangesetUri(sessionUri: URI): URI {
131 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${UNCOMMITTED_CHANGESET_ID}`;
132 }
134 > /**
135 > * Returns the URI _template_ that catalogue entries advertise for the
136 > * per-turn changeset; clients expand `{turnId}` to build the
137 > * subscribable URI via {@link buildTurnChangesetUri}.
138 > */
139 > export function buildTurnChangesetUriTemplate(sessionUri: URI): URI {
140 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${TURN_CHANGESET_PREFIX}${TURN_TEMPLATE_VARIABLE}`;
141 }
143 > /** Returns the subscribable URI for the per-turn changeset of `turnId`. */
144 > export function buildTurnChangesetUri(sessionUri: URI, turnId: string): URI {
145 if (!turnId || turnId.includes('/')) {
146 throw new Error(`buildTurnChangesetUri: turnId must be non-empty and not contain '/' (got ${JSON.stringify(turnId)})`);
148 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${TURN_CHANGESET_PREFIX}${turnId}`;
149 }
151 > /**
152 > * Returns the URI _template_ that catalogue entries advertise for the
153 > * compare-turns changeset; clients expand both `{originalTurnId}` and
154 > * `{modifiedTurnId}` to build the subscribable URI via
155 > * {@link buildCompareTurnsChangesetUri}.
156 > */
157 > export function buildCompareTurnsChangesetUriTemplate(sessionUri: URI): URI {
158 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${COMPARE_CHANGESET_PREFIX}${COMPARE_ORIGINAL_TEMPLATE_VARIABLE}/${COMPARE_MODIFIED_TEMPLATE_VARIABLE}`;
159 }
161 > /**
162 > * Returns the subscribable URI for the compare-turns changeset between
163 > * `originalTurnId` (the "from" endpoint) and `modifiedTurnId` (the "to"
164 > * endpoint). Diff direction is `originalTurnId → modifiedTurnId`.
165 > */
166 > export function buildCompareTurnsChangesetUri(sessionUri: URI, originalTurnId: string, modifiedTurnId: string): URI {
167 if (!originalTurnId || originalTurnId.includes('/')) {
168 throw new Error(`buildCompareTurnsChangesetUri: originalTurnId must be non-empty and not contain '/' (got ${JSON.stringify(originalTurnId)})`);
173 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${COMPARE_CHANGESET_PREFIX}${originalTurnId}/${modifiedTurnId}`;
174 }
176 > /**
177 > * Returns the subscribable URI for an opaque, producer-defined
178 > * `changesetId`. The id must not contain `/` — well-known multi-segment
179 > * shapes have dedicated builders (e.g. {@link buildTurnChangesetUri}).
180 > */
181 > export function buildChangesetUri(sessionUri: URI, changesetId: string): URI {
182 if (!changesetId) {
183 throw new Error('buildChangesetUri: changesetId must be non-empty');
188 return `${sessionUri}${CHANGESET_PATH_SEGMENT}${changesetId}`;
189 }
191 > /**
192 > * Parses a changeset URI back into `(sessionUri, changesetId, kind)`,
193 > * or returns `undefined` if `uri` is not a changeset URI we recognise.
194 > */
195 > export function parseChangesetUri(uri: URI): { sessionUri: URI; changesetId: string; kind: ChangesetKind; turnId?: string; originalTurnId?: string; modifiedTurnId?: string } | undefined {
196 const idx = uri.lastIndexOf(CHANGESET_PATH_SEGMENT);
197 if (idx < 0) {
241 return { sessionUri, changesetId, kind: ChangesetKind.Unknown };
242 }
244 > /** Returns `true` iff `uri` looks like a changeset URI we recognise. */
245 > export function isChangesetUri(uri: URI): boolean {
246 return parseChangesetUri(uri) !== undefined;
247 }
249 > /** Returns `true` iff `uri` is the session-wide changeset URI. */
250 > export function isSessionChangesetUri(uri: URI): boolean {
251 return parseChangesetUri(uri)?.kind === ChangesetKind.Session;
252 }
254 > /** Returns `true` iff `uri` is the uncommitted-changes changeset URI. */
255 > export function isUncommittedChangesetUri(uri: URI): boolean {
256 return parseChangesetUri(uri)?.kind === ChangesetKind.Uncommitted;
257 }
259 > /** Returns the parsed turn id when `uri` is a per-turn changeset URI. */
260 > export function parseTurnChangesetUri(uri: URI): { sessionUri: URI; turnId: string } | undefined {
261 const parsed = parseChangesetUri(uri);
262 if (parsed?.kind !== ChangesetKind.Turn || parsed.turnId === undefined) {
265 return { sessionUri: parsed.sessionUri, turnId: parsed.turnId };
266 }
268 > /** Returns the parsed turn ids when `uri` is a compare-turns changeset URI. */
269 > export function parseCompareTurnsChangesetUri(uri: URI): { sessionUri: URI; originalTurnId: string; modifiedTurnId: string } | undefined {
270 const parsed = parseChangesetUri(uri);
271 if (parsed?.kind !== ChangesetKind.Compare || parsed.originalTurnId === undefined || parsed.modifiedTurnId === undefined) {
274 return { sessionUri: parsed.sessionUri, originalTurnId: parsed.originalTurnId, modifiedTurnId: parsed.modifiedTurnId };
275 }
277 > /**
278 > * Builds the default ordered `summary.changesets` catalogue for a
279 > * session (`Branch Changes`, `Uncommitted Changes`, `This Turn`) with
280 > * label + uriTemplate only. Aggregate counts are filled in later by the
281 > * diff producer as compute passes complete.
282 > *
283 > * The first two entries (`Branch Changes`, `Uncommitted Changes`) are
284 > * git-only; `AgentService._attachGitState` strips them asynchronously
285 > * for sessions whose working directory is not a git repo. The backing
286 > * per-changeset states are still registered for every session — only
287 > * the catalogue advertisements are stripped.
288 > *
289 > * The compare-turns changeset (built by
290 > * {@link buildCompareTurnsChangesetUri}) is intentionally NOT included
291 > * in the default catalogue: it is subscribe-only. Clients that want
292 > * compare-turns diffs construct the URI themselves from two known
293 > * turn ids and subscribe directly.
294 > */
295 > export function buildDefaultChangesetCatalog(sessionUri: URI, state?: ISessionWithDefaultChat): Changeset[] {
296 // Session that failed to create
297 if (!state || state.lifecycle === SessionLifecycle.CreationFailed) {
src/vs/base/common/errors.ts 183 covered LOC · 32 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- errors.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export interface ErrorListenerCallback {
7 > (error: any): void;
8 > }
9 >
10 > export interface ErrorListenerUnbind {
11 > (): void;
12 > }
13 >
14 > // Avoid circular dependency on EventEmitter by implementing a subset of the interface.
15 > export class ErrorHandler {
16 > private unexpectedErrorHandler: (e: any) => void;
17 > private listeners: ErrorListenerCallback[];
18 >
19 > constructor() {
20 >
21 > this.listeners = [];
22 >
23 > this.unexpectedErrorHandler = function (e: any) {
24 setTimeout(() => {
25 if (e.stack) {
34 }, 0);
35 };
36 > } errors.ts
37 >
38 > addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
39 this.listeners.push(listener);
40
43 };
44 }
45 > errors.ts
46 > private emit(e: any): void {
47 this.listeners.forEach((listener) => {
48 listener(e);
49 });
50 }
51 > errors.ts
52 > private _removeListener(listener: ErrorListenerCallback): void {
53 this.listeners.splice(this.listeners.indexOf(listener), 1);
54 }
55 > errors.ts
56 > setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
57 > this.unexpectedErrorHandler = newUnexpectedErrorHandler;
58 > }
59 >
60 > getUnexpectedErrorHandler(): (e: any) => void {
61 return this.unexpectedErrorHandler;
62 }
63 > errors.ts
64 > onUnexpectedError(e: any): void {
65 this.unexpectedErrorHandler(e);
66 this.emit(e);
67 }
68 > errors.ts
69 > // For external errors, we don't want the listeners to be called
70 > onUnexpectedExternalError(e: any): void {
71 this.unexpectedErrorHandler(e);
72 }
73 > } errors.ts
74 >
75 > export const errorHandler = new ErrorHandler();
76 >
77 > /** @skipMangle */
78 > export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
79 > errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler);
80 > }
81 >
82 > /**
83 > * Returns if the error is a SIGPIPE error. SIGPIPE errors should generally be
84 > * logged at most once, to avoid a loop.
85 > *
86 > * @see https://github.com/microsoft/vscode-remote-release/issues/6481
87 > */
88 > export function isSigPipeError(e: unknown): e is Error {
89 if (!e || typeof e !== 'object') {
90 return false;
94 return cast.code === 'EPIPE' && cast.syscall?.toUpperCase() === 'WRITE';
95 }
96 > errors.ts
97 > /**
98 > * This function should only be called with errors that indicate a bug in the product.
99 > * E.g. buggy extensions/invalid user-input/network issues should not be able to trigger this code path.
100 > * If they are, this indicates there is also a bug in the product.
101 > */
102 > export function onBugIndicatingError(e: any): undefined {
103 errorHandler.onUnexpectedError(e);
104 return undefined;
105 }
106 > errors.ts
107 > export function onUnexpectedError(e: any): undefined {
108 // ignore errors from cancelled promises
109 if (!isCancellationError(e)) {
112 return undefined;
113 }
114 > errors.ts
115 > export function onUnexpectedExternalError(e: any): undefined {
116 // ignore errors from cancelled promises
117 if (!isCancellationError(e)) {
120 return undefined;
121 }
122 > errors.ts
123 > type ObjectWithCode = {
124 > readonly code: unknown;
125 > };
126 >
127 function hasErrorCode(error: object): error is ObjectWithCode {
128 return Object.hasOwn(error, 'code');
129 }
130 > errors.ts
131 > export function getErrorCode(error: unknown): string | undefined {
132 if (!error || typeof error !== 'object' || !hasErrorCode(error)) {
133 return undefined;
136 return typeof code === 'string' || typeof code === 'number' ? String(code) : undefined;
137 }
138 > errors.ts
139 > export interface SerializedError {
140 > readonly $isError: true;
141 > readonly name: string;
142 > readonly message: string;
143 > readonly stack: string;
144 > readonly noTelemetry: boolean;
145 > readonly code?: string;
146 > readonly cause?: SerializedError;
147 > }
148 >
149 > type ErrorWithCode = Error & {
150 > code: string | undefined;
151 > };
152 >
153 > export function transformErrorForSerialization(error: Error): SerializedError;
154 > export function transformErrorForSerialization(error: any): any;
155 > export function transformErrorForSerialization(error: any): any {
156 if (error instanceof Error) {
157 const { name, message, cause } = error;
172 return error;
173 }
174 > errors.ts
175 > export function transformErrorFromSerialization(data: SerializedError): Error {
176 let error: Error;
177 if (data.noTelemetry) {
191 return error;
192 }
193 > errors.ts
194 > // see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces
195 > export interface V8CallSite {
196 > getThis(): unknown;
197 > getTypeName(): string | null;
198 > getFunction(): Function | undefined;
199 > getFunctionName(): string | null;
200 > getMethodName(): string | null;
201 > getFileName(): string | null;
202 > getLineNumber(): number | null;
203 > getColumnNumber(): number | null;
204 > getEvalOrigin(): string | undefined;
205 > isToplevel(): boolean;
206 > isEval(): boolean;
207 > isNative(): boolean;
208 > isConstructor(): boolean;
209 > toString(): string;
210 > }
211 >
212 > export const canceledName = 'Canceled';
213 >
214 > /**
215 > * Checks if the given error is a promise in canceled state
216 > */
217 > export function isCancellationError(error: any): boolean {
218 if (error instanceof CancellationError) {
219 return true;
221 return error instanceof Error && error.name === canceledName && error.message === canceledName;
222 }
223 > errors.ts
224 > // !!!IMPORTANT!!!
225 > // Do NOT change this class because it is also used as an API-type.
226 > export class CancellationError extends Error {
227 > constructor() {
228 super(canceledName);
229 this.name = this.message;
230 }
231 > } errors.ts
232 >
233 > export class PendingMigrationError extends Error {
234 >
235 > private static readonly _name = 'PendingMigrationError';
236 >
237 > static is(error: unknown): error is PendingMigrationError {
238 return error instanceof PendingMigrationError || (error instanceof Error && error.name === PendingMigrationError._name);
239 }
240 > errors.ts
241 > constructor(message: string) {
242 super(message);
243 this.name = PendingMigrationError._name;
244 }
245 > } errors.ts
246 >
247 > /**
248 > * @deprecated use {@link CancellationError `new CancellationError()`} instead
249 > */
250 > export function canceled(): Error {
251 const error = new Error(canceledName);
252 error.name = error.message;
253 return error;
254 }
255 > errors.ts
256 > export function illegalArgument(name?: string): Error {
257 if (name) {
258 return new Error(`Illegal argument: ${name}`);
261 }
262 }
263 > errors.ts
264 > export function illegalState(name?: string): Error {
265 if (name) {
266 return new Error(`Illegal state: ${name}`);
269 }
270 }
271 > errors.ts
272 > export class ReadonlyError extends TypeError {
273 > constructor(name?: string) {
274 super(name ? `${name} is read-only and cannot be changed` : 'Cannot change read-only property');
275 }
276 > } errors.ts
277 >
278 > export function getErrorMessage(err: any): string {
279 if (!err) {
280 return 'Error';
291 return String(err);
292 }
293 > errors.ts
294 > export class NotImplementedError extends Error {
295 > constructor(message?: string) {
296 super('NotImplemented');
297 if (message) {
299 }
300 }
301 > } errors.ts
302 >
303 > export class NotSupportedError extends Error {
304 > constructor(message?: string) {
305 super('NotSupported');
306 if (message) {
308 }
309 }
310 > } errors.ts
311 >
312 > export class ExpectedError extends Error {
313 readonly isExpected = true;
314 > } errors.ts
315 >
316 > /**
317 > * Error that when thrown won't be logged in telemetry as an unhandled error.
318 > */
319 > export class ErrorNoTelemetry extends Error {
320 > override readonly name: string;
321 >
322 > constructor(msg?: string) {
323 super(msg);
324 this.name = 'CodeExpectedError';
325 }
326 > errors.ts
327 > public static fromError(err: Error): ErrorNoTelemetry {
328 if (err instanceof ErrorNoTelemetry) {
329 return err;
335 return result;
336 }
337 > errors.ts
338 > public static isErrorNoTelemetry(err: Error): err is ErrorNoTelemetry {
339 return err.name === 'CodeExpectedError';
340 }
341 > } errors.ts
342 >
343 > /**
344 > * This error indicates a bug.
345 > * Do not throw this for invalid user input.
346 > * Only catch this error to recover gracefully from bugs.
347 > */
348 > export class BugIndicatingError extends Error {
349 > constructor(message?: string) {
350 super(message || 'An unexpected bug occurred.');
351 Object.setPrototypeOf(this, BugIndicatingError.prototype);
src/vs/base/common/path.ts 177 covered LOC · 31 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- path.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace
7 > // Copied from: https://github.com/nodejs/node/commits/v22.15.0/lib/path.js
8 > // Excluding: the change that adds primordials
9 > // (https://github.com/nodejs/node/commit/187a862d221dec42fa9a5c4214e7034d9092792f and others)
10 > // Excluding: the change that adds glob matching
11 > // (https://github.com/nodejs/node/commit/57b8b8e18e5e2007114c63b71bf0baedc01936a6)
12 >
13 > /**
14 > * Copyright Joyent, Inc. and other Node contributors.
15 > *
16 > * Permission is hereby granted, free of charge, to any person obtaining a
17 > * copy of this software and associated documentation files (the
18 > * "Software"), to deal in the Software without restriction, including
19 > * without limitation the rights to use, copy, modify, merge, publish,
20 > * distribute, sublicense, and/or sell copies of the Software, and to permit
21 > * persons to whom the Software is furnished to do so, subject to the
22 > * following conditions:
23 > *
24 > * The above copyright notice and this permission notice shall be included
25 > * in all copies or substantial portions of the Software.
26 > *
27 > * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
28 > * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 > * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
30 > * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
31 > * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
32 > * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
33 > * USE OR OTHER DEALINGS IN THE SOFTWARE.
34 > */
35 >
36 > import * as process from './process.js';
37 >
38 > const CHAR_UPPERCASE_A = 65;/* A */
39 > const CHAR_LOWERCASE_A = 97; /* a */
40 > const CHAR_UPPERCASE_Z = 90; /* Z */
41 > const CHAR_LOWERCASE_Z = 122; /* z */
42 > const CHAR_DOT = 46; /* . */
43 > const CHAR_FORWARD_SLASH = 47; /* / */
44 > const CHAR_BACKWARD_SLASH = 92; /* \ */
45 > const CHAR_COLON = 58; /* : */
46 > const CHAR_QUESTION_MARK = 63; /* ? */
47 >
48 > class ErrorInvalidArgType extends Error {
49 > code: 'ERR_INVALID_ARG_TYPE';
50 > constructor(name: string, expected: string, actual: unknown) {
51 // determiner: 'must be' or 'must not be'
52 let determiner;
66 this.code = 'ERR_INVALID_ARG_TYPE';
67 }
68 > } path.ts
69 >
70 function validateObject(pathObject: object, name: string) {
71 if (pathObject === null || typeof pathObject !== 'object') {
73 }
74 }
75 > path.ts
76 function validateString(value: string, name: string) {
77 if (typeof value !== 'string') {
79 }
80 }
81 > path.ts
82 > const platformIsWin32 = (process.platform === 'win32');
83 >
84 function isPathSeparator(code: number | undefined) {
85 return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
86 }
87 > path.ts
88 function isPosixPathSeparator(code: number | undefined) {
89 return code === CHAR_FORWARD_SLASH;
90 }
91 > path.ts
92 function isWindowsDeviceRoot(code: number) {
93 return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
94 (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z);
95 }
96 > path.ts
97 > // Resolves . and .. elements in a path with directory names
98 function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code?: number) => boolean) {
99 let res = '';
163 return res;
164 }
165 > path.ts
166 function formatExt(ext: string): string {
167 return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : '';
168 }
169 > path.ts
170 function _format(sep: string, pathObject: ParsedPath) {
171 validateObject(pathObject, 'pathObject');
178 return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
179 }
180 > path.ts
181 > export interface ParsedPath {
182 > root: string;
183 > dir: string;
184 > base: string;
185 > ext: string;
186 > name: string;
187 > }
188 >
189 > export interface IPath {
190 > normalize(path: string): string;
191 > isAbsolute(path: string): boolean;
192 > join(...paths: string[]): string;
193 > resolve(...pathSegments: string[]): string;
194 > relative(from: string, to: string): string;
195 > dirname(path: string): string;
196 > basename(path: string, suffix?: string): string;
197 > extname(path: string): string;
198 > format(pathObject: ParsedPath): string;
199 > parse(path: string): ParsedPath;
200 > toNamespacedPath(path: string): string;
201 > sep: '\\' | '/';
202 > delimiter: string;
203 > win32: IPath | null;
204 > posix: IPath | null;
205 > }
206 >
207 > export const win32: IPath = {
208 > // path.resolve([from ...], to)
209 > resolve(...pathSegments: string[]): string {
210 let resolvedDevice = '';
211 let resolvedTail = '';
343 `${resolvedDevice}${resolvedTail}` || '.';
344 },
345 > path.ts
346 > normalize(path: string): string {
347 validateString(path, 'path');
348 const len = path.length;
450 return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`;
451 },
452 > path.ts
453 > isAbsolute(path: string): boolean {
454 validateString(path, 'path');
455 const len = path.length;
466 isPathSeparator(path.charCodeAt(2)));
467 },
468 > path.ts
469 > join(...paths: string[]): string {
470 if (paths.length === 0) {
471 return '.';
536 return win32.normalize(joined);
537 },
538 > path.ts
539 >
540 > // It will solve the relative path from `from` to `to`, for instance:
541 > // from = 'C:\\orandea\\test\\aaa'
542 > // to = 'C:\\orandea\\impl\\bbb'
543 > // The output of the function should be: '..\\..\\impl\\bbb'
544 > relative(from: string, to: string): string {
545 validateString(from, 'from');
546 validateString(to, 'to');
699 return toOrig.slice(toStart, toEnd);
700 },
701 > path.ts
702 > toNamespacedPath(path: string): string {
703 // Note: this will *probably* throw somewhere.
704 if (typeof path !== 'string' || path.length === 0) {
730 return resolvedPath;
731 },
732 > path.ts
733 > dirname(path: string): string {
734 validateString(path, 'path');
735 const len = path.length;
818 return path.slice(0, end);
819 },
820 > path.ts
821 > basename(path: string, suffix?: string): string {
822 if (suffix !== undefined) {
823 validateString(suffix, 'suffix');
906 return path.slice(start, end);
907 },
908 > path.ts
909 > extname(path: string): string {
910 validateString(path, 'path');
911 let start = 0;
972 return path.slice(startDot, end);
973 },
974 > path.ts
975 > format: _format.bind(null, '\\'),
976 >
977 > parse(path) {
978 validateString(path, 'path');
979
1126 return ret;
1127 },
1128 > path.ts
1129 > sep: '\\',
1130 > delimiter: ';',
1131 > win32: null,
1132 > posix: null
1133 > };
1134 >
1135 > const posixCwd = (() => {
1136 > if (platformIsWin32) {
1137 // Converts Windows' backslash path separators to POSIX forward slashes
1138 // and truncates any drive indicator
1143 };
1144 }
1145 > path.ts
1146 > // We're already on POSIX, no need for any transformations
1147 > return () => process.cwd();
1148 > })();
1149 >
1150 > export const posix: IPath = {
1151 > // path.resolve([from ...], to)
1152 > resolve(...pathSegments: string[]): string {
1153 let resolvedPath = '';
1154 let resolvedAbsolute = false;
1186 return resolvedPath.length > 0 ? resolvedPath : '.';
1187 },
1188 > path.ts
1189 > normalize(path: string): string {
1190 validateString(path, 'path');
1191
1213 return isAbsolute ? `/${path}` : path;
1214 },
1215 > path.ts
1216 > isAbsolute(path: string): boolean {
1217 validateString(path, 'path');
1218 return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH;
1219 },
1220 > path.ts
1221 > join(...paths: string[]): string {
1222 if (paths.length === 0) {
1223 return '.';
1239 return posix.normalize(path.join('/'));
1240 },
1241 > path.ts
1242 > relative(from: string, to: string): string {
1243 validateString(from, 'from');
1244 validateString(to, 'to');
1312 return `${out}${to.slice(toStart + lastCommonSep)}`;
1313 },
1314 > path.ts
1315 > toNamespacedPath(path: string): string {
1316 // Non-op on posix systems
1317 return path;
1318 },
1319 > path.ts
1320 > dirname(path: string): string {
1321 validateString(path, 'path');
1322 if (path.length === 0) {
1346 return path.slice(0, end);
1347 },
1348 > path.ts
1349 > basename(path: string, suffix?: string): string {
1350 if (suffix !== undefined) {
1351 validateString(suffix, 'suffix');
1426 return path.slice(start, end);
1427 },
1428 > path.ts
1429 > extname(path: string): string {
1430 validateString(path, 'path');
1431 let startDot = -1;
1480 return path.slice(startDot, end);
1481 },
1482 > path.ts
1483 > format: _format.bind(null, '/'),
1484 >
1485 > parse(path: string): ParsedPath {
1486 validateString(path, 'path');
1487
1565 return ret;
1566 },
1567 > path.ts
1568 > sep: '/',
1569 > delimiter: ':',
1570 > win32: null,
1571 > posix: null
1572 > };
1573 >
1574 > posix.win32 = win32.win32 = win32;
1575 > posix.posix = win32.posix = posix;
1576 >
1577 > export const normalize = (platformIsWin32 ? win32.normalize : posix.normalize);
1578 > export const isAbsolute = (platformIsWin32 ? win32.isAbsolute : posix.isAbsolute);
1579 > export const join = (platformIsWin32 ? win32.join : posix.join);
1580 > export const resolve = (platformIsWin32 ? win32.resolve : posix.resolve);
1581 > export const relative = (platformIsWin32 ? win32.relative : posix.relative);
1582 > export const dirname = (platformIsWin32 ? win32.dirname : posix.dirname);
1583 > export const basename = (platformIsWin32 ? win32.basename : posix.basename);
1584 > export const extname = (platformIsWin32 ? win32.extname : posix.extname);
1585 > export const format = (platformIsWin32 ? win32.format : posix.format);
1586 > export const parse = (platformIsWin32 ? win32.parse : posix.parse);
1587 > export const toNamespacedPath = (platformIsWin32 ? win32.toNamespacedPath : posix.toNamespacedPath);
1588 > export const sep = (platformIsWin32 ? win32.sep : posix.sep);
1589 > export const delimiter = (platformIsWin32 ? win32.delimiter : posix.delimiter);
src/vs/platform/agentHost/common/state/protocol/channels-terminal/state.ts 173 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from '../common/state.js';
10 >
11 > // ─── Terminal Types ──────────────────────────────────────────────────────────
12 >
13 > /**
14 > * Lightweight terminal metadata exposed on the root state.
15 > *
16 > * @category Terminal Types
17 > */
18 > export interface TerminalInfo {
19 > /** Terminal URI (subscribable for full terminal state) */
20 > resource: URI;
21 > /** Human-readable terminal title */
22 > title: string;
23 > /** Who currently holds this terminal */
24 > claim: TerminalClaim;
25 > /** Process exit code, if the terminal process has exited */
26 > exitCode?: number;
27 > }
28 >
29 > /**
30 > * Discriminant for terminal claim kinds.
31 > *
32 > * @category Terminal Types
33 > */
34 > export const enum TerminalClaimKind {
35 > Client = 'client',
36 > Session = 'session',
37 > }
38 >
39 > /**
40 > * A terminal claimed by a connected client.
41 > *
42 > * @category Terminal Types
43 > */
44 > export interface TerminalClientClaim {
45 > /** Discriminant */
46 > kind: TerminalClaimKind.Client;
47 > /** The `clientId` of the claiming client */
48 > clientId: string;
49 > }
50 >
51 > /**
52 > * A terminal claimed by a session, optionally scoped to a specific turn or tool call.
53 > *
54 > * @category Terminal Types
55 > */
56 > export interface TerminalSessionClaim {
57 > /** Discriminant */
58 > kind: TerminalClaimKind.Session;
59 > /** Session URI that claimed the terminal */
60 > session: URI;
61 > /** Optional turn identifier within the session */
62 > turnId?: string;
63 > /** Optional tool call identifier within the turn */
64 > toolCallId?: string;
65 > }
66 >
67 > /**
68 > * Describes who currently holds a terminal. A terminal may be claimed by
69 > * either a connected client or a session (e.g. during a tool call).
70 > *
71 > * @category Terminal Types
72 > */
73 > export type TerminalClaim = TerminalClientClaim | TerminalSessionClaim;
74 >
75 > /**
76 > * Full state for a single terminal, loaded when a client subscribes to the terminal's URI.
77 > *
78 > * @category Terminal Types
79 > */
80 > export interface TerminalState {
81 > /** Human-readable terminal title */
82 > title: string;
83 > /** Current working directory of the terminal process */
84 > cwd?: URI;
85 > /** Terminal width in columns */
86 > cols?: number;
87 > /** Terminal height in rows */
88 > rows?: number;
89 > /**
90 > * Typed content parts, replacing the flat `content: string`.
91 > *
92 > * Naive consumers that only need the raw VT stream can reconstruct it with:
93 > * `content.map(p => p.type === 'command' ? p.output : p.value).join('')`
94 > *
95 > * Consumers that need command boundaries can filter by part type.
96 > */
97 > content: TerminalContentPart[];
98 > /** Process exit code, set when the terminal process exits */
99 > exitCode?: number;
100 > /** Who currently holds this terminal */
101 > claim: TerminalClaim;
102 > /**
103 > * Whether this terminal emits `terminal/commandExecuted` and
104 > * `terminal/commandFinished` actions and populates `command`-typed parts.
105 > *
106 > * Clients MUST check this flag before relying on command detection.
107 > * Do NOT use the presence of a `command` part as a feature flag — parts
108 > * are absent in the normal idle state.
109 > */
110 > supportsCommandDetection?: boolean;
111 > /**
112 > * Whether this terminal-style resource is backed by a pseudoterminal.
113 > * When `false`, output is plain text and clients do not need to parse
114 > * VT sequences.
115 > */
116 > isPty?: boolean;
117 > }
118 >
119 > // ─── Terminal Content Parts ──────────────────────────────────────────────────
120 >
121 > /**
122 > * A content part within terminal output.
123 > *
124 > * @category Terminal Types
125 > */
126 > export type TerminalContentPart =
127 > | TerminalUnclassifiedPart
128 > | TerminalCommandPart;
129 >
130 > /**
131 > * Unstructured terminal output — content before, between, or after commands,
132 > * or from terminals that do not support command detection.
133 > *
134 > * @category Terminal Types
135 > */
136 > export interface TerminalUnclassifiedPart {
137 > type: 'unclassified';
138 > /** Accumulated VT output. Appended to by `terminal/data` when no command is executing. */
139 > value: string;
140 > }
141 >
142 > /**
143 > * A single command: its command line and the output it produced.
144 > *
145 > * While `isComplete` is false the command is still executing; `output` grows
146 > * as `terminal/data` actions arrive. At `terminal/commandFinished` the part
147 > * is mutated in-place with `isComplete: true` and the completion metadata.
148 > *
149 > * @category Terminal Types
150 > */
151 > export interface TerminalCommandPart {
152 > type: 'command';
153 > /**
154 > * Stable id matching the `commandId` on the corresponding
155 > * `terminal/commandExecuted` and `terminal/commandFinished` actions.
156 > */
157 > commandId: string;
158 > /** The command line submitted to the shell. */
159 > commandLine: string;
160 > /**
161 > * Accumulated VT output. Appended to by `terminal/data` while `isComplete`
162 > * is false. Shell integration escape sequences are stripped by the server.
163 > */
164 > output: string;
165 > /** Unix timestamp (ms) when execution started, as reported by the server. */
166 > timestamp: number;
167 > /** Whether the command has finished. */
168 > isComplete: boolean;
169 > /** Shell exit code. Set at completion. `undefined` if unknown. */
170 > exitCode?: number;
171 > /** Wall-clock duration in milliseconds. Set at completion. */
172 > durationMs?: number;
173 > }
src/vs/platform/agentHost/common/state/protocol/channels-chat/commands.ts 147 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- commands.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from '../common/state.js';
10 > import type { BaseParams } from '../common/commands.js';
11 > import type { Message, SideChatSelection } from './state.js';
12 >
13 > // ─── createChat ──────────────────────────────────────────────────────────────
14 >
15 > /**
16 > * How a new chat uses its source chat and turn.
17 > */
18 > export const enum ChatSourceKind {
19 > /** Copy source history through the referenced turn into the new chat. */
20 > Fork = 'fork',
21 > /** Supply source context without copying it into the new chat's visible history. */
22 > SideChat = 'sideChat',
23 > }
24 >
25 > /**
26 > * Copies source history through a completed turn into the new chat.
27 > */
28 > export interface ForkChatSource {
29 > /** Discriminant */
30 > kind: ChatSourceKind.Fork;
31 > /** URI of the existing source chat. */
32 > chat: URI;
33 > /**
34 > * Completed turn identifier in the source chat.
35 > *
36 > * Content through this turn is copied into the new chat's visible `turns`.
37 > */
38 > turnId: string;
39 > }
40 >
41 > /**
42 > * Supplies source context to a new side chat without copying it into the side
43 > * chat's visible history.
44 > */
45 > export interface SideChatSource {
46 > /** Discriminant */
47 > kind: ChatSourceKind.SideChat;
48 > /** URI of the existing source chat. */
49 > chat: URI;
50 > /**
51 > * Stable source-turn identifier in the source chat.
52 > *
53 > * Hosts resolve this id against the source chat's current `activeTurn` or its
54 > * retained `turns` when accepting `createChat`. If it names the current
55 > * active turn, the host snapshots the source chat's retained history plus
56 > * that turn's current user message and any partial assistant response already
57 > * available. Once that turn later becomes historical, it is still referenced
58 > * by this same identifier.
59 > */
60 > turnId: string;
61 > /**
62 > * Optional immutable selected-text snapshot to carry into the created side
63 > * chat's origin.
64 > *
65 > * When present, the host MUST snapshot and preserve this exact selection when
66 > * it accepts `createChat`; later source-turn deltas do not alter it.
67 > */
68 > selection?: SideChatSelection;
69 > }
70 >
71 > /**
72 > * Identifies a source chat for a new chat.
73 > */
74 > export type ChatSource =
75 > | ForkChatSource
76 > | SideChatSource;
77 >
78 > /**
79 > * Creates a new chat within a session.
80 > *
81 > * @category Commands
82 > * @method createChat
83 > * @direction Client → Server
84 > * @messageType Request
85 > * @version 1
86 > */
87 > export interface CreateChatParams extends BaseParams {
88 > /** Session URI containing the new chat. */
89 > channel: URI;
90 > /** Chat URI (client-chosen, e.g. `ahp-chat:/<uuid>`). */
91 > chat: URI;
92 > /** Optional initial message for the new chat. */
93 > initialMessage?: Message;
94 > /**
95 > * Optional source chat and source turn.
96 > *
97 > * The source chat MUST belong to this session. Clients MUST only request
98 > * `kind: "fork"` when the selected agent advertises
99 > * `capabilities.multipleChats.fork`, and `kind: "sideChat"` when the
100 > * selected agent advertises `capabilities.multipleChats.sideChat`. Both
101 > * source forms carry a stable top-level `turnId`. Forks target completed
102 > * turns. Side chats also carry a stable `turnId`, which the host resolves
103 > * against the source chat's current active turn or retained history. If it
104 > * resolves to the active turn, the host snapshots the currently available
105 > * partial response when accepting `createChat`. When
106 > * `source.kind === "sideChat"` and `source.selection` is present, the host
107 > * also snapshots and preserves that exact selected text in the created chat's
108 > * origin; any `responsePartId` there is provenance only, not a live range.
109 > */
110 > source?: ChatSource;
111 > /**
112 > * Initial working-directory subset for this chat. Every entry MUST be
113 > * present in the owning session's `workingDirectories`; the server MUST
114 > * reject any entry that is not. When absent, the chat inherits the full
115 > * session set. Forked chats (those whose `source.kind` is `"fork"`) inherit
116 > * the source chat's `workingDirectories`; this field is ignored for forks.
117 > *
118 > * A client MUST NOT supply this field unless the agent advertises
119 > * {@link AgentCapabilities.multipleWorkingDirectories}.
120 > */
121 > workingDirectories?: URI[];
122 > /**
123 > * The chat's primary working directory — the distinguished root this chat is
124 > * centered on. When set, it MUST be one of the chat's effective working
125 > * directories ({@link workingDirectories}, or the session's set when that is
126 > * omitted). A client SHOULD supply this when the agent advertises
127 > * {@link MultipleWorkingDirectoriesCapability.requiresPrimary}; a host MAY
128 > * reject creation that omits it, or fall back to the first of the chat's
129 > * directories. Fixed at creation and reported (read-only) on
130 > * {@link ChatState.primaryWorkingDirectory}. Ignored for forks (a chat whose
131 > * `source.kind` is `"fork"` inherits the source chat's primary).
132 > */
133 > primaryWorkingDirectory?: URI;
134 > }
135 >
136 > // ─── disposeChat ─────────────────────────────────────────────────────────────
137 >
138 > /**
139 > * Disposes a chat and cleans up server-side resources.
140 > *
141 > * @category Commands
142 > * @method disposeChat
143 > * @direction Client → Server
144 > * @messageType Request
145 > * @version 1
146 > */
147 > export interface DisposeChatParams extends BaseParams { }
src/vs/platform/agentHost/common/state/sessionProtocol.ts 140 covered LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionProtocol.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Protocol messages using JSON-RPC 2.0 framing for the sessions process.
7 > // See protocol.md for the full design.
8 > //
9 > // Most types are re-exported from the auto-generated protocol layer.
10 > // This file adds VS Code-specific additions (ISetAuthTokenParams, ProtocolError)
11 > // and backward-compatible aliases.
12 >
13 > // ---- Re-exports from protocol -----------------------------------------------
14 >
15 > // JSON-RPC base types
16 > export type {
17 > JsonRpcErrorResponse,
18 > JsonRpcNotification,
19 > JsonRpcParseErrorResponse,
20 > JsonRpcRequest,
21 > JsonRpcResponse,
22 > JsonRpcSuccessResponse,
23 > } from './protocol/messages.js';
24 >
25 > // Typed message unions
26 > export type {
27 > AhpClientNotification,
28 > AhpNotification,
29 > AhpRequest,
30 > AhpResponse,
31 > AhpServerNotification,
32 > AhpSuccessResponse,
33 > CommandMap,
34 > ClientNotificationMap,
35 > ProtocolMessage,
36 > ServerNotificationMap,
37 > } from './protocol/messages.js';
38 >
39 > // Command params and results
40 > export type {
41 > CreateSessionParams,
42 > DirectoryEntry,
43 > DispatchActionParams,
44 > DisposeSessionParams,
45 > FetchTurnsParams,
46 > FetchTurnsResult,
47 > InitializeParams,
48 > InitializeResult,
49 > ListSessionsParams,
50 > ListSessionsResult,
51 > ReconnectParams,
52 > ReconnectReplayResult,
53 > ReconnectResult,
54 > ReconnectSnapshotResult,
55 > ResourceCopyParams,
56 > ResourceCopyResult,
57 > ResourceDeleteParams,
58 > ResourceDeleteResult,
59 > ResourceListParams,
60 > ResourceListResult,
61 > ResourceMkdirParams,
62 > ResourceMkdirResult,
63 > ResourceMoveParams,
64 > ResourceMoveResult,
65 > ResourceReadParams,
66 > ResourceReadResult,
67 > ResourceResolveParams,
68 > ResourceResolveResult,
69 > ResourceWriteParams,
70 > ResourceWriteResult,
71 > SubscribeParams,
72 > SubscribeResult,
73 > UnsubscribeParams,
74 > } from './protocol/commands.js';
75 >
76 > export type {
77 > CreateResourceWatchParams,
78 > CreateResourceWatchResult,
79 > } from './protocol/channels-resource-watch/commands.js';
80 >
81 > export { ContentEncoding, ReconnectResultType, ResourceType, ResourceWriteMode } from './protocol/commands.js';
82 > export { ResourceChangeType } from './protocol/channels-resource-watch/state.js';
83 > export type { ResourceChange, ResourceWatchState } from './protocol/channels-resource-watch/state.js';
84 >
85 > // Error codes
86 > export { AhpErrorCodes, JsonRpcErrorCodes } from './protocol/errors.js';
87 > export type { AhpErrorCode, JsonRpcErrorCode } from './protocol/errors.js';
88 >
89 > // Snapshot type (re-exported from state). The generated `Snapshot.state`
90 > // union now includes `ChatState`, so per-chat snapshots type-check directly.
91 > import type { Snapshot as ProtocolSnapshot } from './protocol/state.js';
92 > export type IStateSnapshot = ProtocolSnapshot;
93 >
94 > // ---- Backward-compatible error code aliases ---------------------------------
95 >
96 > export const JSON_RPC_PARSE_ERROR = -32700 as const;
97 > export const JSON_RPC_INTERNAL_ERROR = -32603 as const;
98 > export const AHP_SESSION_NOT_FOUND = -32001 as const;
99 > export const AHP_PROVIDER_NOT_FOUND = -32002 as const;
100 > export const AHP_SESSION_ALREADY_EXISTS = -32003 as const;
101 > export const AHP_TURN_IN_PROGRESS = -32004 as const;
102 > export const AHP_UNSUPPORTED_PROTOCOL_VERSION = -32005 as const;
103 > export const AHP_CONTENT_NOT_FOUND = -32006 as const;
104 > export const AHP_AUTH_REQUIRED = -32007 as const;
105 >
106 > // ---- Type guards -----------------------------------------------------------
107 >
108 > import type { AhpRequest, AhpNotification, AhpSuccessResponse, ProtocolMessage, JsonRpcErrorResponse } from './protocol/messages.js';
109 >
110 > export function isJsonRpcRequest(msg: ProtocolMessage): msg is AhpRequest {
111 return 'method' in msg && 'id' in msg;
112 }
114 > export function isJsonRpcNotification(msg: ProtocolMessage): msg is AhpNotification {
115 return 'method' in msg && !('id' in msg);
116 }
118 > export function isJsonRpcResponse(msg: ProtocolMessage): msg is AhpSuccessResponse | JsonRpcErrorResponse {
119 return 'id' in msg && !('method' in msg);
120 }
122 > // ---- VS Code-specific types ------------------------------------------------
123 >
124 > /**
125 > * Error with a JSON-RPC error code for protocol-level failures.
126 > * Optionally carries a `data` payload for structured error details.
127 > */
128 > export class ProtocolError extends Error {
129 > constructor(readonly code: number, message: string, readonly data?: unknown) {
130 super(message);
131 }
133 >
134 > /**
135 > * VS Code-specific extension: set the auth token on the server.
136 > * Not yet part of the official protocol.
137 > */
138 > export interface ISetAuthTokenParams {
139 > readonly token: string;
140 > }
141 >
142 > // ---- Server → Client notification param aliases (backward compat) -----------
143 >
144 > import type { INotification } from './sessionActions.js';
145 >
146 > export interface INotificationBroadcastParams {
147 > readonly notification: INotification;
148 > }
src/vs/base/test/common/virtualScheduling/trace.ts 122 covered LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- trace.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { BugIndicatingError } from '../../../common/errors.js';
7 >
8 > /**
9 > * # Trace — causal-chain attribution for scheduled work
10 > *
11 > * A {@link Trace} is an immutable value identifying a causal chain. Every
12 > * non-root trace carries a `parent`; the head of the chain has no parent.
13 > * Use {@link child} to extend a chain when scheduling follow-up work.
14 > *
15 > * Traces are used to answer "who caused this?" for any virtual event:
16 > * useful for debugging, for per-owner termination, and for attribution in
17 > * error messages.
18 > */
19 > export class Trace {
20 > private static _idCounter = 0;
21 > public readonly id: number = ++Trace._idCounter;
22 > public readonly root: Trace;
23 > public readonly depth: number;
24 >
25 > constructor(
26 > public readonly parent: Trace | undefined,
27 > public readonly label: string,
28 > public readonly stack: string | undefined = undefined,
29 > ) {
30 > this.root = parent?.root ?? this;
31 > this.depth = (parent?.depth ?? -1) + 1;
32 > }
33 >
34 > child(label: string, stack?: string): Trace {
35 return new Trace(this, label, stack);
36 }
37 > trace.ts
38 > /** "#id label ← #id label ← … ← #id label" */
39 > describe(): string {
40 const parts: string[] = [];
41 for (let t: Trace | undefined = this; t; t = t.parent) {
44 return parts.join(' ← ');
45 }
46 > trace.ts
47 > toString(): string { return this.describe(); }
48 > }
49 >
50 > /** Sentinel for "no known causal predecessor". */
51 > export const ROOT_TRACE: Trace = new Trace(undefined, '<root>');
52 >
53 > export function createTraceRoot(label: string, stack?: string): Trace {
54 return new Trace(undefined, label, stack);
55 }
56 > trace.ts
57 > interface Frame {
58 > readonly trace: Trace;
59 > readonly prev: Frame | undefined;
60 > }
61 >
62 > const ROOT_FRAME: Frame = { trace: ROOT_TRACE, prev: undefined };
63 >
64 > /**
65 > * Options for {@link TraceContext.runAsHandler}.
66 > *
67 > * # Why this is a per-call option
68 > *
69 > * `runAsHandler` cannot restore the previous trace synchronously: microtasks
70 > * enqueued by `fn` (including awaited continuations) must observe the new
71 > * trace. So the reset is deferred — but it must fire after the *closure* of
72 > * the microtask queue (the current microtask plus every microtask it
73 > * recursively enqueues), not just one drain.
74 > *
75 > * Per spec, the host doesn't run a macrotask until the microtask queue is
76 > * empty, so any macrotask primitive (`setTimeout(0)`, `setImmediate`, the
77 > * `setTimeout0` shim) achieves this. Letting the *caller* supply the sink
78 > * means:
79 > *
80 > * - the {@link VirtualTimeProcessor} can route the reset through the same
81 > * primitive its embedding uses for its own host hops, eliminating any
82 > * race between the processor's hops and the trace-reset timer;
83 > *
84 > * - production code without a processor can still use a real
85 > * `setTimeout(0)`-based sink and get the same semantics;
86 > *
87 > * - tests can install a deterministic sink (e.g. a hand-driven queue) for
88 > * fully synchronous assertions.
89 > */
90 > export interface RunAsHandlerOptions {
91 > /**
92 > * Sink for the deferred trace-reset.
93 > *
94 > * Must invoke `reset` after the microtask closure that follows the
95 > * `runAsHandler` call returns — i.e. on the next host macrotask.
96 > */
97 > readonly afterMicrotaskClosure: (reset: () => void) => void;
98 > }
99 >
100 > /**
101 > * Holds the mutable "current trace frame" slot. Construct fresh instances
102 > * for test isolation, or use {@link TraceContext.instance} for shared state.
103 > */
104 > export class TraceContext {
105 > public static readonly instance = new TraceContext();
106 >
107 > private _current: Frame = ROOT_FRAME;
108 > private _isHandlerRunning = false;
109 >
110 > currentTrace(): Trace { return this._current.trace; }
111 >
112 > /**
113 > * Install `t` as current for the synchronous duration of `fn`, then
114 > * restore. Nestable. Microtasks enqueued by fn that run after fn returns
115 > * see the *restored* trace — use {@link runAsHandler} when continuation
116 > * inheritance is wanted.
117 > */
118 > runWithTrace<T>(t: Trace, fn: () => T): T {
119 const prev = this._current;
120 const next: Frame = { trace: t, prev };
132 }
133 }
134 > trace.ts
135 > /**
136 > * Install `t` as current and run `fn`. The trace stays current through
137 > * the microtask closure that follows `fn`, so awaited continuations
138 > * inside fn observe `t`. The reset is dispatched via
139 > * `opts.afterMicrotaskClosure`.
140 > *
141 > * Throws on synchronous re-entry: timer callbacks never nest on the
142 > * same JS stack frame, so this only fires for misuse.
143 > */
144 > runAsHandler<T>(t: Trace, fn: () => T, opts: RunAsHandlerOptions): T {
145 if (this._isHandlerRunning) {
146 throw new Error(
165 }
166 }
167 > trace.ts
168 > _resetForTesting(): void {
169 this._current = ROOT_FRAME;
170 this._isHandlerRunning = false;
171 }
172 > } trace.ts
src/vs/platform/agentHost/common/state/protocol/channels-session/reducer.ts 121 covered LOC · 36 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import { SessionLifecycle, SessionStatus, CustomizationType, McpServerStatus, type SessionState, type SessionInputRequest, type McpServerCustomization } from './state.js';
11 > import type { SessionAction } from '../action-origin.generated.js';
12 > import { softAssertNever } from '../common/reducer-helpers.js';
13 >
14 > // ─── Helpers ─────────────────────────────────────────────────────────────────
15 >
16 > /** Bitmask covering the mutually-exclusive activity bits (bits 0–4). */
17 > const STATUS_ACTIVITY_MASK = (1 << 5) - 1;
18 >
19 > /** Sets or clears a metadata flag on a status value. */
20 function withStatusFlag(status: SessionStatus, flag: SessionStatus, set: boolean): SessionStatus {
21 return set ? status | flag : status & ~flag;
22 }
23 > reducer.ts
24 > /**
25 > * Reflects the session-level {@link SessionState.inputNeeded | input queue}
26 > * into the activity bits of `status`. A non-empty queue promotes the activity
27 > * to {@link SessionStatus.InputNeeded}; emptying it clears the
28 > * input-needed-specific bit. Since `InputNeeded` implies
29 > * {@link SessionStatus.InProgress}, an unblocked turn falls back to
30 > * `InProgress` while an already-idle session stays idle. Orthogonal flags
31 > * (`IsRead` / `IsArchived`) are preserved.
32 > */
33 function withInputNeededStatus(status: SessionStatus, inputNeeded: readonly SessionInputRequest[]): SessionStatus {
34 if (inputNeeded.length > 0) {
37 return status & ~(SessionStatus.InputNeeded & ~SessionStatus.InProgress);
38 }
39 > reducer.ts
40 function updateMcpServerCustomization(
41 state: SessionState,
84 return { ...state, customizations: updated };
85 }
86 > reducer.ts
87 > // ─── Session Reducer ─────────────────────────────────────────────────────────
88 >
89 > /**
90 > * Pure reducer for session state. Handles all {@link SessionAction} variants.
91 > */
92 > export function sessionReducer(state: SessionState, action: SessionAction, log?: (msg: string) => void): SessionState {
93 > switch (action.type) { reducer.ts
94 > // ── Lifecycle ──────────────────────────────────────────────────────────
95 >
96 > case ActionType.SessionReady:
97 > // `SessionReady` is purely a lifecycle transition (Creating -> reducer.ts
98 > // Ready). It must not touch `status`: for provisional sessions the
99 > // first turn can start before materialization completes, so an
100 > // `activeTurn` may already be set when this action is dispatched
101 > // (e.g. from a materialize-session handler). Other reducers keep
102 > // `status` in sync with the activity state, so leaving it alone here
103 > // is correct.
104 > return { ...state, lifecycle: SessionLifecycle.Ready };
105 > reducer.ts
106 > case ActionType.SessionCreationFailed:
107 return {
108 ...state,
110 creationError: action.error,
111 };
112 > reducer.ts
113 > case ActionType.SessionChatAdded: {
114 const list = state.chats;
115 const idx = list.findIndex(c => c.resource === action.summary.resource);
121 return { ...state, chats: updated };
122 }
123 > reducer.ts
124 > case ActionType.SessionChatRemoved: {
125 const list = state.chats;
126 const idx = list.findIndex(c => c.resource === action.chat);
136 return next;
137 }
138 > reducer.ts
139 > case ActionType.SessionChatUpdated: {
140 > const list = state.chats; reducer.ts
141 > const idx = list.findIndex(c => c.resource === action.chat);
142 > if (idx < 0) {
143 return state;
144 }
145 > const { resource: _ignored, ...changes } = action.changes; reducer.ts
146 > const updated = list.slice();
147 > updated[idx] = { ...list[idx], ...changes };
148 > return { ...state, chats: updated };
149 > }
150 > reducer.ts
151 > case ActionType.SessionDefaultChatChanged:
152 return { ...state, defaultChat: action.defaultChat };
153 > reducer.ts
154 > // ── Metadata ──────────────────────────────────────────────────────────
155 >
156 > case ActionType.SessionTitleChanged:
157 return { ...state, title: action.title };
158 > reducer.ts
159 > case ActionType.SessionIsReadChanged:
160 return {
161 ...state,
162 status: withStatusFlag(state.status, SessionStatus.IsRead, action.isRead),
163 };
164 > reducer.ts
165 > case ActionType.SessionIsArchivedChanged:
166 return {
167 ...state,
168 status: withStatusFlag(state.status, SessionStatus.IsArchived, action.isArchived),
169 };
170 > reducer.ts
171 > case ActionType.SessionActivityChanged:
172 return { ...state, activity: action.activity };
173 > reducer.ts
174 > case ActionType.SessionChangesetsChanged: {
175 const { changesets: _omit, ...stateWithoutChangesets } = state;
176 return action.changesets
178 : stateWithoutChangesets;
179 }
180 > reducer.ts
181 > case ActionType.SessionConfigChanged:
182 if (!state.config) {
183 return state;
190 },
191 };
192 > reducer.ts
193 > case ActionType.SessionMetaChanged:
194 return { ...state, _meta: action._meta };
195 > reducer.ts
196 > case ActionType.SessionServerToolsChanged:
197 return { ...state, serverTools: action.tools };
198 > reducer.ts
199 > case ActionType.SessionActiveClientSet: {
200 const list = state.activeClients;
201 const idx = list.findIndex(c => c.clientId === action.activeClient.clientId);
207 return { ...state, activeClients: updated };
208 }
209 > reducer.ts
210 > case ActionType.SessionActiveClientRemoved: {
211 const list = state.activeClients;
212 const idx = list.findIndex(c => c.clientId === action.clientId);
218 return { ...state, activeClients: updated };
219 }
220 > reducer.ts
221 > // ── Working Directories ─────────────────────────────────────────────
222 >
223 > case ActionType.SessionWorkingDirectorySet: {
224 const list = state.workingDirectories ?? [];
225 if (list.includes(action.directory)) {
228 return { ...state, workingDirectories: [...list, action.directory] };
229 }
230 > reducer.ts
231 > case ActionType.SessionWorkingDirectoryRemoved: {
232 const list = state.workingDirectories;
233 if (!list) {
242 return { ...state, workingDirectories: updated };
243 }
244 > reducer.ts
245 > // ── Input Needed ────────────────────────────────────────────────────
246 >
247 > case ActionType.SessionInputNeededSet: {
248 const list = state.inputNeeded ?? [];
249 const idx = list.findIndex(r => r.id === action.request.id);
254 return { ...state, inputNeeded, status: withInputNeededStatus(state.status, inputNeeded) };
255 }
256 > reducer.ts
257 > case ActionType.SessionInputNeededRemoved: {
258 const list = state.inputNeeded;
259 if (!list) {
274 return next;
275 }
276 > reducer.ts
277 > // ── Customizations ──────────────────────────────────────────────────
278 >
279 > case ActionType.SessionCustomizationsChanged:
280 return { ...state, customizations: action.customizations };
281 > reducer.ts
282 > case ActionType.SessionCustomizationToggled: {
283 const list = state.customizations;
284 if (!list) {
312 return state;
313 }
314 > reducer.ts
315 > case ActionType.SessionCustomizationUpdated: {
316 const list = state.customizations ?? [];
317 const idx = list.findIndex(c => c.id === action.customization.id);
323 return { ...state, customizations: updated };
324 }
325 > reducer.ts
326 > case ActionType.SessionCustomizationRemoved: {
327 const list = state.customizations;
328 if (!list) {
358 return { ...state, customizations: updated };
359 }
360 > reducer.ts
361 > case ActionType.SessionMcpServerStateChanged: {
362 return updateMcpServerCustomization(state, action.id, entry => ({
363 ...entry,
366 }));
367 }
368 > reducer.ts
369 > case ActionType.SessionMcpServerStartRequested: {
370 return updateMcpServerCustomization(state, action.id, entry => ({
371 ...entry,
374 }));
375 }
376 > reducer.ts
377 > case ActionType.SessionMcpServerStopRequested: {
378 return updateMcpServerCustomization(state, action.id, entry => ({
379 ...entry,
382 }));
383 }
384 > reducer.ts
385 > default:
386 softAssertNever(action, log);
387 return state;
388 > } reducer.ts
389 > }
src/vs/platform/instantiation/common/instantiation.ts 121 covered LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- instantiation.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { DisposableStore } from '../../../base/common/lifecycle.js';
7 > import * as descriptors from './descriptors.js';
8 > import { ServiceCollection } from './serviceCollection.js';
9 >
10 > // ------ internal util
11 >
12 > export namespace _util {
13 >
14 > export const serviceIds = new Map<string, ServiceIdentifier<any>>();
15 >
16 > export const DI_TARGET = '$di$target';
17 > export const DI_DEPENDENCIES = '$di$dependencies';
18 >
19 > export function getServiceDependencies(ctor: DI_TARGET_OBJ): { id: ServiceIdentifier<any>; index: number }[] {
20 return ctor[DI_DEPENDENCIES] || [];
21 }
23 > export interface DI_TARGET_OBJ extends Function {
24 > [DI_TARGET]: Function;
25 > [DI_DEPENDENCIES]: { id: ServiceIdentifier<any>; index: number }[];
26 > }
27 > }
28 >
29 > // --- interfaces ------
30 >
31 > export type BrandedService = { _serviceBrand: undefined };
32 >
33 > export interface IConstructorSignature<T, Args extends any[] = []> {
34 > new <Services extends BrandedService[]>(...args: [...Args, ...Services]): T;
35 > }
36 >
37 > export interface ServicesAccessor {
38 > get<T>(id: ServiceIdentifier<T>): T;
39 > }
40 >
41 > export const IInstantiationService = createDecorator<IInstantiationService>('instantiationService');
42 >
43 > /**
44 > * Given a list of arguments as a tuple, attempt to extract the leading, non-service arguments
45 > * to their own tuple.
46 > */
47 > export type GetLeadingNonServiceArgs<TArgs extends any[]> =
48 > TArgs extends [] ? []
49 > : TArgs extends [...infer TFirst, BrandedService] ? GetLeadingNonServiceArgs<TFirst>
50 > : TArgs;
51 >
52 > export interface IInstantiationService {
53 >
54 > readonly _serviceBrand: undefined;
55 >
56 > /**
57 > * Synchronously creates an instance that is denoted by the descriptor
58 > */
59 > createInstance<T>(descriptor: descriptors.SyncDescriptor0<T>): T;
60 > createInstance<Ctor extends new (...args: any[]) => unknown, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
61 >
62 > /**
63 > * Calls a function with a service accessor.
64 > */
65 > invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R;
66 >
67 > /**
68 > * Creates a child of this service which inherits all current services
69 > * and adds/overwrites the given services.
70 > *
71 > * NOTE that the returned child is `disposable` and should be disposed when not used
72 > * anymore. This will also dispose all the services that this service has created.
73 > */
74 > createChild(services: ServiceCollection, store?: DisposableStore): IInstantiationService;
75 >
76 > /**
77 > * Disposes this instantiation service.
78 > *
79 > * - Will dispose all services that this instantiation service has created.
80 > * - Will dispose all its children but not its parent.
81 > * - Will NOT dispose services-instances that this service has been created with
82 > * - Will NOT dispose consumer-instances this service has created
83 > */
84 > dispose(): void;
85 > }
86 >
87 >
88 > /**
89 > * Identifies a service of type `T`.
90 > */
91 > export interface ServiceIdentifier<T> {
92 > (...args: any[]): void;
93 > type: T;
94 > }
95 >
96 >
97 > function storeServiceDependency(id: ServiceIdentifier<unknown>, target: Function, index: number): void { instantiation.ts
98 > if ((target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] === target) {
99 (target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES].push({ id, index });
100 > } else { instantiation.ts
101 > (target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES] = [{ id, index }];
102 > (target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] = target;
103 > }
104 > }
106 > /**
107 > * The *only* valid way to create a {{ServiceIdentifier}}.
108 > */
109 > export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
110 >
111 > if (_util.serviceIds.has(serviceId)) {
112 return _util.serviceIds.get(serviceId)!;
113 }
115 > const id = function (target: Function, key: string, index: number) {
116 > if (arguments.length !== 3) { instantiation.ts
117 throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
118 }
119 > storeServiceDependency(id, target, index); instantiation.ts
120 > } as ServiceIdentifier<T>;
122 > id.toString = () => serviceId;
123 >
124 > _util.serviceIds.set(serviceId, id);
125 > return id;
126 > }
127 >
128 > export function refineServiceDecorator<T1, T extends T1>(serviceIdentifier: ServiceIdentifier<T1>): ServiceIdentifier<T> {
129 return <ServiceIdentifier<T>>serviceIdentifier;
130 }
src/vs/platform/telemetry/common/telemetry.ts 115 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- telemetry.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { createDecorator } from '../../instantiation/common/instantiation.js';
7 > import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from './gdprTypings.js';
8 >
9 > export const ITelemetryService = createDecorator<ITelemetryService>('telemetryService');
10 >
11 > export interface ITelemetryData {
12 > from?: string;
13 > target?: string;
14 > [key: string]: string | unknown | undefined;
15 > }
16 >
17 > export interface ITelemetryService {
18 >
19 > readonly _serviceBrand: undefined;
20 >
21 > readonly telemetryLevel: TelemetryLevel;
22 >
23 > readonly sessionId: string;
24 > readonly machineId: string;
25 > readonly sqmId: string;
26 > readonly devDeviceId: string;
27 > readonly firstSessionDate: string;
28 > readonly msftInternal?: boolean;
29 >
30 > /**
31 > * Whether error telemetry will get sent. If false, `publicLogError` will no-op.
32 > */
33 > readonly sendErrorTelemetry: boolean;
34 >
35 > /**
36 > * @deprecated Use publicLog2 and the typescript GDPR annotation where possible
37 > */
38 > publicLog(eventName: string, data?: ITelemetryData): void;
39 >
40 > /**
41 > * Sends a telemetry event that has been privacy approved.
42 > * Do not call this unless you have been given approval.
43 > */
44 > publicLog2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>): void;
45 >
46 > /**
47 > * @deprecated Use publicLogError2 and the typescript GDPR annotation where possible
48 > */
49 > publicLogError(errorEventName: string, data?: ITelemetryData): void;
50 >
51 > publicLogError2<E extends ClassifiedEvent<OmitMetadata<T>> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck<T, E>): void;
52 >
53 > setExperimentProperty(name: string, value: string): void;
54 >
55 > /**
56 > * Sets a common property that will be attached to all telemetry events.
57 > * Common properties are added after PII cleaning and cannot be overridden by event data.
58 > */
59 > setCommonProperty(name: string, value: string | boolean): void;
60 > }
61 >
62 > export function telemetryLevelEnabled(service: ITelemetryService, level: TelemetryLevel): boolean {
63 return service.telemetryLevel >= level;
64 }
66 > /**
67 > * Replaces `/` and `\` with `|` in model identifiers to prevent the
68 > * telemetry pipeline from redacting them as file paths.
69 > */
70 > export function escapeModelIdForTelemetry(modelId: string | undefined): string | undefined {
71 return modelId?.replace(/[\/\\]/g, '|');
72 }
74 > export interface ITelemetryEndpoint {
75 > id: string;
76 > aiKey: string;
77 > sendErrorTelemetry: boolean;
78 > }
79 >
80 > export const ICustomEndpointTelemetryService = createDecorator<ICustomEndpointTelemetryService>('customEndpointTelemetryService');
81 >
82 > export interface ICustomEndpointTelemetryService {
83 > readonly _serviceBrand: undefined;
84 >
85 > publicLog(endpoint: ITelemetryEndpoint, eventName: string, data?: ITelemetryData): void;
86 > publicLogError(endpoint: ITelemetryEndpoint, errorEventName: string, data?: ITelemetryData): void;
87 > }
88 >
89 > // Keys
90 > export const currentSessionDateStorageKey = 'telemetry.currentSessionDate';
91 > export const firstSessionDateStorageKey = 'telemetry.firstSessionDate';
92 > export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
93 > export const machineIdKey = 'telemetry.machineId';
94 > export const sqmIdKey = 'telemetry.sqmId';
95 > export const devDeviceIdKey = 'telemetry.devDeviceId';
96 >
97 > // Configuration Keys
98 > export const TELEMETRY_SECTION_ID = 'telemetry';
99 > export const TELEMETRY_SETTING_ID = 'telemetry.telemetryLevel';
100 > export const TELEMETRY_CRASH_REPORTER_SETTING_ID = 'telemetry.enableCrashReporter';
101 > export const TELEMETRY_OLD_SETTING_ID = 'telemetry.enableTelemetry';
102 >
103 > export const enum TelemetryLevel {
104 > NONE = 0,
105 > CRASH = 1,
106 > ERROR = 2,
107 > USAGE = 3
108 > }
109 >
110 > export const enum TelemetryConfiguration {
111 > OFF = 'off',
112 > CRASH = 'crash',
113 > ERROR = 'error',
114 > ON = 'all'
115 > }
116 >
117 > export interface ICommonProperties {
118 > [name: string]: string | boolean | undefined;
119 > }
src/vs/base/common/equals.ts 108 covered LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- equals.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as arrays from './arrays.js';
7 >
8 > /*
9 > * Each function in this file which offers an equality comparison, has an accompanying
10 > * `*C` variant which returns an EqualityComparer function.
11 > *
12 > * The `*C` variant allows for easier composition of equality comparers and improved type-inference.
13 > */
14 >
15 >
16 > /** Represents a function that decides if two values are equal. */
17 > export type EqualityComparer<T> = (a: T, b: T) => boolean;
18 >
19 > export interface IEquatable<T> {
20 > equals(other: T): boolean;
21 > }
22 >
23 > /**
24 > * Compares two items for equality using strict equality.
25 > */
26 > export function strictEquals<T>(a: T, b: T): boolean {
27 return a === b;
28 }
29 > equals.ts
30 > export function strictEqualsC<T>(): EqualityComparer<T> {
31 return (a, b) => a === b;
32 }
33 > equals.ts
34 > /**
35 > * Checks if the items of two arrays are equal.
36 > * By default, strict equality is used to compare elements, but a custom equality comparer can be provided.
37 > */
38 > export function arrayEquals<T>(a: readonly T[], b: readonly T[], itemEquals?: EqualityComparer<T>): boolean {
39 return arrays.equals(a, b, itemEquals ?? strictEquals);
40 }
41 > equals.ts
42 > /**
43 > * Checks if the items of two arrays are equal.
44 > * By default, strict equality is used to compare elements, but a custom equality comparer can be provided.
45 > */
46 > export function arrayEqualsC<T>(itemEquals?: EqualityComparer<T>): EqualityComparer<readonly T[]> {
47 return (a, b) => arrays.equals(a, b, itemEquals ?? strictEquals);
48 }
49 > equals.ts
50 > /**
51 > * Drills into arrays (items ordered) and objects (keys unordered) and uses strict equality on everything else.
52 > */
53 > export function structuralEquals<T>(a: T, b: T): boolean {
54 if (a === b) {
55 return true;
95 return false;
96 }
97 > equals.ts
98 > export function structuralEqualsC<T>(): EqualityComparer<T> {
99 return (a, b) => structuralEquals(a, b);
100 }
101 > equals.ts
102 > /**
103 > * `getStructuralKey(a) === getStructuralKey(b) <=> structuralEquals(a, b)`
104 > * (assuming that a and b are not cyclic structures and nothing extends globalThis Array).
105 > */
106 > export function getStructuralKey(t: unknown): string {
107 return JSON.stringify(toNormalizedJsonStructure(t));
108 }
109 > equals.ts
110 > let objectId = 0;
111 > const objIds = new WeakMap<object, number>();
112 >
113 function toNormalizedJsonStructure(t: unknown): unknown {
114 if (Array.isArray(t)) {
136 return t;
137 }
138 > equals.ts
139 >
140 > /**
141 > * Two items are considered equal, if their stringified representations are equal.
142 > */
143 > export function jsonStringifyEquals<T>(a: T, b: T): boolean {
144 return JSON.stringify(a) === JSON.stringify(b);
145 }
146 > equals.ts
147 > /**
148 > * Two items are considered equal, if their stringified representations are equal.
149 > */
150 > export function jsonStringifyEqualsC<T>(): EqualityComparer<T> {
151 return (a, b) => JSON.stringify(a) === JSON.stringify(b);
152 }
153 > equals.ts
154 > /**
155 > * Uses `item.equals(other)` to determine equality.
156 > */
157 > export function thisEqualsC<T extends IEquatable<T>>(): EqualityComparer<T> {
158 return (a, b) => a.equals(b);
159 }
160 > equals.ts
161 > /**
162 > * Checks if two items are both null or undefined, or are equal according to the provided equality comparer.
163 > */
164 > export function equalsIfDefined<T>(v1: T | undefined | null, v2: T | undefined | null, equals: EqualityComparer<T>): boolean {
165 if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) {
166 return v2 === v1;
168 return equals(v1, v2);
169 }
170 > equals.ts
171 > /**
172 > * Returns an equality comparer that checks if two items are both null or undefined, or are equal according to the provided equality comparer.
173 > */
174 > export function equalsIfDefinedC<T>(equals: EqualityComparer<T>): EqualityComparer<T | undefined | null> {
175 return (v1, v2) => {
176 if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) {
180 };
181 }
182 > equals.ts
183 > /**
184 > * Each function in this file which offers an equality comparison, has an accompanying
185 > * `*C` variant which returns an EqualityComparer function.
186 > *
187 > * The `*C` variant allows for easier composition of equality comparers and improved type-inference.
188 > */
189 > export namespace equals {
190 > export const strict = strictEquals;
191 > export const strictC = strictEqualsC;
192 >
193 > export const array = arrayEquals;
194 > export const arrayC = arrayEqualsC;
195 >
196 > export const structural = structuralEquals;
197 > export const structuralC = structuralEqualsC;
198 >
199 > export const jsonStringify = jsonStringifyEquals;
200 > export const jsonStringifyC = jsonStringifyEqualsC;
201 >
202 > export const thisC = thisEqualsC;
203 >
204 > export const ifDefined = equalsIfDefined;
205 > export const ifDefinedC = equalsIfDefinedC;
206 > }
src/vs/platform/agentHost/common/state/protocol/channels-changeset/commands.ts 105 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- commands.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI, ContentRef, StringOrMarkdown, TextRange } from '../common/state.js';
10 > import type { BaseParams } from '../common/commands.js';
11 >
12 > // ─── invokeChangesetOperation ────────────────────────────────────────────────
13 >
14 > /**
15 > * Discriminator for {@link ChangesetOperationTarget}. Mirrors the
16 > * non-`Changeset` members of {@link ChangesetOperationScope} — the
17 > * `Changeset` scope has no target.
18 > *
19 > * @category Commands
20 > */
21 > export const enum ChangesetOperationTargetKind {
22 > /** Operation acts on a single file. */
23 > Resource = 'resource',
24 > /** Operation acts on a line range within a single file. */
25 > Range = 'range',
26 > }
27 >
28 > /**
29 > * Identifies the file or range a {@link ChangesetOperation} should act on.
30 > *
31 > * The `kind` MUST match one of the operation's declared
32 > * {@link ChangesetOperation.scopes}.
33 > *
34 > * @category Commands
35 > */
36 > export type ChangesetOperationTarget =
37 > | { kind: ChangesetOperationTargetKind.Resource; resource: URI; side?: 'before' | 'after' }
38 > | { kind: ChangesetOperationTargetKind.Range; resource: URI; side?: 'before' | 'after'; range: TextRange };
39 >
40 > /**
41 > * Optional follow-up surfaced by the server after an operation completes —
42 > * a {@link ContentRef} the client can fetch and display.
43 > *
44 > * Set `external` to `true` to open the content in the user's preferred
45 > * external handler (e.g. browser); otherwise the client is expected to
46 > * surface it inline.
47 > *
48 > * @category Commands
49 > */
50 > export interface ChangesetOperationFollowUp {
51 > content: ContentRef;
52 > /** When `true`, open in an external handler rather than inline. */
53 > external?: boolean;
54 > }
55 >
56 > /**
57 > * Invokes a server-defined {@link ChangesetOperation} against a changeset,
58 > * a single file, or a line range.
59 > *
60 > * The server validates that `operationId` exists in the changeset's
61 > * current `operations` list and that the requested `target.kind` is
62 > * contained in the operation's `scopes`. Invalid combinations result in a
63 > * JSON-RPC error.
64 > *
65 > * State changes resulting from invocation flow back through the normal
66 > * `changeset/*` action stream on the relevant changeset URIs. Clients
67 > * SHOULD NOT synthesise local optimistic changes for invocations unless
68 > * the server explicitly opts in via a future capability.
69 > *
70 > * @category Commands
71 > * @method invokeChangesetOperation
72 > * @direction Client → Server
73 > * @messageType Request
74 > * @version 2
75 > */
76 > export interface InvokeChangesetOperationParams extends BaseParams {
77 > /** The expanded changeset URI. */
78 > channel: URI;
79 > /** Matches {@link ChangesetOperation.id} from the changeset's `operations` list. */
80 > operationId: string;
81 > /**
82 > * Target of the operation. Required iff the chosen scope is
83 > * `'resource'` or `'range'`. Omit for changeset-scoped operations.
84 > */
85 > target?: ChangesetOperationTarget;
86 > }
87 >
88 > /**
89 > * Result of the {@link InvokeChangesetOperationParams | `invokeChangesetOperation`}
90 > * command.
91 > *
92 > * Success is implicit: the server returns this result when it accepted
93 > * the operation. Failure is signalled by rejecting the JSON-RPC request
94 > * with an appropriate error code, not by any field on this result. The
95 > * operation MAY still produce subsequent failure feedback through the
96 > * {@link ChangesetStatusChangedAction | `changeset/statusChanged`} stream.
97 > *
98 > * @category Commands
99 > */
100 > export interface InvokeChangesetOperationResult {
101 > /** Optional human-readable message describing the result. */
102 > message?: StringOrMarkdown;
103 > /** Optional follow-up: a URI to open (e.g. a PR), a content ref, etc. */
104 > followUp?: ChangesetOperationFollowUp;
105 > }
src/vs/base/common/arraysFind.ts 95 covered LOC · 16 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- arraysFind.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Comparator } from './arrays.js';
7 >
8 > export function findLast<T, R extends T>(array: readonly T[], predicate: (item: T, index: number) => item is R, fromIndex?: number): R | undefined;
9 > export function findLast<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex?: number): T | undefined;
10 > export function findLast<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = array.length - 1): T | undefined {
11 const idx = findLastIdx(array, predicate, fromIndex);
12 if (idx === -1) {
15 return array[idx];
16 }
18 > export function findLastIdx<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = array.length - 1): number {
19 for (let i = fromIndex; i >= 0; i--) {
20 const element = array[i];
27 return -1;
28 }
30 > export function findFirst<T, R extends T>(array: readonly T[], predicate: (item: T, index: number) => item is R, fromIndex?: number): R | undefined;
31 > export function findFirst<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex?: number): T | undefined;
32 > export function findFirst<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = 0): T | undefined {
33 const idx = findFirstIdx(array, predicate, fromIndex);
34 if (idx === -1) {
37 return array[idx];
38 }
40 > export function findFirstIdx<T>(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = 0): number {
41 for (let i = fromIndex; i < array.length; i++) {
42 const element = array[i];
49 return -1;
50 }
52 > /**
53 > * Finds the last item where predicate is true using binary search.
54 > * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
55 > *
56 > * @returns `undefined` if no item matches, otherwise the last item that matches the predicate.
57 > */
58 > export function findLastMonotonous<T>(array: readonly T[], predicate: (item: T) => boolean): T | undefined {
59 const idx = findLastIdxMonotonous(array, predicate);
60 return idx === -1 ? undefined : array[idx];
61 }
63 > /**
64 > * Finds the last item where predicate is true using binary search.
65 > * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
66 > *
67 > * @returns `startIdx - 1` if predicate is false for all items, otherwise the index of the last item that matches the predicate.
68 > */
69 > export function findLastIdxMonotonous<T>(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number {
70 let i = startIdx;
71 let j = endIdxEx;
80 return i - 1;
81 }
83 > /**
84 > * Finds the first item where predicate is true using binary search.
85 > * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`!
86 > *
87 > * @returns `undefined` if no item matches, otherwise the first item that matches the predicate.
88 > */
89 > export function findFirstMonotonous<T>(array: readonly T[], predicate: (item: T) => boolean): T | undefined {
90 const idx = findFirstIdxMonotonousOrArrLen(array, predicate);
91 return idx === array.length ? undefined : array[idx];
92 }
94 > /**
95 > * Finds the first item where predicate is true using binary search.
96 > * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`!
97 > *
98 > * @returns `endIdxEx` if predicate is false for all items, otherwise the index of the first item that matches the predicate.
99 > */
100 > export function findFirstIdxMonotonousOrArrLen<T>(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number {
101 let i = startIdx;
102 let j = endIdxEx;
111 return i;
112 }
114 > export function findFirstIdxMonotonous<T>(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number {
115 const idx = findFirstIdxMonotonousOrArrLen(array, predicate, startIdx, endIdxEx);
116 return idx === array.length ? -1 : idx;
117 }
119 > /**
120 > * Use this when
121 > * * You have a sorted array
122 > * * You query this array with a monotonous predicate to find the last item that has a certain property.
123 > * * You query this array multiple times with monotonous predicates that get weaker and weaker.
124 > */
125 > export class MonotonousArray<T> {
126 > public static assertInvariants = false;
127 >
128 > private _findLastMonotonousLastIdx = 0;
129 > private _prevFindLastPredicate: ((item: T) => boolean) | undefined;
130 >
131 > constructor(private readonly _array: readonly T[]) {
132 }
134 > /**
135 > * The predicate must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
136 > * For subsequent calls, current predicate must be weaker than (or equal to) the previous predicate, i.e. more entries must be `true`.
137 > */
138 > findLastMonotonous(predicate: (item: T) => boolean): T | undefined {
139 if (MonotonousArray.assertInvariants) {
140 if (this._prevFindLastPredicate) {
152 return idx === -1 ? undefined : this._array[idx];
153 }
154 > } arraysFind.ts
155 >
156 > /**
157 > * Returns the first item that is equal to or greater than every other item.
158 > */
159 > export function findFirstMax<T>(array: readonly T[], comparator: Comparator<T>): T | undefined {
160 if (array.length === 0) {
161 return undefined;
171 return max;
172 }
174 > /**
175 > * Returns the last item that is equal to or greater than every other item.
176 > */
177 > export function findLastMax<T>(array: readonly T[], comparator: Comparator<T>): T | undefined {
178 if (array.length === 0) {
179 return undefined;
189 return max;
190 }
192 > /**
193 > * Returns the first item that is equal to or less than every other item.
194 > */
195 > export function findFirstMin<T>(array: readonly T[], comparator: Comparator<T>): T | undefined {
196 return findFirstMax(array, (a, b) => -comparator(a, b));
197 }
199 > export function findMaxIdx<T>(array: readonly T[], comparator: Comparator<T>): number {
200 if (array.length === 0) {
201 return -1;
211 return maxIdx;
212 }
214 > /**
215 > * Returns the first mapped value of the array which is not undefined.
216 > */
217 > export function mapFindFirst<T, R>(items: Iterable<T>, mapFn: (value: T) => R | undefined): R | undefined {
218 for (const value of items) {
219 const mapped = mapFn(value);
src/vs/base/test/common/virtualScheduling/virtualClock.ts 94 covered LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- virtualClock.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { compareBy, numberComparator, tieBreakComparators } from '../../../common/arrays.js';
7 > import { Emitter } from '../../../common/event.js';
8 > import { IDisposable } from '../../../common/lifecycle.js';
9 > import { Trace } from './trace.js';
10 >
11 > export type VirtualTime = number;
12 >
13 > /** Debug source description for an event. */
14 > export interface EventSource {
15 > toString(): string;
16 > readonly stackTrace?: string;
17 > }
18 >
19 > /**
20 > * A unit of work scheduled at a point in virtual time.
21 > *
22 > * Timer callbacks are events. External completions (e.g. fake fs reads) can
23 > * also be modelled as events whose virtual completion time is chosen by a
24 > * scheduling policy — to the {@link VirtualClock} they are indistinguishable.
25 > */
26 > export interface VirtualEvent {
27 > readonly time: VirtualTime;
28 > readonly source: EventSource;
29 > readonly trace?: Trace;
30 > /**
31 > * Hint for the {@link Embedding}: this event prefers to run on a real
32 > * animation frame (e.g. so DOM measurements after it observe a real
33 > * reflow). Pure-time tests can ignore the hint.
34 > */
35 > readonly preferRealAnimationFrame?: boolean;
36 > run(): void;
37 > }
38 >
39 > interface QueuedEvent extends VirtualEvent { readonly id: number }
40 >
41 > const eventComparator = tieBreakComparators<QueuedEvent>(
42 > compareBy(e => e.time, numberComparator),
43 > compareBy(e => e.id, numberComparator),
44 > );
45 >
46 > /**
47 > * A pure data structure: a virtual clock + a priority queue of events.
48 > *
49 > * The clock has no concept of "real time". It is advanced exclusively by
50 > * {@link runNext}, which sets `now` to the next event's `time` before running
51 > * it. The {@link VirtualTimeProcessor} is the only intended driver, but the
52 > * clock is useful in isolation (e.g. for unit-testing a scheduler or for
53 > * stepping a scenario manually).
54 > */
55 > export class VirtualClock {
56 > private _now: VirtualTime;
57 > private _idCounter = 0;
58 > private readonly _queue = new SimplePriorityQueue<QueuedEvent>(eventComparator);
59 > private readonly _onEventScheduled = new Emitter<VirtualEvent>();
60 >
61 > public readonly onEventScheduled = this._onEventScheduled.event;
62 >
63 > constructor(startTime: VirtualTime = 0) {
64 this._now = startTime;
65 }
67 > get now(): VirtualTime { return this._now; }
68 > get hasEvents(): boolean { return this._queue.length > 0; }
69 >
70 > schedule(event: VirtualEvent): IDisposable {
71 if (event.time < this._now) {
72 throw new Error(`Scheduled time (${event.time}) must be >= now (${this._now}).`);
77 return { dispose: () => this._queue.remove(queued) };
78 }
80 > peekNext(): VirtualEvent | undefined { return this._queue.getMin(); }
81 >
82 > runNext(): VirtualEvent | undefined {
83 const e = this._queue.removeMin();
84 if (e) {
88 return e;
89 }
91 > getEvents(): readonly VirtualEvent[] { return this._queue.toSortedArray(); }
92 > }
93 >
94 > class SimplePriorityQueue<T> {
95 > private _items: T[] = [];
96 > private _sorted = true;
97 >
98 > constructor(private readonly _compare: (a: T, b: T) => number) { }
99 >
100 > get length(): number { return this._items.length; }
101 >
102 > add(value: T): void {
103 this._items.push(value);
104 this._sorted = false;
105 }
107 > remove(value: T): void {
108 const i = this._items.indexOf(value);
109 if (i !== -1) { this._items.splice(i, 1); }
110 }
112 > getMin(): T | undefined { this._ensureSorted(); return this._items[0]; }
113 > removeMin(): T | undefined { this._ensureSorted(); return this._items.shift(); }
114 > toSortedArray(): T[] { this._ensureSorted(); return [...this._items]; }
115 >
116 > private _ensureSorted(): void {
117 if (this._sorted) { return; }
118 this._items.sort(this._compare);
119 this._sorted = true;
120 }
121 > } virtualClock.ts
src/vs/platform/agentHost/common/meta/agentToolCallMeta.ts 92 covered LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentToolCallMeta.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type { Mutable } from '../../../../base/common/types.js';
7 >
8 > /** Anything carrying a tool call's `_meta` bag (persisted state or wire actions). */
9 > interface IHasToolCallMeta {
10 > readonly _meta?: Record<string, unknown>;
11 > }
12 >
13 > /**
14 > * Well-known typed view over a tool call's open `_meta` bag. Producers and
15 > * consumers agree on these keys here so the two sides can't drift; always read
16 > * the bag through {@link readToolCallMeta}, which validates each field and drops
17 > * wrong-typed values.
18 > */
19 > export interface IToolCallMeta {
20 > /**
21 > * VS Code rendering hint. `terminal` routes the call to the command/output
22 > * renderer, `subagent` to the subagent UI, `search` to the search renderer;
23 > * everything else falls through to the generic invocation renderer. Set by
24 > * the agent adapter, never matched on raw tool name by the renderer.
25 > */
26 > readonly toolKind?: ToolKind;
27 > /** Shell language for a `terminal` tool call (drives syntax highlighting). */
28 > readonly language?: string;
29 > /** Short task description for a `subagent` tool call (e.g. "Find related files"). */
30 > readonly subagentDescription?: string;
31 > /** Agent name for a `subagent` tool call (e.g. "explore"). */
32 > readonly subagentAgentName?: string;
33 > /** Chat URI of the subagent this tool call spawns, stamped by the host (see {@link buildSubagentChatUri}); the resource may not be registered yet. */
34 > readonly subagentChatUri?: string;
35 > /** Raw, pre-stringified tool arguments captured for display/debugging. */
36 > readonly toolArguments?: unknown;
37 > /** Originating MCP server name, when the call came from an MCP server. */
38 > readonly mcpServerName?: string;
39 > /** Originating MCP tool name, when the call came from an MCP server. */
40 > readonly mcpToolName?: string;
41 > /** MCP App render data, when the call exposes an interactive App surface. */
42 > readonly ui?: IToolCallUiMeta;
43 > /**
44 > * Set by the host's side-effect layer when the call was auto-approved
45 > * because of an `autoApprove` session-config setting (rather than an
46 > * explicit user action), so the client can render it as setting-driven.
47 > */
48 > readonly autoApproveBySetting?: boolean;
49 > /** Transient runtime corpus for the local client tool-search invocation. */
50 > readonly toolSearchCandidates?: readonly IToolSearchCandidate[];
51 > }
52 >
53 > /** Minimal metadata needed to embed and rank a deferred tool. */
54 > export interface IToolSearchCandidate {
55 > readonly name: string;
56 > readonly description: string;
57 > }
58 >
59 > /**
60 > * The set of VS Code-recognized tool-call rendering kinds. Add a new value here
61 > * (and teach the renderer to handle it) rather than matching on tool name.
62 > */
63 > export type ToolKind = 'terminal' | 'subagent' | 'search';
64 >
65 > /**
66 > * MCP App render data carried under {@link IToolCallMeta.ui}. Clients gate
67 > * mounting the App webview on both a `resourceUri` and a `channel` being
68 > * present.
69 > */
70 > export interface IToolCallUiMeta {
71 > /** The MCP App's UI resource URI (an `ui://` resource the App renders). */
72 > readonly resourceUri: string;
73 > /** AHP `mcp://` channel the App's sub-RPCs route back through, when ready. */
74 > readonly channel?: string;
75 > }
76 >
77 function isToolKind(value: unknown): value is ToolKind {
78 return value === 'terminal' || value === 'subagent' || value === 'search';
79 }
81 function readToolCallUiMeta(value: unknown): IToolCallUiMeta | undefined {
82 if (!value || typeof value !== 'object' || Array.isArray(value)) {
93 return result;
94 }
96 function readToolSearchCandidates(value: unknown): readonly IToolSearchCandidate[] | undefined {
97 if (!Array.isArray(value)) {
114 return result;
115 }
117 > /**
118 > * Reads the well-known {@link IToolCallMeta} keys from a tool call's `_meta`
119 > * bag, dropping unknown keys and wrong-typed values.
120 > */
121 > export function readToolCallMeta(source: IHasToolCallMeta): IToolCallMeta {
122 const meta = source._meta;
123 if (!meta) {
140 return result;
141 }
143 > /**
144 > * Serializes a typed {@link IToolCallMeta} into the `_meta` record, dropping
145 > * `undefined` entries and returning `undefined` when empty. Build a tool call's
146 > * `_meta` through this so producers stay in lock-step with
147 > * {@link readToolCallMeta}.
148 > */
149 > export function toToolCallMeta(meta: IToolCallMeta): Record<string, unknown> | undefined {
150 const result: Record<string, unknown> = {};
151 for (const [key, value] of Object.entries(meta)) {
src/vs/base/common/extpath.ts 91 covered LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extpath.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from './charCode.js';
7 > import { isAbsolute, join, normalize, posix, sep } from './path.js';
8 > import { isWindows } from './platform.js';
9 > import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings.js';
10 > import { isNumber } from './types.js';
11 >
12 > export function isPathSeparator(code: number) {
13 return code === CharCode.Slash || code === CharCode.Backslash;
14 }
15 > extpath.ts
16 > /**
17 > * Takes a Windows OS path and changes backward slashes to forward slashes.
18 > * This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
19 > * Using it on a Linux or MaxOS path might change it.
20 > */
21 > export function toSlashes(osPath: string) {
22 return osPath.replace(/[\\/]/g, posix.sep);
23 }
24 > extpath.ts
25 > /**
26 > * Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:
27 > * - turns backward slashes into forward slashes
28 > * - makes it absolute if it starts with a drive letter
29 > * This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
30 > * Using it on a Linux or MaxOS path might change it.
31 > */
32 > export function toPosixPath(osPath: string) {
33 if (osPath.indexOf('/') === -1) {
34 osPath = toSlashes(osPath);
39 return osPath;
40 }
41 > extpath.ts
42 > /**
43 > * Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
44 > * `getRoot('files:///files/path') === files:///`,
45 > * or `getRoot('\\server\shares\path') === \\server\shares\`
46 > */
47 > export function getRoot(path: string, sep: string = posix.sep): string {
48 if (!path) {
49 return '';
111 return '';
112 }
113 > extpath.ts
114 > /**
115 > * Check if the path follows this pattern: `\\hostname\sharename`.
116 > *
117 > * @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
118 > * @return A boolean indication if the path is a UNC path, on none-windows
119 > * always false.
120 > */
121 > export function isUNC(path: string): boolean {
122 if (!isWindows) {
123 // UNC is a windows concept
162 return true;
163 }
164 > extpath.ts
165 > // Reference: https://en.wikipedia.org/wiki/Filename
166 > const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
167 > const UNIX_INVALID_FILE_CHARS = /[/]/g;
168 > const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;
169 > export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {
170 const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;
171
201 return true;
202 }
203 > extpath.ts
204 > /**
205 > * @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are
206 > * in a context without services, consider to pass down the `extUri` from the outside
207 > * or use `extUriBiasedIgnorePathCase` if you know what you are doing.
208 > */
209 > export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {
210 const identityEquals = (pathA === pathB);
211 if (!ignoreCase || identityEquals) {
219 return equalsIgnoreCase(pathA, pathB);
220 }
221 > extpath.ts
222 > /**
223 > * @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If
224 > * you are in a context without services, consider to pass down the `extUri` from the
225 > * outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.
226 > */
227 > export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, forcePosixSemantics = false): boolean {
228 const separator = forcePosixSemantics ? posix.sep : sep;
229
272 return base.indexOf(parentCandidate) === 0;
273 }
274 > extpath.ts
275 > export function isWindowsDriveLetter(char0: number): boolean {
276 return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
277 }
278 > extpath.ts
279 > export function sanitizeFilePath(candidate: string, cwd: string): string {
280
281 // Special case: allow to open a drive letter without trailing backslash
295 return removeTrailingPathSeparator(candidate);
296 }
297 > extpath.ts
298 > export function removeTrailingPathSeparator(candidate: string): string {
299 if (isWindows) {
300 candidate = rtrim(candidate, sep);
316 return candidate;
317 }
318 > extpath.ts
319 > export function isRootOrDriveLetter(path: string): boolean {
320 const pathNormalized = normalize(path);
321
331 return pathNormalized === posix.sep;
332 }
333 > extpath.ts
334 > export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {
335 if (isWindowsOS) {
336 return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon;
339 return false;
340 }
341 > extpath.ts
342 > export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined {
343 return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined;
344 }
345 > extpath.ts
346 > export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
347 if (candidate.length > path.length) {
348 return -1;
360 return path.indexOf(candidate);
361 }
362 > extpath.ts
363 > export interface IPathWithLineAndColumn {
364 > path: string;
365 > line?: number;
366 > column?: number;
367 > }
368 >
369 > export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
370 const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
371
395 };
396 }
397 > extpath.ts
398 > const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
399 > const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789';
400 >
401 > export function randomPath(parent?: string, prefix?: string, randomLength = 8): string {
402 let suffix = '';
403 for (let i = 0; i < randomLength; i++) {
src/vs/base/common/cancellation.ts 90 covered LOC · 18 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- cancellation.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Emitter, Event } from './event.js';
7 > import { DisposableStore, IDisposable } from './lifecycle.js';
8 >
9 > export interface CancellationToken {
10 >
11 > /**
12 > * A flag signalling is cancellation has been requested.
13 > */
14 > readonly isCancellationRequested: boolean;
15 >
16 > /**
17 > * An event which fires when cancellation is requested. This event
18 > * only ever fires `once` as cancellation can only happen once. Listeners
19 > * that are registered after cancellation will be called (next event loop run),
20 > * but also only once.
21 > *
22 > * @event
23 > */
24 > readonly onCancellationRequested: (listener: (e: void) => unknown, thisArgs?: unknown, disposables?: IDisposable[]) => IDisposable;
25 > }
26 >
27 > const shortcutEvent: Event<void> = Object.freeze(function (callback, context?): IDisposable {
28 const handle = setTimeout(callback.bind(context), 0);
29 return { dispose() { clearTimeout(handle); } };
30 });
32 > export namespace CancellationToken {
33 >
34 > export function isCancellationToken(thing: unknown): thing is CancellationToken {
35 if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) {
36 return true;
45 && typeof (thing as CancellationToken).onCancellationRequested === 'function';
46 }
48 >
49 > export const None = Object.freeze<CancellationToken>({
50 > isCancellationRequested: false,
51 > onCancellationRequested: Event.None
52 > });
53 >
54 > export const Cancelled = Object.freeze<CancellationToken>({
55 > isCancellationRequested: true,
56 > onCancellationRequested: shortcutEvent
57 > });
58 > }
59 >
60 class MutableToken implements CancellationToken {
61
62 private _isCancelled: boolean = false;
63 private _emitter: Emitter<void> | null = null;
65 > public cancel() {
66 if (!this._isCancelled) {
67 this._isCancelled = true;
72 }
73 }
75 > get isCancellationRequested(): boolean {
76 return this._isCancelled;
77 }
79 > get onCancellationRequested(): Event<void> {
80 if (this._isCancelled) {
81 return shortcutEvent;
86 return this._emitter.event;
87 }
89 > public dispose(): void {
90 if (this._emitter) {
91 this._emitter.dispose();
93 }
94 }
96 >
97 > export class CancellationTokenSource {
98 >
99 > private _token?: CancellationToken = undefined;
100 > private _parentListener?: IDisposable = undefined;
101 >
102 > constructor(parent?: CancellationToken) {
103 this._parentListener = parent && parent.onCancellationRequested(this.cancel, this);
104 }
106 > get token(): CancellationToken {
107 if (!this._token) {
108 // be lazy and create the token only when
112 return this._token;
113 }
115 > cancel(): void {
116 if (!this._token) {
117 // save an object by returning the default
125 }
126 }
128 > dispose(cancel: boolean = false): void {
129 if (cancel) {
130 this.cancel();
140 }
141 }
142 > } cancellation.ts
143 >
144 > export function cancelOnDispose(store: DisposableStore): CancellationToken {
145 const source = new CancellationTokenSource();
146 store.add({ dispose() { source.cancel(); } });
147 return source.token;
148 }
150 > /**
151 > * A pool that aggregates multiple cancellation tokens. The pool's own token
152 > * (accessible via `pool.token`) is cancelled only after every token added
153 > * to the pool has been cancelled. Adding tokens after the pool token has
154 > * been cancelled has no effect.
155 > */
156 > export class CancellationTokenPool {
157
158 private readonly _source = new CancellationTokenSource();
162 private _cancelled: number = 0;
163 private _isDone: boolean = false;
165 > get token(): CancellationToken {
166 return this._source.token;
167 }
169 > /**
170 > * Add a token to the pool. If the token is already cancelled it is counted
171 > * immediately. Tokens added after the pool token has been cancelled are ignored.
172 > */
173 > add(token: CancellationToken): void {
174 if (this._isDone) {
175 return;
191 this._listeners.add(d);
192 }
194 > private _check(): void {
195 if (!this._isDone && this._total > 0 && this._total === this._cancelled) {
196 this._isDone = true;
src/vs/base/common/cache.ts 88 covered LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- cache.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken, CancellationTokenSource } from './cancellation.js';
7 > import { IDisposable } from './lifecycle.js';
8 >
9 > export interface CacheResult<T> extends IDisposable {
10 > promise: Promise<T>;
11 > }
12 >
13 > export class Cache<T> {
14 >
15 > private result: CacheResult<T> | null = null;
16 > constructor(private task: (ct: CancellationToken) => Promise<T>) { }
17 >
18 > get(): CacheResult<T> {
19 if (this.result) {
20 return this.result;
35 return this.result;
36 }
37 > } cache.ts
38 >
39 > export function identity<T>(t: T): T {
40 return t;
41 }
42 > cache.ts
43 > interface ICacheOptions<TArg> {
44 > /**
45 > * The cache key is used to identify the cache entry.
46 > * Strict equality is used to compare cache keys.
47 > */
48 > getCacheKey: (arg: TArg) => unknown;
49 > }
50 >
51 > /**
52 > * Uses a LRU cache to make a given parametrized function cached.
53 > * Caches just the last key/value.
54 > */
55 > export class LRUCachedFunction<TArg, TComputed> {
56 > private lastCache: TComputed | undefined = undefined;
57 > private lastArgKey: unknown | undefined = undefined;
58 >
59 > private readonly _fn: (arg: TArg) => TComputed;
60 > private readonly _computeKey: (arg: TArg) => unknown;
61 >
62 > constructor(fn: (arg: TArg) => TComputed);
63 > constructor(options: ICacheOptions<TArg>, fn: (arg: TArg) => TComputed);
64 > constructor(arg1: ICacheOptions<TArg> | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) {
65 > if (typeof arg1 === 'function') {
66 > this._fn = arg1;
67 > this._computeKey = identity;
68 > } else {
69 this._fn = arg2!;
70 this._computeKey = arg1.getCacheKey;
71 }
72 > } cache.ts
73 >
74 > public get(arg: TArg): TComputed {
75 const key = this._computeKey(arg);
76 if (this.lastArgKey !== key) {
80 return this.lastCache!;
81 }
82 > } cache.ts
83 >
84 > /**
85 > * Uses an unbounded cache to memoize the results of the given function.
86 > */
87 > export class CachedFunction<TArg, TComputed> {
88 > private readonly _map = new Map<TArg, TComputed>();
89 > private readonly _map2 = new Map<unknown, TComputed>();
90 > public get cachedValues(): ReadonlyMap<TArg, TComputed> {
91 > return this._map;
92 > }
93 >
94 > private readonly _fn: (arg: TArg) => TComputed;
95 > private readonly _computeKey: (arg: TArg) => unknown;
96 >
97 > constructor(fn: (arg: TArg) => TComputed);
98 > constructor(options: ICacheOptions<TArg>, fn: (arg: TArg) => TComputed);
99 > constructor(arg1: ICacheOptions<TArg> | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) {
100 if (typeof arg1 === 'function') {
101 this._fn = arg1;
106 }
107 }
108 > cache.ts
109 > public get(arg: TArg): TComputed {
110 const key = this._computeKey(arg);
111 if (this._map2.has(key)) {
118 return value;
119 }
120 > } cache.ts
121 >
122 > /**
123 > * Uses an unbounded cache to memoize the results of the given function.
124 > */
125 > export class WeakCachedFunction<TArg, TComputed> {
126 > private readonly _map = new WeakMap<WeakKey, TComputed>();
127 >
128 > private readonly _fn: (arg: TArg) => TComputed;
129 > private readonly _computeKey: (arg: TArg) => unknown;
130 >
131 > constructor(fn: (arg: TArg) => TComputed);
132 > constructor(options: ICacheOptions<TArg>, fn: (arg: TArg) => TComputed);
133 > constructor(arg1: ICacheOptions<TArg> | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) {
134 if (typeof arg1 === 'function') {
135 this._fn = arg1;
140 }
141 }
142 > cache.ts
143 > public get(arg: TArg): TComputed {
144 const key = this._computeKey(arg) as WeakKey;
145 if (this._map.has(key)) {
151 return value;
152 }
153 > } cache.ts
src/vs/platform/agentHost/node/agentHostChangesetStateCache.ts 78 covered LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentHostChangesetStateCache.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { LinkedMap, Touch } from '../../../base/common/map.js';
7 > import { ChangesetStatus, type ChangesetState, type URI } from '../common/state/sessionState.js';
8 >
9 > /**
10 > * Default number of expanded changeset states kept hot in memory.
11 > *
12 > * This cache only stores the subscribable `ChangesetState` payloads. The
13 > * lightweight catalogue on `SessionSummary.changesets` remains on the session
14 > * summary, and static changesets can be rehydrated from persisted metadata or
15 > * recomputed on demand. The limit is intentionally a soft cap: subscribed or
16 > * actively-computing changesets may pin the cache above this value until they
17 > * become evictable.
18 > */
19 > const DEFAULT_CHANGESET_STATE_SOFT_LIMIT = 500;
20 >
21 > export interface IAgentHostChangesetStateRetentionOptions {
22 > /**
23 > * Number of expanded changeset states kept hot in memory. The limit is soft:
24 > * entries for which {@link canEvict} returns false may temporarily keep the
25 > * cache above this value.
26 > */
27 > readonly softLimit?: number;
28 >
29 > /**
30 > * Returns whether a changeset state can be silently evicted from the cache.
31 > * Production callers should provide this from `AgentService`, which owns
32 > * protocol subscription refcounts and can ask the changeset service about
33 > * active producers. Return false for changesets that are subscribed or have
34 > * an active producer that may still publish into the changeset URI.
35 > */
36 > readonly canEvict?: (changeset: URI) => boolean;
37 > }
38 >
39 > /**
40 > * Owns the memory policy for expanded changeset states.
41 > *
42 > * The state manager owns protocol sequencing and reducer application; this
43 > * helper owns the cache mechanics needed to keep dormant changesets bounded.
44 > * Eviction here is deliberately silent: protocol-visible teardown still goes
45 > * through `AgentHostStateManager.disposeChangeset`, which emits
46 > * `ChangesetCleared` before removing state.
47 > */
48 > export class AgentHostChangesetStateCache {
49 >
50 > private readonly _states = new Map<string, ChangesetState>();
51 > private readonly _lru = new LinkedMap<string, true>();
52 > private readonly _softLimit: number;
53 > private readonly _canEvict: (changeset: URI) => boolean;
54 >
55 > constructor(options: IAgentHostChangesetStateRetentionOptions = {}) {
56 > this._softLimit = Math.max(0, options.softLimit ?? DEFAULT_CHANGESET_STATE_SOFT_LIMIT); agentHostChangesetStateCache.ts
57 > this._canEvict = options.canEvict ?? (() => true);
58 > }
60 > keys(): IterableIterator<string> {
61 return this._states.keys();
62 }
64 > has(changeset: URI): boolean {
65 return this._states.has(changeset);
66 }
68 > get(changeset: URI): ChangesetState | undefined {
69 this._touch(changeset);
70 return this._states.get(changeset);
71 }
73 > set(changeset: URI, state: ChangesetState): void {
74 this._states.set(changeset, state);
75 this._touch(changeset);
76 this._evictIfOverLimit();
77 }
79 > delete(changeset: URI): void {
80 this._states.delete(changeset);
81 this._lru.delete(changeset);
82 }
84 > register(changeset: URI, initialStatus: ChangesetStatus = ChangesetStatus.Computing): void {
85 if (this._states.has(changeset)) {
86 this._touch(changeset);
89 this.set(changeset, { status: initialStatus, files: [] });
90 }
92 > /** Re-runs eviction after external liveness changes, such as unsubscribe or compute completion. */
93 > trimEvictableEntries(): void {
94 this._evictIfOverLimit();
95 }
97 > private _touch(changeset: URI): void {
98 if (this._states.has(changeset)) {
99 this._lru.set(changeset, true, Touch.AsNew);
100 }
101 }
103 > private _evictIfOverLimit(): void {
104 if (this._softLimit === 0) {
105 for (const changeset of [...this._lru.keys()]) {
src/vs/base/test/common/virtualScheduling/embedding.ts 76 covered LOC · 2 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- embedding.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { setTimeout0, setTimeout0IsFaster } from '../../../common/platform.js';
7 > import { TimeApi } from './timeApi.js';
8 > import { VirtualEvent } from './virtualClock.js';
9 >
10 > /**
11 > * # The processor/host embedding
12 > *
13 > * An {@link Embedding} is the contract between the processor's pure state
14 > * machine and the host event loop. It is invoked once per virtual step that
15 > * produced progress, and decides *how* the processor reaches the host before
16 > * the next step.
17 > *
18 > * ## Contract
19 > *
20 > * On each invocation the embedding MUST do exactly one of:
21 > *
22 > * 1. Return `'continueSync'` **without** calling `then`. The processor will
23 > * loop in place on the same host stack frame.
24 > *
25 > * 2. Schedule `then` on a host primitive (microtask, macrotask, paint frame)
26 > * and return `'cbScheduled'`. The processor will return and wait for the
27 > * callback to re-enter the trampoline.
28 > *
29 > * The embedding MUST NOT call `then` synchronously and also return
30 > * `'cbScheduled'` (that would re-enter the trampoline before this call
31 > * completed). Likewise, returning `'continueSync'` while having scheduled
32 > * `then` async would cause `then` to fire after the trampoline already
33 > * looped — also a bug.
34 > *
35 > * ## Why a callback contract instead of async/await
36 > *
37 > * Every `await` is an implicit microtask hop. For code whose job is to
38 > * decide host hops, that's the wrong abstraction: the reader has to mentally
39 > * compile the `await` to a boundary. With this contract, every host hop is
40 > * a named call to a single primitive (`api.setTimeout`, `setTimeout0`,
41 > * `api.requestAnimationFrame`, …) at exactly one site in this file.
42 > */
43 > export type Embedding = (
44 > nextEvent: VirtualEvent,
45 > then: () => void,
46 > ) => 'continueSync' | 'cbScheduled';
47 >
48 > /**
49 > * Tasks never schedule via promise chains. The processor runs virtual events
50 > * back-to-back on a single host stack frame — fastest possible, but starves
51 > * the host event loop for the duration of the run.
52 > *
53 > * Use only for tests where no `await` / `.then` chains are involved between
54 > * scheduling and execution of virtual events.
55 > */
56 > export const syncEmbedding: Embedding = () => 'continueSync';
57 >
58 > /**
59 > * Tasks may schedule via `await` / `.then`. Between virtual events, yield to
60 > * the host so the *microtask closure* — the current microtask plus every
61 > * microtask it transitively enqueues — drains before the next event runs.
62 > *
63 > * This is the embedding to use for almost all integration-style tests.
64 > */
65 > export function drainMicrotasksEmbedding(realApi: TimeApi): Embedding {
66 return (next, then) => {
67 if (next.preferRealAnimationFrame && realApi.requestAnimationFrame) {
73 };
74 }
76 > /**
77 > * Schedule `cb` after the closure of the current microtask queue: `cb`
78 > * fires only after the current microtask AND every microtask it
79 > * (recursively, transitively) enqueues has settled.
80 > *
81 > * Per the HTML spec, a macrotask runs only when the microtask queue is
82 > * empty, so any macrotask primitive achieves this. We pick the fastest
83 > * one available on the host.
84 > */
85 > export function nextMacrotask(api: TimeApi, cb: () => void): void {
86 if (setTimeout0IsFaster) { setTimeout0(cb); return; }
87 if (api.setImmediate) { api.setImmediate(cb); return; }
src/vs/base/common/collections.ts 74 covered LOC · 18 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- collections.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > /**
7 > * An interface for a JavaScript object that
8 > * acts a dictionary. The keys are strings.
9 > */
10 > export type IStringDictionary<V> = Record<string, V>;
11 >
12 > /**
13 > * An interface for a JavaScript object that
14 > * acts a dictionary. The keys are numbers.
15 > */
16 > export type INumberDictionary<V> = Record<number, V>;
17 >
18 > /**
19 > * Groups the collection into a dictionary based on the provided
20 > * group function.
21 > */
22 > export function groupBy<K extends string | number | symbol, V>(data: readonly V[], groupFn: (element: V) => K): Partial<Record<K, V[]>> {
23 const result: Partial<Record<K, V[]>> = Object.create(null);
24 for (const element of data) {
32 return result;
33 }
35 > export function groupByMap<K, V>(data: V[], groupFn: (element: V) => K): Map<K, V[]> {
36 const result = new Map<K, V[]>();
37 for (const element of data) {
46 return result;
47 }
49 > export function diffSets<T>(before: ReadonlySet<T>, after: ReadonlySet<T>): { removed: T[]; added: T[] } {
50 const removed: T[] = [];
51 const added: T[] = [];
62 return { removed, added };
63 }
65 > /**
66 > * Checks whether two sets contain exactly the same elements.
67 > *
68 > * @param a - The first set.
69 > * @param b - The second set.
70 > * @returns `true` if both sets have the same size and every element of `a` is also in `b`.
71 > */
72 > export function equalSets<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): boolean {
73 if (a === b) {
74 return true;
84 return true;
85 }
87 > export function diffMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[]; added: V[] } {
88 const removed: V[] = [];
89 const added: V[] = [];
100 return { removed, added };
101 }
103 > /**
104 > * Computes the intersection of two sets.
105 > *
106 > * @param setA - The first set.
107 > * @param setB - The second iterable.
108 > * @returns A new set containing the elements that are in both `setA` and `setB`.
109 > */
110 > export function intersection<T>(setA: Set<T>, setB: Iterable<T>): Set<T> {
111 const result = new Set<T>();
112 for (const elem of setB) {
117 return result;
118 }
120 > export class SetWithKey<T> implements Set<T> {
121 > private _map = new Map<unknown, T>();
122 >
123 > constructor(values: T[], private toKey: (t: T) => unknown) {
124 for (const value of values) {
125 this.add(value);
126 }
127 }
129 > get size(): number {
130 return this._map.size;
131 }
133 > add(value: T): this {
134 const key = this.toKey(value);
135 this._map.set(key, value);
136 return this;
137 }
139 > delete(value: T): boolean {
140 return this._map.delete(this.toKey(value));
141 }
143 > has(value: T): boolean {
144 return this._map.has(this.toKey(value));
145 }
147 > *entries(): SetIterator<[T, T]> {
148 for (const entry of this._map.values()) {
149 yield [entry, entry];
150 }
151 }
153 > keys(): SetIterator<T> {
154 return this.values();
155 }
157 > *values(): SetIterator<T> {
158 for (const entry of this._map.values()) {
159 yield entry;
160 }
161 }
163 > clear(): void {
164 this._map.clear();
165 }
167 > forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: unknown): void {
168 this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this));
169 }
171 > [Symbol.iterator](): SetIterator<T> {
172 return this.values();
173 }
175 > [Symbol.toStringTag]: string = 'SetWithKey';
176 > }
src/vs/base/common/hash.ts 74 covered LOC · 17 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- hash.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { encodeHex, VSBuffer } from './buffer.js';
7 > import * as strings from './strings.js';
8 >
9 > type NotSyncHashable = ArrayBufferLike | ArrayBufferView;
10 >
11 > /**
12 > * Return a hash value for an object.
13 > *
14 > * Note that this should not be used for binary data types. Instead,
15 > * prefer {@link hashAsync}.
16 > */
17 > export function hash<T>(obj: T extends NotSyncHashable ? never : T): number {
18 return doHash(obj, 0);
19 }
20 > hash.ts
21 > export function doHash(obj: unknown, hashVal: number): number {
22 switch (typeof obj) {
23 case 'object':
40 }
41 }
42 > hash.ts
43 > export function numberHash(val: number, initialHashVal: number): number {
44 return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
45 }
46 > hash.ts
47 function booleanHash(b: boolean, initialHashVal: number): number {
48 return numberHash(b ? 433 : 863, initialHashVal);
49 }
50 > hash.ts
51 > export function stringHash(s: string, hashVal: number) {
52 hashVal = numberHash(149417, hashVal);
53 for (let i = 0, length = s.length; i < length; i++) {
56 return hashVal;
57 }
58 > hash.ts
59 function arrayHash(arr: unknown[], initialHashVal: number): number {
60 initialHashVal = numberHash(104579, initialHashVal);
61 return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);
62 }
63 > hash.ts
64 function objectHash(obj: object, initialHashVal: number): number {
65 initialHashVal = numberHash(181387, initialHashVal);
69 }, initialHashVal);
70 }
71 > hash.ts
72 >
73 >
74 > /** Hashes the input as SHA-1, returning a hex-encoded string. */
75 > export const hashAsync = (input: string | ArrayBufferView | VSBuffer) => {
76 // Note: I would very much like to expose a streaming interface for hashing
77 // generally, but this is not available in web crypto yet, see
96 return crypto.subtle.digest('sha-1', buff as ArrayBufferView<ArrayBuffer>).then(toHexString); // CodeQL [SM04514] we use sha1 here for validating old stored client state, not for security
97 };
98 > hash.ts
99 > const enum SHA1Constant {
100 > BLOCK_SIZE = 64, // 512 / 8
101 > UNICODE_REPLACEMENT = 0xFFFD,
102 > }
103 >
104 function leftRotate(value: number, bits: number, totalBits: number = 32): number {
105 // delta + bits = totalBits
112 return ((value << bits) | ((mask & value) >>> delta)) >>> 0;
113 }
114 > hash.ts
115 > function toHexString(buffer: ArrayBuffer): string;
116 > function toHexString(value: number, bitsize?: number): string;
117 function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {
118 if (bufferOrValue instanceof ArrayBuffer) {
122 return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');
123 }
124 > hash.ts
125 > /**
126 > * A SHA1 implementation that works with strings and does not allocate.
127 > *
128 > * Prefer to use {@link hashAsync} in async contexts
129 > */
130 > export class StringSHA1 {
131 > private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320
132 >
133 > private _h0 = 0x67452301;
134 > private _h1 = 0xEFCDAB89;
135 > private _h2 = 0x98BADCFE;
136 > private _h3 = 0x10325476;
137 > private _h4 = 0xC3D2E1F0;
138 >
139 > private readonly _buff: Uint8Array;
140 > private readonly _buffDV: DataView;
141 > private _buffLen: number;
142 > private _totalLen: number;
143 > private _leftoverHighSurrogate: number;
144 > private _finished: boolean;
145 >
146 > constructor() {
147 this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */);
148 this._buffDV = new DataView(this._buff.buffer);
152 this._finished = false;
153 }
154 > hash.ts
155 > public update(str: string): void {
156 const strLen = str.length;
157 if (strLen === 0) {
208 this._leftoverHighSurrogate = leftoverHighSurrogate;
209 }
210 > hash.ts
211 > private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {
212 if (codePoint < 0x0080) {
213 buff[buffLen++] = codePoint;
238 return buffLen;
239 }
240 > hash.ts
241 > public digest(): string {
242 if (!this._finished) {
243 this._finished = true;
253 return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4);
254 }
255 > hash.ts
256 > private _wrapUp(): void {
257 this._buff[this._buffLen++] = 0x80;
258 this._buff.subarray(this._buffLen).fill(0);
271 this._step();
272 }
273 > hash.ts
274 > private _step(): void {
275 const bigBlock32 = StringSHA1._bigBlock32;
276 const data = this._buffDV;
322 this._h4 = (this._h4 + e) & 0xffffffff;
323 }
324 > } hash.ts
src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/state.ts 73 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from '../common/state.js';
10 >
11 > // ─── Resource Watch Types ────────────────────────────────────────────────────
12 >
13 > /**
14 > * Full state for a single resource watch, returned when a client subscribes
15 > * to an `ahp-resource-watch:` URI.
16 > *
17 > * Watches are otherwise stateless: the watcher exists to deliver
18 > * {@link ResourceWatchChangedAction} events. The state carries only the
19 > * descriptor of what is being watched so a re-subscribing client can
20 > * recover the watch configuration after reconnecting.
21 > *
22 > * @category Resource Watch Types
23 > */
24 > export interface ResourceWatchState {
25 > /**
26 > * The URI being watched. For recursive watches this is the root of the
27 > * subtree; for non-recursive watches this is the single file or
28 > * directory.
29 > */
30 > root: URI;
31 > /**
32 > * `true` if the watcher reports changes for descendants of `root`;
33 > * `false` if it only reports changes to `root` itself (and, when
34 > * `root` is a directory, its direct children).
35 > */
36 > recursive: boolean;
37 > /**
38 > * Optional glob patterns or paths relative to `root` to exclude from
39 > * change reporting.
40 > */
41 > excludes?: { items: string[] };
42 > /**
43 > * Optional glob patterns or paths relative to `root` to restrict
44 > * change reporting to. Omit to report every change under `root`
45 > * subject to `excludes`.
46 > */
47 > includes?: { items: string[] };
48 > }
49 >
50 > // ─── Resource Change ─────────────────────────────────────────────────────────
51 >
52 > /**
53 > * Discriminant for {@link ResourceChange.type}.
54 > *
55 > * @category Resource Watch Types
56 > */
57 > export const enum ResourceChangeType {
58 > Added = 'added',
59 > Updated = 'updated',
60 > Deleted = 'deleted',
61 > }
62 >
63 > /**
64 > * A single change observed by a resource watcher.
65 > *
66 > * @category Resource Watch Types
67 > */
68 > export interface ResourceChange {
69 > /** The URI of the resource that changed. */
70 > uri: URI;
71 > /** The kind of change observed. */
72 > type: ResourceChangeType;
73 > }
src/vs/base/common/iterator.ts 65 covered LOC · 23 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- iterator.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { isIterable } from './types.js';
7 >
8 > export namespace Iterable {
9 >
10 > export function is<T = unknown>(thing: unknown): thing is Iterable<T> {
11 > return !!thing && typeof thing === 'object' && typeof (thing as Iterable<T>)[Symbol.iterator] === 'function'; iterator.ts
12 > }
14 > const _empty: Iterable<never> = Object.freeze([]);
15 > export function empty<T = never>(): readonly never[] {
16 return _empty as readonly never[];
17 }
19 > export function* single<T>(element: T): Iterable<T> {
20 yield element;
21 }
23 > export function wrap<T>(iterableOrElement: Iterable<T> | T): Iterable<T> {
24 if (is(iterableOrElement)) {
25 return iterableOrElement;
28 }
29 }
31 > export function from<T>(iterable: Iterable<T> | undefined | null): Iterable<T> {
32 return iterable ?? (_empty as Iterable<T>);
33 }
35 > export function* reverse<T>(array: ReadonlyArray<T>): Iterable<T> {
36 for (let i = array.length - 1; i >= 0; i--) {
37 yield array[i];
38 }
39 }
41 > export function isEmpty<T>(iterable: Iterable<T> | undefined | null): boolean {
42 return !iterable || iterable[Symbol.iterator]().next().done === true;
43 }
45 > export function first<T>(iterable: Iterable<T>): T | undefined {
46 return iterable[Symbol.iterator]().next().value;
47 }
49 > export function some<T>(iterable: Iterable<T>, predicate: (t: T, i: number) => unknown): boolean {
50 let i = 0;
51 for (const element of iterable) {
56 return false;
57 }
59 > export function every<T>(iterable: Iterable<T>, predicate: (t: T, i: number) => unknown): boolean {
60 let i = 0;
61 for (const element of iterable) {
66 return true;
67 }
69 > export function find<T, R extends T>(iterable: Iterable<T>, predicate: (t: T) => t is R): R | undefined;
70 > export function find<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): T | undefined;
71 > export function find<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): T | undefined {
72 for (const element of iterable) {
73 if (predicate(element)) {
78 return undefined;
79 }
81 > export function filter<T, R extends T>(iterable: Iterable<T>, predicate: (t: T) => t is R): Iterable<R>;
82 > export function filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T>;
83 > export function* filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T> {
84 for (const element of iterable) {
85 if (predicate(element)) {
88 }
89 }
91 > export function* map<T, R>(iterable: Iterable<T>, fn: (t: T, index: number) => R): Iterable<R> {
92 let index = 0;
93 for (const element of iterable) {
95 }
96 }
98 > export function* flatMap<T, R>(iterable: Iterable<T>, fn: (t: T, index: number) => Iterable<R>): Iterable<R> {
99 let index = 0;
100 for (const element of iterable) {
102 }
103 }
104 > iterator.ts
105 > export function* concat<T>(...iterables: (Iterable<T> | T)[]): Iterable<T> {
106 for (const item of iterables) {
107 if (isIterable(item)) {
112 }
113 }
114 > iterator.ts
115 > export function reduce<T, R>(iterable: Iterable<T>, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R {
116 let value = initialValue;
117 for (const element of iterable) {
120 return value;
121 }
122 > iterator.ts
123 > export function length<T>(iterable: Iterable<T>): number {
124 let count = 0;
125 for (const _ of iterable) {
128 return count;
129 }
130 > iterator.ts
131 > /**
132 > * Returns an iterable slice of the array, with the same semantics as `array.slice()`.
133 > */
134 > export function* slice<T>(arr: ReadonlyArray<T>, from: number, to = arr.length): Iterable<T> {
135 if (from < -arr.length) {
136 from = 0;
150 }
151 }
152 > iterator.ts
153 > /**
154 > * Consumes `atMost` elements from iterable and returns the consumed elements,
155 > * and an iterable for the rest of the elements.
156 > */
157 > export function consume<T>(iterable: Iterable<T>, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable<T>] {
158 const consumed: T[] = [];
159
176 return [consumed, { [Symbol.iterator]() { return iterator; } }];
177 }
178 > iterator.ts
179 > export async function asyncToArray<T>(iterable: AsyncIterable<T>): Promise<T[]> {
180 const result: T[] = [];
181 for await (const item of iterable) {
184 return result;
185 }
186 > iterator.ts
187 > export async function asyncToArrayFlat<T>(iterable: AsyncIterable<T[]>): Promise<T[]> {
188 let result: T[] = [];
189 for await (const item of iterable) {
src/vs/base/common/objects.ts 65 covered LOC · 13 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- objects.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { isTypedArray, isObject, isUndefinedOrNull } from './types.js';
7 >
8 > export function deepClone<T>(obj: T): T {
9 if (!obj || typeof obj !== 'object') {
10 return obj;
19 return result;
20 }
21 > objects.ts
22 > export function deepFreeze<T>(obj: T): T {
23 if (!obj || typeof obj !== 'object') {
24 return obj;
39 return obj;
40 }
41 > objects.ts
42 > const _hasOwnProperty = Object.prototype.hasOwnProperty;
43 >
44 >
45 > export function cloneAndChange(obj: any, changer: (orig: any) => any): any {
46 return _cloneAndChange(obj, changer, new Set());
47 }
48 > objects.ts
49 function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>): any {
50 if (isUndefinedOrNull(obj)) {
82 return obj;
83 }
84 > objects.ts
85 > /**
86 > * Copies all properties of source into destination. The optional parameter "overwrite" allows to control
87 > * if existing properties on the destination should be overwritten or not. Defaults to true (overwrite).
88 > */
89 > export function mixin(destination: any, source: any, overwrite: boolean = true): any {
90 if (!isObject(destination)) {
91 return source;
109 return destination;
110 }
111 > objects.ts
112 > export function equals(one: any, other: any): boolean {
113 if (one === other) {
114 return true;
162 return true;
163 }
164 > objects.ts
165 > /**
166 > * Calls `JSON.Stringify` with a replacer to break apart any circular references.
167 > * This prevents `JSON`.stringify` from throwing the exception
168 > * "Uncaught TypeError: Converting circular structure to JSON"
169 > */
170 > export function safeStringify(obj: any): string {
171 const seen = new Set<any>();
172 return JSON.stringify(obj, (key, value) => {
184 });
185 }
186 > objects.ts
187 > /**
188 > * Like `JSON.stringify`, but with deterministic ordering of object keys so that
189 > * structurally equal inputs always produce the same string. Useful for cache
190 > * keys derived from arbitrary object payloads.
191 > *
192 > * - Object keys are sorted at every level of nesting.
193 > * - Properties whose value is `undefined` are omitted (matching `JSON.stringify`).
194 > * - Circular references are replaced with the string `"[Circular]"` to avoid
195 > * throwing.
196 > * - A top-level `undefined` returns the string `'undefined'`; any other
197 > * stringification failure returns the empty string.
198 > */
199 > export function stableStringify(value: unknown): string {
200 if (value === undefined) {
201 return 'undefined';
207 }
208 }
209 > objects.ts
210 function _stableStringify(value: unknown, seen: WeakSet<object>): string {
211 if (value === null || typeof value !== 'object') {
230 return '{' + parts.join(',') + '}';
231 }
232 > objects.ts
233 > type obj = { [key: string]: any };
234 > /**
235 > * Returns an object that has keys for each value that is different in the base object. Keys
236 > * that do not exist in the target but in the base object are not considered.
237 > *
238 > * Note: This is not a deep-diffing method, so the values are strictly taken into the resulting
239 > * object if they differ.
240 > *
241 > * @param base the object to diff against
242 > * @param obj the object to use for diffing
243 > */
244 > export function distinct(base: obj, target: obj): obj {
245 const result = Object.create(null);
246
261 return result;
262 }
263 > objects.ts
264 > export function getCaseInsensitive(target: obj, key: string): unknown {
265 const lowercaseKey = key.toLowerCase();
266 const equivalentKey = Object.keys(target).find(k => k.toLowerCase() === lowercaseKey);
267 return equivalentKey ? target[equivalentKey] : target[key];
268 }
269 > objects.ts
270 > export function filter(obj: obj, predicate: (key: string, value: any) => boolean): obj {
271 const result = Object.create(null);
272 for (const [key, value] of Object.entries(obj)) {
277 return result;
278 }
279 > objects.ts
280 > export function mapValues<T extends {}, R>(obj: T, fn: (value: T[keyof T], key: string) => R): { [K in keyof T]: R } {
281 const result: { [key: string]: R } = {};
282 for (const [key, value] of Object.entries(obj)) {
src/vs/platform/agentHost/common/state/protocol/common/notifications.ts 63 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- notifications.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import type { URI } from './state.js';
10 >
11 > /**
12 > * Reason why authentication is required.
13 > *
14 > * @category Protocol Notifications
15 > */
16 > export const enum AuthRequiredReason {
17 > /** The client has not yet authenticated for the resource */
18 > Required = 'required',
19 > /** A previously valid token has expired or been revoked */
20 > Expired = 'expired',
21 > }
22 >
23 > // ─── auth/required ───────────────────────────────────────────────────────────
24 >
25 > /**
26 > * Sent by the server when a protected resource requires (re-)authentication.
27 > *
28 > * This notification MAY be associated with any channel — for example, an
29 > * agent advertised on the root channel, or a per-session resource. The
30 > * `channel` field identifies the subscription the auth requirement belongs
31 > * to; the `resource` field carries the OAuth-protected resource identifier
32 > * (per RFC 9728).
33 > *
34 > * Clients should obtain a fresh token and push it via the `authenticate`
35 > * command.
36 > *
37 > * @category Protocol Notifications
38 > * @method auth/required
39 > * @direction Server → Client
40 > * @messageType Notification
41 > * @version 1
42 > * @see {@link /specification/authentication | Authentication}
43 > * @example
44 > * ```json
45 > * {
46 > * "jsonrpc": "2.0",
47 > * "method": "auth/required",
48 > * "params": {
49 > * "channel": "ahp-root://",
50 > * "resource": "https://api.github.com",
51 > * "reason": "expired"
52 > * }
53 > * }
54 > * ```
55 > */
56 > export interface AuthRequiredParams {
57 > /** Channel URI this notification belongs to */
58 > channel: URI;
59 > /** The protected resource identifier that requires authentication */
60 > resource: string;
61 > /** Why authentication is required */
62 > reason?: AuthRequiredReason;
63 > }
src/vs/base/test/common/utils.ts 56 covered LOC · 13 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- utils.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { DisposableStore, DisposableTracker, IDisposable, setDisposableTracker } from '../../common/lifecycle.js';
7 > import { join } from '../../common/path.js';
8 > import { isWindows } from '../../common/platform.js';
9 > import { URI } from '../../common/uri.js';
10 >
11 > export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
12 >
13 > export function toResource(this: any, path: string): URI {
14 if (isWindows) {
15 return URI.file(join('C:\\', btoa(this.test.fullTitle()), path));
18 return URI.file(join('/', btoa(this.test.fullTitle()), path));
19 }
20 > utils.ts
21 > export function suiteRepeat(n: number, description: string, callback: (this: any) => void): void {
22 for (let i = 0; i < n; i++) {
23 suite(`${description} (iteration ${i})`, callback);
24 }
25 }
26 > utils.ts
27 > export function testRepeat(n: number, description: string, callback: (this: any) => any): void {
28 for (let i = 0; i < n; i++) {
29 test(`${description} (iteration ${i})`, callback);
30 }
31 }
32 > utils.ts
33 export async function assertThrowsAsync(block: () => any, message: string | Error = 'Missing expected exception'): Promise<void> {
34 try {
41 throw err;
42 }
43 > utils.ts
44 > /**
45 > * Use this function to ensure that all disposables are cleaned up at the end of each test in the current suite.
46 > *
47 > * Use `markAsSingleton` if disposable singletons are created lazily that are allowed to outlive the test.
48 > * Make sure that the singleton properly registers all child disposables so that they are excluded too.
49 > *
50 > * @returns A {@link DisposableStore} that can optionally be used to track disposables in the test.
51 > * This will be automatically disposed on test teardown.
52 > */
53 > export function ensureNoDisposablesAreLeakedInTestSuite(): Pick<DisposableStore, 'add'> {
54 > let tracker: DisposableTracker | undefined;
55 > let store: DisposableStore;
56 > setup(() => {
57 > store = new DisposableStore(); utils.ts
58 > tracker = new DisposableTracker();
59 > setDisposableTracker(tracker);
60 > }); utils.ts
61 >
62 > teardown(function (this: import('mocha').Context) {
63 > store.dispose(); utils.ts
64 > setDisposableTracker(null);
65 > if (this.currentTest?.state !== 'failed') {
66 > const result = tracker!.computeLeakingDisposables();
67 > if (result) {
68 console.error(result.details);
69 throw new Error(`There are ${result.leaks.length} undisposed disposables!${result.details}`);
70 }
71 > } utils.ts
72 > }); utils.ts
73 >
74 > // Wrap store as the suite function is called before it's initialized
75 > const testContext = {
76 > add<T extends IDisposable>(o: T): T {
77 return store.add(o);
78 }
79 > }; utils.ts
80 > return testContext;
81 > }
82 >
83 > export function throwIfDisposablesAreLeaked(body: () => void, logToConsole = true): void {
84 const tracker = new DisposableTracker();
85 setDisposableTracker(tracker);
88 computeLeakingDisposables(tracker, logToConsole);
89 }
90 > utils.ts
91 export async function throwIfDisposablesAreLeakedAsync(body: () => Promise<void>): Promise<void> {
92 const tracker = new DisposableTracker();
96 computeLeakingDisposables(tracker);
97 }
98 > utils.ts
99 function computeLeakingDisposables(tracker: DisposableTracker, logToConsole = true) {
100 const result = tracker.computeLeakingDisposables();
src/vs/base/common/process.ts 53 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- process.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { INodeProcess, isMacintosh, isWindows } from './platform.js';
7 >
8 > let safeProcess: Omit<INodeProcess, 'arch'> & { arch: string | undefined };
9 > declare const process: INodeProcess;
10 >
11 > // Native sandbox environment
12 > const vscodeGlobal = (globalThis as { vscode?: { process?: INodeProcess } }).vscode;
13 > if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') {
14 const sandboxProcess: INodeProcess = vscodeGlobal.process;
15 safeProcess = {
20 };
21 }
22 > process.ts
23 > // Native node.js environment
24 > else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') {
25 > safeProcess = {
26 > get platform() { return process.platform; },
27 > get arch() { return process.arch; },
28 > get env() { return process.env; },
29 > cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }
30 > };
31 }
32
44 };
45 }
46 > process.ts
47 > /**
48 > * Provides safe access to the `cwd` property in node.js, sandboxed or web
49 > * environments.
50 > *
51 > * Note: in web, this property is hardcoded to be `/`.
52 > *
53 > * @skipMangle
54 > */
55 > export const cwd = safeProcess.cwd;
56 >
57 > /**
58 > * Provides safe access to the `env` property in node.js, sandboxed or web
59 > * environments.
60 > *
61 > * Note: in web, this property is hardcoded to be `{}`.
62 > */
63 > export const env = safeProcess.env;
64 >
65 > /**
66 > * Provides safe access to the `platform` property in node.js, sandboxed or web
67 > * environments.
68 > */
69 > export const platform = safeProcess.platform;
70 >
71 > /**
72 > * Provides safe access to the `arch` method in node.js, sandboxed or web
73 > * environments.
74 > * Note: `arch` is `undefined` in web
75 > */
76 > export const arch = safeProcess.arch;
src/vs/platform/agentHost/common/sessionConfigKeys.ts 52 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionConfigKeys.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > /**
7 > * Well-known keys used in the agent-host configuration value bag.
8 > *
9 > * The Agent Host Protocol's config schema is intentionally generic — agents
10 > * are free to advertise any property names. These constants capture the
11 > * names that the platform itself consumes (e.g. {@link SessionConfigKey.AutoApprove}
12 > * drives tool auto-approval) or that clients interpret via convention
13 > * (e.g. {@link SessionConfigKey.Branch}, {@link SessionConfigKey.Isolation}).
14 > *
15 > * Provider-owned platform properties use these names in an agent's
16 > * `resolveSessionConfig` response. Worktree properties are owned and
17 > * contributed by the host and are not passed to agents.
18 > */
19 > export const enum SessionConfigKey {
20 > /** `'autoApprove'` — tool auto-approval level. */
21 > AutoApprove = 'autoApprove',
22 > /** `'permissions'` — per-tool session allow/deny lists. */
23 > Permissions = 'permissions',
24 > /** `'isolation'` — host-owned `'folder'` or `'worktree'` selection. */
25 > Isolation = 'isolation',
26 > /** `'branch'` — host-owned base branch to work from. */
27 > Branch = 'branch',
28 > /** `'mode'` — agent execution mode (interactive / plan / autopilot). */
29 > Mode = 'mode',
30 > /** `'worktreeBranchPrefix'` — host-owned prefix for the worktree branch name. */
31 > WorktreeBranchPrefix = 'worktreeBranchPrefix',
32 > /** `'worktreeIncludeFiles'` — host-owned glob patterns for files copied into a new worktree. */
33 > WorktreeIncludeFiles = 'worktreeIncludeFiles',
34 > }
35 >
36 > /**
37 > * The set of enum values the unified permission picker *tolerates* for the
38 > * {@link SessionConfigKey.AutoApprove} property when deciding whether a
39 > * session's schema is "well-known" (and therefore handled by the dedicated
40 > * permission picker rather than the generic per-property fallback).
41 > *
42 > * `default` is the required baseline level; `assisted` and `autoApprove` are
43 > * offered elevated levels. `autopilot` is retained for backward compatibility
44 > * with sessions created before it moved onto the mode axis.
45 > */
46 > export const KNOWN_AUTO_APPROVE_VALUES: ReadonlySet<string> = new Set(['default', 'assisted', 'autoApprove', 'autopilot']);
47 >
48 > /**
49 > * The set of enum values understood for the {@link SessionConfigKey.Mode}
50 > * property: the agent execution mode axis.
51 > */
52 > export const KNOWN_MODE_VALUES: ReadonlySet<string> = new Set(['interactive', 'plan', 'autopilot']);
src/vs/base/common/assert.ts 48 covered LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- assert.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { BugIndicatingError, onUnexpectedError } from './errors.js';
7 >
8 > /**
9 > * Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
10 > *
11 > * @deprecated Use `assert(...)` instead.
12 > * This method is usually used like this:
13 > * ```ts
14 > * import * as assert from 'vs/base/common/assert';
15 > * assert.ok(...);
16 > * ```
17 > *
18 > * However, `assert` in that example is a user chosen name.
19 > * There is no tooling for generating such an import statement.
20 > * Thus, the `assert(...)` function should be used instead.
21 > */
22 > export function ok(value?: unknown, message?: string) {
23 if (!value) {
24 throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
25 }
26 }
27 > assert.ts
28 > export function assertNever(value: never, message = 'Unreachable'): never {
29 throw new Error(message);
30 }
31 > assert.ts
32 > export function softAssertNever(value: never): void {
33 // no-op
34 }
35 > assert.ts
36 > /**
37 > * Asserts that a condition is `truthy`.
38 > *
39 > * @throws provided {@linkcode messageOrError} if the {@linkcode condition} is `falsy`.
40 > *
41 > * @param condition The condition to assert.
42 > * @param messageOrError An error message or error object to throw if condition is `falsy`.
43 > */
44 > export function assert(
45 condition: boolean,
46 messageOrError: string | Error = 'unexpected state',
55 }
56 }
57 > assert.ts
58 > /**
59 > * Like assert, but doesn't throw.
60 > */
61 > export function softAssert(condition: boolean, message = 'Soft Assertion Failed'): void {
62 if (!condition) {
63 onUnexpectedError(new BugIndicatingError(message));
64 }
65 }
66 > assert.ts
67 > /**
68 > * condition must be side-effect free!
69 > */
70 > export function assertFn(condition: () => boolean): void {
71 if (!condition()) {
72 // eslint-disable-next-line no-debugger
77 }
78 }
79 > assert.ts
80 > export function checkAdjacentItems<T>(items: readonly T[], predicate: (item1: T, item2: T) => boolean): boolean {
81 let i = 0;
82 while (i < items.length - 1) {
src/vs/base/test/common/virtualScheduling/timeApi.ts 48 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- timeApi.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export interface TimeoutId { readonly _timeoutIdBrand: void }
7 > export interface IntervalId { readonly _intervalIdBrand: void }
8 > export interface ImmediateId { readonly _immediateIdBrand: void }
9 > export type AnimationFrameId = number & { readonly _animationFrameIdBrand: void };
10 >
11 > /**
12 > * The subset of host time APIs the processor and embeddings need.
13 > *
14 > * Used both for the real host API (captured via {@link captureGlobalTimeApi})
15 > * and for the virtual replacement that runs through a {@link VirtualClock}.
16 > *
17 > * Keeping this as a plain interface means the processor never reaches into
18 > * `globalThis` directly: the boundary between "real time" and "virtual time"
19 > * is exactly which `TimeApi` instance is in use.
20 > */
21 > export interface TimeApi {
22 > setTimeout(handler: () => void, timeout?: number): TimeoutId;
23 > clearTimeout(id: TimeoutId): void;
24 > setInterval(handler: () => void, interval: number): IntervalId;
25 > clearInterval(id: IntervalId): void;
26 > setImmediate?: ((handler: () => void) => ImmediateId);
27 > clearImmediate?: ((id: ImmediateId) => void);
28 > requestAnimationFrame?: ((cb: (time: number) => void) => AnimationFrameId);
29 > cancelAnimationFrame?: ((id: AnimationFrameId) => void);
30 > Date: DateConstructor;
31 > }
32 >
33 > export function captureGlobalTimeApi(): TimeApi {
34 > return {
35 > setTimeout: globalThis.setTimeout.bind(globalThis) as unknown as TimeApi['setTimeout'],
36 > clearTimeout: globalThis.clearTimeout.bind(globalThis) as unknown as TimeApi['clearTimeout'],
37 > setInterval: globalThis.setInterval.bind(globalThis) as unknown as TimeApi['setInterval'],
38 > clearInterval: globalThis.clearInterval.bind(globalThis) as unknown as TimeApi['clearInterval'],
39 > setImmediate: globalThis.setImmediate?.bind(globalThis) as unknown as TimeApi['setImmediate'],
40 > clearImmediate: globalThis.clearImmediate?.bind(globalThis) as unknown as TimeApi['clearImmediate'],
41 > requestAnimationFrame: globalThis.requestAnimationFrame?.bind(globalThis) as unknown as TimeApi['requestAnimationFrame'],
42 > cancelAnimationFrame: globalThis.cancelAnimationFrame?.bind(globalThis) as unknown as TimeApi['cancelAnimationFrame'],
43 > Date: globalThis.Date,
44 > };
45 > }
46 >
47 > /** A snapshot of the real host time API at module-load time. */
48 > export const realTimeApi: TimeApi = captureGlobalTimeApi();
src/vs/base/test/common/virtualScheduling/traceLogger.ts 47 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- traceLogger.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { LogEntryLike } from '../executionGraph.js';
7 > import { Trace, TraceContext } from './trace.js';
8 >
9 > /**
10 > * A minimal logger for tests that captures the active {@link Trace} at log
11 > * time so messages can later be woven into a swimlane diagram next to the
12 > * timer events that produced them.
13 > */
14 > export interface ITraceLogger {
15 > log(message: string): void;
16 > warn(message: string): void;
17 > error(message: string): void;
18 > /**
19 > * Run `fn` synchronously and log it as a marker in the trace. The
20 > * marker text is `fn.toString()` so call sites read naturally as e.g.
21 > * `logger.logRun(() => model.trigger())`. Returns whatever `fn`
22 > * returns.
23 > */
24 > logRun<T>(fn: () => T): T;
25 > }
26 >
27 > /**
28 > * One log entry produced by an {@link ITraceLogger}. Extends
29 > * {@link LogEntryLike} (consumed by `buildHistoryFromTasks`) with a level so
30 > * renderers can differentiate `log` / `warn` / `error`.
31 > */
32 > export interface ITraceLogEntry extends LogEntryLike {
33 > readonly trace: Trace;
34 > readonly level: 'log' | 'warn' | 'error';
35 > }
36 >
37 > /**
38 > * Build an {@link ITraceLogger} that pushes every call into `buffer`,
39 > * tagging each entry with the trace that's current at call time.
40 > *
41 > * Pass the same `buffer` to `buildHistoryFromTasks(history, startTime,
42 > * buffer)` to interleave log lines with the timer swimlane.
43 > */
44 > export function createTraceLogger(buffer: ITraceLogEntry[]): ITraceLogger {
45 const make = (level: 'log' | 'warn' | 'error') => (message: string) => {
46 buffer.push({
61 };
62 }
64 > /** Best-effort one-line description of `fn` for trace log markers. */
65 function _describeFn(fn: () => unknown): string {
66 const src = fn.toString();
72 return _collapseWhitespace(src);
73 }
75 function _collapseWhitespace(s: string): string {
76 return s.replace(/\s+/g, ' ').trim();
src/vs/platform/agentHost/common/agentCustomizationSettings.ts 46 covered LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentCustomizationSettings.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type { ConfigPropertySchema, RootState } from './state/protocol/state.js';
7 >
8 > export const AGENT_CUSTOMIZATION_SETTINGS_META_KEY = 'vscode.agentCustomizationSettings';
9 >
10 > export interface IAgentCustomizationSettingDescriptor {
11 > readonly key: string;
12 > readonly group: string;
13 > readonly kind?: 'multiline';
14 > readonly saveLabel?: string;
15 > }
16 >
17 > export interface IAgentCustomizationSettingsDescriptor {
18 > readonly provider: string;
19 > readonly title: string;
20 > readonly description: string;
21 > readonly settings: readonly IAgentCustomizationSettingDescriptor[];
22 > readonly configurationFile?: {
23 > readonly resource: string;
24 > readonly title: string;
25 > readonly description: string;
26 > readonly openLabel: string;
27 > readonly documentationUrl?: string;
28 > readonly documentationLabel?: string;
29 > };
30 > }
31 >
32 > export interface IAgentCustomizationSettingsRegistration extends IAgentCustomizationSettingsDescriptor {
33 > readonly properties: Readonly<Record<string, ConfigPropertySchema>>;
34 > }
35 >
36 function isAgentCustomizationSettingDescriptor(value: unknown): value is IAgentCustomizationSettingDescriptor {
37 if (!value || typeof value !== 'object') {
44 && (setting.saveLabel === undefined || typeof setting.saveLabel === 'string');
45 }
47 function isAgentCustomizationSettingsDescriptor(value: unknown): value is IAgentCustomizationSettingsDescriptor {
48 if (!value || typeof value !== 'object') {
62 && (file.documentationLabel === undefined || typeof file.documentationLabel === 'string'));
63 }
65 > export function getAgentCustomizationSettingsEntries(state: RootState | undefined): readonly IAgentCustomizationSettingsDescriptor[] {
66 const meta = state?._meta;
67 const value = meta?.[AGENT_CUSTOMIZATION_SETTINGS_META_KEY];
68 return Array.isArray(value) ? value.filter(isAgentCustomizationSettingsDescriptor) : [];
69 }
71 > export function withAgentCustomizationSettings(state: RootState | undefined, entries: readonly IAgentCustomizationSettingsDescriptor[]): Record<string, unknown> {
72 return { ...state?._meta, [AGENT_CUSTOMIZATION_SETTINGS_META_KEY]: entries };
73 }
75 > export function readAgentCustomizationSettings(state: RootState | undefined, provider: string): IAgentCustomizationSettingsDescriptor | undefined {
76 return getAgentCustomizationSettingsEntries(state).find(entry => entry.provider === provider);
77 }
79 > export function getProviderBackedRootConfigKeys(state: RootState | undefined): ReadonlySet<string> {
80 return new Set(getAgentCustomizationSettingsEntries(state).flatMap(entry => entry.settings.map(setting => setting.key)));
81 }
83 > export function preserveProviderBackedRootConfigValues(state: RootState | undefined, replacement: Readonly<Record<string, unknown>>): Record<string, unknown> {
84 const values = { ...replacement };
85 const current = state?.config?.values;
src/vs/base/test/common/virtualScheduling/virtualTimeApi.ts 45 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- virtualTimeApi.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IDisposable } from '../../../common/lifecycle.js';
7 > import { realTimeApi, TimeApi } from './timeApi.js';
8 > import { ROOT_TRACE, TraceContext } from './trace.js';
9 > import { VirtualClock } from './virtualClock.js';
10 >
11 > // V8 default `Error.stackTraceLimit` of 10 swallows everything past the
12 > // first async boundary in the stacks we capture for trace diagnostics.
13 > // Bump it so swimlane callers actually see the user code that scheduled a
14 > // timer rather than just the Promise wrapper.
15 > if (typeof Error.stackTraceLimit === 'number' && Error.stackTraceLimit < 50) {
16 > Error.stackTraceLimit = 50;
17 > }
18 >
19 > /** Virtual timer IDs are `IDisposable`s. Recover one from an opaque id. */
20 function asDisposable(id: unknown): IDisposable | undefined {
21 if (id === null || typeof id !== 'object') { return undefined; }
23 return typeof maybe.dispose === 'function' ? id as IDisposable : undefined;
24 }
26 > export interface CreateVirtualTimeApiOptions {
27 > /**
28 > * If `true`, `requestAnimationFrame` is faked: callbacks are scheduled
29 > * onto the virtual queue at `now + 16ms` and the resulting event hints
30 > * the embedding to use a real `requestAnimationFrame` so the host can
31 > * reflow before the callback runs. Useful for fixtures that need DOM
32 > * measurements after rAF callbacks.
33 > *
34 > * If `false` (default), `requestAnimationFrame` is left to the host.
35 > */
36 > readonly fakeRequestAnimationFrame?: boolean;
37 > }
38 >
39 > /**
40 > * Build a {@link TimeApi} that schedules every timer call into `clock`'s
41 > * virtual queue, capturing the current trace at schedule time so that
42 > * causal chains (`setTimeout` → `setTimeout`, etc.) are preserved.
43 > *
44 > * The returned API is suitable to install with {@link pushGlobalTimeApi},
45 > * which is what {@link runWithFakedTimers} does internally.
46 > */
47 > export function createVirtualTimeApi(
48 clock: VirtualClock,
49 options?: CreateVirtualTimeApiOptions,
190 return api;
191 }
193 > // Re-exported for convenience: many tests want to install both at once.
194 > export { pushGlobalTimeApi } from './globalTimeApi.js';
src/vs/base/common/linkedList.ts 44 covered LOC · 13 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- linkedList.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > class Node<E> {
7 >
8 > static readonly Undefined = new Node<unknown>(undefined);
9 >
10 > element: E;
11 > next: Node<E> | typeof Node.Undefined;
12 > prev: Node<E> | typeof Node.Undefined;
13 >
14 > constructor(element: E) {
15 > this.element = element;
16 > this.next = Node.Undefined;
17 > this.prev = Node.Undefined;
18 > }
19 > }
20 >
21 > export class LinkedList<E> {
22
23 private _first: Node<E> | typeof Node.Undefined = Node.Undefined;
24 private _last: Node<E> | typeof Node.Undefined = Node.Undefined;
25 private _size: number = 0;
27 > get size(): number {
28 return this._size;
29 }
31 > isEmpty(): boolean {
32 return this._first === Node.Undefined;
33 }
35 > clear(): void {
36 let node = this._first;
37 while (node !== Node.Undefined) {
46 this._size = 0;
47 }
49 > unshift(element: E): () => void {
50 return this._insert(element, false);
51 }
53 > push(element: E): () => void {
54 return this._insert(element, true);
55 }
57 > private _insert(element: E, atTheEnd: boolean): () => void {
58 const newNode = new Node(element);
59 if (this._first === Node.Undefined) {
85 };
86 }
88 > shift(): E | undefined {
89 if (this._first === Node.Undefined) {
90 return undefined;
95 }
96 }
98 > pop(): E | undefined {
99 if (this._last === Node.Undefined) {
100 return undefined;
105 }
106 }
108 > peek(): E | undefined {
109 if (this._last === Node.Undefined) {
110 return undefined;
114 }
115 }
117 > private _remove(node: Node<E> | typeof Node.Undefined): void {
118 if (node.prev !== Node.Undefined && node.next !== Node.Undefined) {
119 // middle
141 this._size -= 1;
142 }
144 > *[Symbol.iterator](): Iterator<E> {
145 let node = this._first;
146 while (node !== Node.Undefined) {
src/vs/base/test/common/virtualScheduling/runWithFakedTimers.ts 44 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- runWithFakedTimers.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationTokenSource } from '../../../common/cancellation.js';
7 > import { drainMicrotasksEmbedding } from './embedding.js';
8 > import { pushGlobalTimeApi } from './globalTimeApi.js';
9 > import { realTimeApi } from './timeApi.js';
10 > import { untilToken, VirtualTimeProcessor } from './processor.js';
11 > import { createRecordingRealTimeApi, RecordedTimerEvent } from './recordingTimeApi.js';
12 > import { VirtualClock } from './virtualClock.js';
13 > import { createVirtualTimeApi } from './virtualTimeApi.js';
14 >
15 > export interface RunWithFakedTimersOptions {
16 > readonly startTime?: number;
17 > /** Default `true`. Set `false` to bypass virtual time entirely (for
18 > * cases where the same test is parameterised over real/virtual time). */
19 > readonly useFakeTimers?: boolean;
20 > /** No effect in the new processor; accepted for legacy compatibility.
21 > * The drain-microtasks embedding picks the fastest available macrotask
22 > * primitive automatically. */
23 > readonly useSetImmediate?: boolean;
24 > /** Maximum number of virtual events the run is allowed to execute
25 > * before being rejected. Default 100. */
26 > readonly maxTaskCount?: number;
27 > /**
28 > * If set, called once `fn` resolves with the recorded timer events.
29 > * In virtual mode the events come from the {@link VirtualTimeProcessor}'s
30 > * own history; in real mode a recording wrapper around the host time
31 > * API is installed for the duration of `fn`. Useful for swimlane
32 > * diagnostics.
33 > */
34 > readonly onHistory?: (history: readonly RecordedTimerEvent[]) => void;
35 > }
36 >
37 > /**
38 > * Run `fn` with a virtual clock installed as the global time API.
39 > *
40 > * After `fn` resolves, the virtual queue is drained (so any timers `fn`
41 > * scheduled and `await`ed for, transitively, complete deterministically).
42 > * If `fn` throws, the queue is *not* drained — the original error is
43 > * re-thrown immediately.
44 > */
45 export async function runWithFakedTimers<T>(
46 options: RunWithFakedTimersOptions,
src/vs/base/common/uint.ts 43 covered LOC · 2 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- uint.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export const enum Constants {
7 > /**
8 > * MAX SMI (SMall Integer) as defined in v8.
9 > * one bit is lost for boxing/unboxing flag.
10 > * one bit is lost for sign flag.
11 > * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
12 > */
13 > MAX_SAFE_SMALL_INTEGER = 1 << 30,
14 >
15 > /**
16 > * MIN SMI (SMall Integer) as defined in v8.
17 > * one bit is lost for boxing/unboxing flag.
18 > * one bit is lost for sign flag.
19 > * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
20 > */
21 > MIN_SAFE_SMALL_INTEGER = -(1 << 30),
22 >
23 > /**
24 > * Max unsigned integer that fits on 8 bits.
25 > */
26 > MAX_UINT_8 = 255, // 2^8 - 1
27 >
28 > /**
29 > * Max unsigned integer that fits on 16 bits.
30 > */
31 > MAX_UINT_16 = 65535, // 2^16 - 1
32 >
33 > /**
34 > * Max unsigned integer that fits on 32 bits.
35 > */
36 > MAX_UINT_32 = 4294967295, // 2^32 - 1
37 >
38 > UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
39 > }
40 >
41 > export function toUint8(v: number): number {
42 if (v < 0) {
43 return 0;
48 return v | 0;
49 }
50 > uint.ts
51 > export function toUint32(v: number): number {
52 if (v < 0) {
53 return 0;
src/vs/base/test/common/virtualScheduling/globalTimeApi.ts 42 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- globalTimeApi.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IDisposable } from '../../../common/lifecycle.js';
7 > import { captureGlobalTimeApi, realTimeApi, TimeApi } from './timeApi.js';
8 >
9 > /** Cast through `unknown` so we don't widen our typed `TimeApi` shapes to `any`. */
10 > type AsGlobal<K extends keyof typeof globalThis> = (typeof globalThis)[K];
11 >
12 > /**
13 > * Ensure `fn` carries an `originalFn` back-door pointing at the real
14 > * (non-virtual) `setTimeout`. We prefer the existing tag on `fn`, then a tag
15 > * inherited from `previousFn` (which may itself be a wrapper that already
16 > * carried the back-door), and finally fall back to `realTimeApi.setTimeout`
17 > * — which has its own `originalFn` set at module load.
18 > */
19 function ensureSetTimeoutOriginalFn(fn: TimeApi['setTimeout'], previousFn: TimeApi['setTimeout']): TimeApi['setTimeout'] {
20 const tagged = fn as TimeApi['setTimeout'] & { originalFn?: TimeApi['setTimeout'] };
26 return tagged;
27 }
29 > /**
30 > * Replace the global time APIs (`setTimeout`, `setInterval`, …, `Date`,
31 > * optionally `requestAnimationFrame`) with the ones from `api`. Returns a
32 > * disposable that restores the previous globals.
33 > *
34 > * The previous globals are captured *at install time*, so nested installs
35 > * compose correctly (the disposable restores to whatever was current when
36 > * this call was made, not to the original real values).
37 > *
38 > * `setTimeout.originalFn` is preserved on the installed function so callers
39 > * like the component-explorer host can escape virtual time when polling.
40 > * If `api.setTimeout` does not already carry `originalFn`, it is copied from
41 > * the previous global (or defaulted to the real `setTimeout`) so wrapping
42 > * APIs such as a logging wrapper don't drop the back-door.
43 > */
44 > export function pushGlobalTimeApi(api: TimeApi): IDisposable {
45 const previous = captureGlobalTimeApi();
46
74 };
75 }
77 > // One-shot tag on the *real* setTimeout: lets callers (e.g. the
78 > // component-explorer host's polling loop) escape virtual time even after
79 > // pushGlobalTimeApi has installed a virtual version on top. The `originalFn`
80 > // property is not on the `setTimeout` signature by design — it's a back-door
81 > // convention shared with the polling code.
82 > (realTimeApi.setTimeout as unknown as { originalFn: TimeApi['setTimeout'] }).originalFn = realTimeApi.setTimeout;
src/vs/base/common/lazy.ts 39 covered LOC · 4 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- lazy.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > enum LazyValueState {
7 > Uninitialized,
8 > Running,
9 > Completed,
10 > }
11 >
12 > export class Lazy<T> {
13 >
14 > private _state = LazyValueState.Uninitialized;
15 > private _value?: T;
16 > private _error: Error | undefined;
17 >
18 > constructor(
19 > private readonly executor: () => T, lazy.ts
20 > ) { }
21 > lazy.ts
22 > /**
23 > * True if the lazy value has been resolved.
24 > */
25 > get hasValue(): boolean { return this._state === LazyValueState.Completed; }
26 >
27 > /**
28 > * Get the wrapped value.
29 > *
30 > * This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only
31 > * resolved once. `getValue` will re-throw exceptions that are hit while resolving the value
32 > */
33 > get value(): T {
34 if (this._state === LazyValueState.Uninitialized) {
35 this._state = LazyValueState.Running;
50 return this._value!;
51 }
52 > lazy.ts
53 > /**
54 > * Get the wrapped value without forcing evaluation.
55 > */
56 > get rawValue(): T | undefined { return this._value; }
57 > }
src/vs/base/test/common/virtualScheduling/index.ts 36 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- index.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Greenfield virtual scheduling primitives.
7 > //
8 > // This folder is the new home for virtual-time scheduling. It supersedes
9 > // `timeTravelScheduler.ts` and `traceableTimeApi.ts`, both of which are
10 > // retained as @deprecated re-export shims.
11 >
12 > export type { TimeApi } from './timeApi.js';
13 > export { captureGlobalTimeApi, realTimeApi } from './timeApi.js';
14 >
15 > export type { EventSource, VirtualEvent, VirtualTime } from './virtualClock.js';
16 > export { VirtualClock } from './virtualClock.js';
17 >
18 > export type { RunAsHandlerOptions } from './trace.js';
19 > export { ROOT_TRACE, Trace, TraceContext, createTraceRoot } from './trace.js';
20 >
21 > export type { Embedding } from './embedding.js';
22 > export { drainMicrotasksEmbedding, nextMacrotask, syncEmbedding } from './embedding.js';
23 >
24 > export type { RunOptions, TerminationPolicy, VirtualTimeProcessorOptions } from './processor.js';
25 > export { VirtualTimeProcessor, untilIdle, untilTime, untilToken } from './processor.js';
26 >
27 > export { pushGlobalTimeApi } from './globalTimeApi.js';
28 > export type { CreateVirtualTimeApiOptions } from './virtualTimeApi.js';
29 > export { createVirtualTimeApi } from './virtualTimeApi.js';
30 > export { createLoggingTimeApi } from './loggingTimeApi.js';
31 > export type { RecordedTimerEvent } from './recordingTimeApi.js';
32 > export { createRecordingRealTimeApi } from './recordingTimeApi.js';
33 > export type { ITraceLogEntry, ITraceLogger } from './traceLogger.js';
34 > export { createTraceLogger } from './traceLogger.js';
35 > export type { RunWithFakedTimersOptions } from './runWithFakedTimers.js';
36 > export { runWithFakedTimers } from './runWithFakedTimers.js';
src/vs/base/test/common/timeTravelScheduler.ts 35 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- timeTravelScheduler.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > /**
7 > * @deprecated The contents of this file have moved to
8 > * `./virtualScheduling/index.js`. This re-export is kept for backwards
9 > * compatibility and will be removed once all callers have migrated.
10 > *
11 > * Notes for migration:
12 > * - `TimeTravelScheduler` is now {@link VirtualClock} (same constructor).
13 > * - `AsyncSchedulerProcessor` is now {@link VirtualTimeProcessor}; its
14 > * constructor takes an explicit {@link Embedding}, and {@link Run.options}
15 > * use `until` (a {@link TerminationPolicy}) instead of the implicit
16 > * "drain queue" behaviour. See {@link runWithFakedTimers} for a
17 > * drop-in helper.
18 > * - `originalGlobalValues` is now {@link realTimeApi}.
19 > */
20 >
21 > export {
22 > captureGlobalTimeApi,
23 > createLoggingTimeApi,
24 > createVirtualTimeApi,
25 > pushGlobalTimeApi,
26 > realTimeApi as originalGlobalValues,
27 > runWithFakedTimers,
28 > VirtualClock as TimeTravelScheduler,
29 > } from './virtualScheduling/index.js';
30 >
31 > export type {
32 > CreateVirtualTimeApiOptions,
33 > RunWithFakedTimersOptions,
34 > TimeApi,
35 > } from './virtualScheduling/index.js';
src/vs/platform/agentHost/common/annotationsUri.ts 35 covered LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- annotationsUri.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type { URI } from './state/sessionState.js';
7 >
8 > /**
9 > * Helpers for building / parsing the URI clients subscribe to in order to
10 > * receive an {@link import('./state/protocol/state.js').AnnotationsState}.
11 > *
12 > * Each session exposes exactly one annotations channel, nested under the
13 > * session URI namespace:
14 > *
15 > * <sessionUri>/annotations
16 > *
17 > * Keeping the annotations URI nested under the session URI lets the server
18 > * cleanly tear down a session's annotations when that session is disposed
19 > * (the reverse-lookup is just a string-prefix scan, mirroring changesets).
20 > */
21 >
22 > /** Marker injected into an annotations channel URI's path. */
23 > const ANNOTATIONS_PATH_SEGMENT = '/annotations';
24 >
25 > /** Returns the subscribable URI for a session's annotations channel. */
26 > export function buildAnnotationsUri(sessionUri: URI): URI {
27 return `${sessionUri}${ANNOTATIONS_PATH_SEGMENT}`;
28 }
30 > /**
31 > * Parses an annotations channel URI back into its owning `sessionUri`, or
32 > * returns `undefined` if `uri` is not an annotations channel URI.
33 > */
34 > export function parseAnnotationsUri(uri: URI): { sessionUri: URI } | undefined {
35 if (!uri.endsWith(ANNOTATIONS_PATH_SEGMENT)) {
36 return undefined;
42 return { sessionUri };
43 }
45 > /** Returns `true` iff `uri` is a session's annotations channel URI. */
46 > export function isAnnotationsUri(uri: URI): boolean {
47 return parseAnnotationsUri(uri) !== undefined;
48 }
src/vs/base/common/marshallingIds.ts 33 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- marshallingIds.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export const enum MarshalledId {
7 > Uri = 1,
8 > Regexp,
9 > ScmResource,
10 > ScmResourceGroup,
11 > ScmProvider,
12 > CommentController,
13 > CommentThread,
14 > CommentThreadInstance,
15 > CommentThreadReply,
16 > CommentNode,
17 > CommentThreadNode,
18 > TimelineActionContext,
19 > NotebookCellActionContext,
20 > NotebookActionContext,
21 > TerminalContext,
22 > TestItemContext,
23 > Date,
24 > TestMessageMenuArgs,
25 > ChatViewContext,
26 > LanguageModelToolResult,
27 > LanguageModelTextPart,
28 > LanguageModelThinkingPart,
29 > LanguageModelPromptTsxPart,
30 > LanguageModelDataPart,
31 > AgentSessionContext,
32 > ChatResponsePullRequestPart,
33 > }
src/vs/base/test/common/virtualScheduling/recordingTimeApi.ts 33 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- recordingTimeApi.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { realTimeApi, TimeApi } from './timeApi.js';
7 > import { Trace, TraceContext } from './trace.js';
8 > import { EventSource } from './virtualClock.js';
9 >
10 > /**
11 > * One entry in a real-time trace recording. Structurally compatible with
12 > * `VirtualEvent` (and `ScheduledTaskLike` consumed by
13 > * `buildHistoryFromTasks`), so the same swimlane renderer can plot both.
14 > */
15 > export interface RecordedTimerEvent {
16 > readonly time: number;
17 > readonly source: EventSource;
18 > readonly trace?: Trace;
19 > }
20 >
21 > /**
22 > * Wrap the real host time API so every `setTimeout` / `setInterval` /
23 > * `requestAnimationFrame` call is tagged with a child {@link Trace} and
24 > * pushes a {@link RecordedTimerEvent} into `history` when the handler
25 > * actually runs.
26 > *
27 > * Handlers are invoked through {@link TraceContext.runAsHandler} so causal
28 > * chains carry across awaits inside a handler. Note: because each handler's
29 > * deferred trace-reset fires as its own real macrotask, attribution can
30 > * drift slightly when many handlers fire in quick succession — accurate
31 > * enough for diagnostics, not for assertions.
32 > */
33 > export function createRecordingRealTimeApi(history: RecordedTimerEvent[]): TimeApi {
34 const realSetTimeout = realTimeApi.setTimeout;
35
src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts 31 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import type { ResourceWatchState } from './state.js';
11 > import type { ResourceWatchAction } from '../action-origin.generated.js';
12 >
13 > /**
14 > * Pure reducer for resource-watch state. Handles every
15 > * {@link ResourceWatchAction} variant.
16 > *
17 > * Watches are intentionally event-pass-through: change events are
18 > * delivered via `resourceWatch/changed` actions but the reducer keeps no
19 > * history of them. The state therefore tracks only the watch descriptor,
20 > * which is set at subscription time and never mutates over the life of
21 > * the watch.
22 > *
23 > * The reducer uses an `if`/else shape rather than `switch`/`softAssertNever`
24 > * because `ResourceWatchAction` currently has a single variant — TypeScript
25 > * does not narrow single-variant discriminated unions to `never` after the
26 > * sole case branch, so the usual exhaustiveness pattern would not compile.
27 > * Unknown action types degrade gracefully (mirroring `softAssertNever`'s
28 > * runtime behaviour) so a client speaking an older protocol stays correct
29 > * if the server adds new `resourceWatch/*` actions in a future version.
30 > */
31 > export function resourceWatchReducer(state: ResourceWatchState, action: ResourceWatchAction, log?: (msg: string) => void): ResourceWatchState {
32 if (action.type === ActionType.ResourceWatchChanged) {
33 return state;
src/vs/platform/agentHost/common/state/protocol/channels-root/reducer.ts 30 covered LOC · 8 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import type { RootState } from './state.js';
11 > import type { RootAction } from '../action-origin.generated.js';
12 > import { softAssertNever } from '../common/reducer-helpers.js';
13 >
14 > /**
15 > * Pure reducer for root state. Handles all {@link RootAction} variants.
16 > */
17 > export function rootReducer(state: RootState, action: RootAction, log?: (msg: string) => void): RootState {
18 > switch (action.type) { reducer.ts
19 > case ActionType.RootAgentsChanged:
20 return { ...state, agents: action.agents };
21 > reducer.ts
22 > case ActionType.RootActiveSessionsChanged:
23 > return { ...state, activeSessions: action.activeSessions }; reducer.ts
24 > reducer.ts
25 > case ActionType.RootTerminalsChanged:
26 return { ...state, terminals: action.terminals };
27 > reducer.ts
28 > case ActionType.RootConfigChanged:
29 if (!state.config) {
30 return state;
37 },
38 };
39 > reducer.ts
40 > default:
41 softAssertNever(action, log);
42 return state;
43 > } reducer.ts
44 > }
src/vs/base/common/errorMessage.ts 29 covered LOC · 6 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- errorMessage.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as arrays from './arrays.js';
7 > import * as types from './types.js';
8 > import * as nls from '../../nls.js';
9 > import { IAction } from './actions.js';
10 >
11 function exceptionToErrorMessage(exception: any, verbose: boolean): string {
12 if (verbose && (exception.stack || exception.stacktrace)) {
16 return detectSystemErrorMessage(exception);
17 }
19 function stackToString(stack: string[] | string | undefined): string | undefined {
20 if (Array.isArray(stack)) {
24 return stack;
25 }
27 function detectSystemErrorMessage(exception: any): string {
28
39 return exception.message || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
40 }
42 > /**
43 > * Tries to generate a human readable error message out of the error. If the verbose parameter
44 > * is set to true, the error message will include stacktrace details if provided.
45 > *
46 > * @returns A string containing the error message.
47 > */
48 > export function toErrorMessage(error: any = null, verbose: boolean = false): string {
49 if (!error) {
50 return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
88 return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
89 }
91 >
92 > export interface IErrorWithActions extends Error {
93 > actions: IAction[];
94 > }
95 >
96 > export function isErrorWithActions(obj: unknown): obj is IErrorWithActions {
97 const candidate = obj as IErrorWithActions | undefined;
98
99 return candidate instanceof Error && Array.isArray(candidate.actions);
100 }
102 > export function createErrorWithActions(messageOrError: string | Error, actions: IAction[]): IErrorWithActions {
103 let error: IErrorWithActions;
104 if (typeof messageOrError === 'string') {
src/vs/platform/agentHost/common/state/protocol/channels-annotations/reducer.ts 29 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import type { AnnotationEntry, Annotation, AnnotationsState } from './state.js';
11 > import type { AnnotationsAction } from '../action-origin.generated.js';
12 > import { softAssertNever } from '../common/reducer-helpers.js';
13 >
14 > /**
15 > * Pure reducer for annotations state. Handles every {@link AnnotationsAction}
16 > * variant.
17 > *
18 > * Per the spec, every annotations action is client-dispatchable; the reducer
19 > * runs identically on the client (optimistic, write-ahead) and the server. It
20 > * preserves the dispatch order of annotations (and of entries within an
21 > * annotation): new entries are appended; `*Set` actions with a matching id
22 > * replace in place, while actions whose target id is unknown are no-ops
23 > * (mirroring `changeset/fileRemoved` semantics). The single-entry
24 > * minimum invariant is enforced by producers, not the reducer — removing an
25 > * annotation's last entry via {@link AnnotationsEntryRemovedAction} (instead
26 > * of {@link AnnotationsRemovedAction}) would leave an empty annotation,
27 > * which is observable but not catastrophic.
28 > */
29 > export function annotationsReducer(state: AnnotationsState, action: AnnotationsAction, log?: (msg: string) => void): AnnotationsState {
30 switch (action.type) {
31 case ActionType.AnnotationsSet: {
src/vs/platform/agentHost/common/state/protocol/common/reducer-helpers.ts 29 covered LOC · 2 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer-helpers.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { IS_CLIENT_DISPATCHABLE, type RootAction, type ClientRootAction, type SessionAction, type ClientSessionAction, type TerminalAction, type ClientTerminalAction, type ChangesetAction, type ClientChangesetAction, type AnnotationsAction, type ClientAnnotationsAction } from '../action-origin.generated.js';
10 >
11 > /**
12 > * Soft assertion for exhaustiveness checking. Place in the `default` branch of
13 > * a switch on a discriminated union so the compiler errors when a new variant
14 > * is added but not handled.
15 > *
16 > * At runtime, logs a warning instead of throwing so that forward-compatible
17 > * clients receiving unknown actions from a newer server degrade gracefully.
18 > */
19 > export function softAssertNever(value: never, log?: (msg: string) => void): void {
20 const msg = `Unhandled action type: ${JSON.stringify(value)}`;
21 (log ?? console.warn)(msg);
22 }
24 > // ─── Dispatch Validation ─────────────────────────────────────────────────────
25 >
26 > /**
27 > * Type guard that checks whether an action may be dispatched by a client.
28 > *
29 > * Servers SHOULD call this to validate incoming `dispatchAction` requests
30 > * and reject any action the client is not allowed to originate.
31 > */
32 > export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | AnnotationsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientAnnotationsAction {
33 return IS_CLIENT_DISPATCHABLE[action.type];
34 }
src/vs/base/common/stopwatch.ts 25 covered LOC · 6 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- stopwatch.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > declare const globalThis: { performance: { now(): number } };
7 > const performanceNow = globalThis.performance.now.bind(globalThis.performance);
8 >
9 > export class StopWatch {
10 >
11 > private _startTime: number;
12 > private _stopTime: number;
13 >
14 > private readonly _now: () => number;
15 >
16 > public static create(highResolution?: boolean): StopWatch {
17 return new StopWatch(highResolution);
18 }
20 > constructor(highResolution?: boolean) {
21 this._now = highResolution === false ? Date.now : performanceNow;
22 this._startTime = this._now();
23 this._stopTime = -1;
24 }
26 > public stop(): void {
27 this._stopTime = this._now();
28 }
30 > public reset(): void {
31 this._startTime = this._now();
32 this._stopTime = -1;
33 }
35 > public elapsed(): number {
36 if (this._stopTime !== -1) {
37 return this._stopTime - this._startTime;
39 return this._now() - this._startTime;
40 }
41 > } stopwatch.ts
src/vs/platform/agentHost/common/state/protocol/channels-changeset/reducer.ts 24 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import { ChangesetStatus, ChangesetOperationStatus, type ChangesetState, type ChangesetFile, type ChangesetOperation } from './state.js';
11 > import type { ChangesetAction } from '../action-origin.generated.js';
12 > import { softAssertNever } from '../common/reducer-helpers.js';
13 >
14 > /**
15 > * Pure reducer for changeset state. Handles all {@link ChangesetAction}
16 > * variants.
17 > *
18 > * The reducer preserves a stable file order by appending new files via
19 > * {@link ActionType.ChangesetFileSet} when the id is unknown, and replacing in
20 > * place when it matches an existing entry. Per-file review lives on
21 > * {@link ChangesetFile.reviewed} and is toggled (per file, in batches) by the
22 > * client-dispatchable {@link ActionType.ChangesetFilesReviewChanged}.
23 > */
24 > export function changesetReducer(state: ChangesetState, action: ChangesetAction, log?: (msg: string) => void): ChangesetState {
25 switch (action.type) {
26 case ActionType.ChangesetStatusChanged: {
src/vs/platform/agentHost/common/state/sessionReducers.ts 20 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionReducers.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // Re-exports the protocol reducers and adds VS Code-specific helpers.
7 > // The actual reducer logic lives in the auto-generated protocol layer.
8 >
9 > // Re-export reducers from the protocol layer
10 > export { rootReducer, sessionReducer, chatReducer, changesetReducer, annotationsReducer, softAssertNever, isClientDispatchable } from './protocol/reducers.js';
11 >
12 > import { readToolCallMeta, type ToolKind } from '../meta/agentToolCallMeta.js';
13 > import type { ICompletedToolCall, ToolCallState } from './sessionState.js';
14 >
15 > /**
16 > * Extracts the VS Code-specific `toolKind` hint from a tool call's `_meta`
17 > * bag. This is not part of the protocol and is injected by the agent adapter
18 > * (e.g. `copilotEventMapper`).
19 > */
20 > export function getToolKind(tc: ToolCallState | ICompletedToolCall): ToolKind | undefined {
21 return readToolCallMeta(tc).toolKind;
22 }
src/vs/platform/agentHost/common/state/protocol/channels-terminal/reducer.ts 17 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > import { ActionType } from '../common/actions.js';
10 > import type { TerminalState, TerminalContentPart } from './state.js';
11 > import type { TerminalAction } from '../action-origin.generated.js';
12 > import { softAssertNever } from '../common/reducer-helpers.js';
13 >
14 > /**
15 > * Pure reducer for terminal state. Handles all {@link TerminalAction} variants.
16 > */
17 > export function terminalReducer(state: TerminalState, action: TerminalAction, log?: (msg: string) => void): TerminalState {
18 switch (action.type) {
19 case ActionType.TerminalData: {
src/vs/platform/agentHost/common/state/protocol/state.ts 17 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- state.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export * from './common/state.js';
10 > export * from './channels-root/state.js';
11 > export * from './channels-session/state.js';
12 > export * from './channels-chat/state.js';
13 > export * from './channels-terminal/state.js';
14 > export * from './channels-changeset/state.js';
15 > export * from './channels-annotations/state.js';
16 > export * from './channels-otlp/state.js';
17 > export * from './channels-resource-watch/state.js';
src/vs/platform/agentHost/common/state/protocol/actions.ts 16 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- actions.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export * from './common/actions.js';
10 > export * from './channels-root/actions.js';
11 > export * from './channels-session/actions.js';
12 > export * from './channels-chat/actions.js';
13 > export * from './channels-terminal/actions.js';
14 > export * from './channels-changeset/actions.js';
15 > export * from './channels-annotations/actions.js';
16 > export * from './channels-resource-watch/actions.js';
src/vs/platform/agentHost/common/state/protocol/reducers.ts 16 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- reducers.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export { rootReducer } from './channels-root/reducer.js';
10 > export { sessionReducer } from './channels-session/reducer.js';
11 > export { chatReducer } from './channels-chat/reducer.js';
12 > export { terminalReducer } from './channels-terminal/reducer.js';
13 > export { changesetReducer } from './channels-changeset/reducer.js';
14 > export { annotationsReducer } from './channels-annotations/reducer.js';
15 > export { resourceWatchReducer } from './channels-resource-watch/reducer.js';
16 > export { softAssertNever, isClientDispatchable } from './common/reducer-helpers.js';
src/vs/base/test/common/virtualScheduling/loggingTimeApi.ts 15 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- loggingTimeApi.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { TimeApi } from './timeApi.js';
7 >
8 > /**
9 > * Wrap `underlying` so that every call to `setTimeout`, `setInterval`,
10 > * `setImmediate` or `requestAnimationFrame` invokes `onCall` first.
11 > *
12 > * Useful for diagnostics — e.g. logging timer registrations made outside of
13 > * virtual time, to find leaks of real-time scheduling into a fixture.
14 > */
15 > export function createLoggingTimeApi(
16 underlying: TimeApi,
17 onCall: (name: string, stack: string | undefined, handler?: () => void) => void,
src/vs/platform/agentHost/common/state/protocol/commands.ts 15 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- commands.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export * from './common/commands.js';
10 > export * from './channels-root/commands.js';
11 > export * from './channels-session/commands.js';
12 > export * from './channels-chat/commands.js';
13 > export * from './channels-terminal/commands.js';
14 > export * from './channels-changeset/commands.js';
15 > export * from './channels-resource-watch/commands.js';
src/vs/platform/agentHost/common/state/protocol/notifications.ts 11 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- notifications.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export * from './common/notifications.js';
10 > export * from './channels-root/notifications.js';
11 > export * from './channels-otlp/notifications.js';
src/vs/base/common/functional.ts 9 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- functional.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > /**
7 > * Given a function, returns a function that is only calling that function once.
8 > */
9 > export function createSingleCallFunction<T extends Function>(this: unknown, fn: T, fnDidRunCallback?: () => void): T {
10 const _this = this;
11 let didCall = false;
src/vs/base/common/symbols.ts 9 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- symbols.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > /**
7 > * Can be passed into the Delayed to defer using a microtask
8 > * */
9 > export const MicrotaskDelay = Symbol('MicrotaskDelay');
src/vs/platform/agentHost/common/state/protocol/errors.ts 9 covered LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- errors.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // allow-any-unicode-comment-file
7 > // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts
8 >
9 > export * from './common/errors.js';