pi-ghost-autocomplete 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/metrics.d.ts CHANGED
@@ -7,12 +7,30 @@
7
7
  * and the parser whitelist (runtime, in `bench.ts`) derive from this array so
8
8
  * adding a mode means updating one place.
9
9
  */
10
- export declare const COMPLETION_MODES: readonly ["deterministic", "llm", "cache", "trie", "speculative-warm", "provisional", "profile-slash"];
10
+ export declare const COMPLETION_MODES: readonly ["deterministic", "llm", "cache", "trie", "speculative-warm", "provisional", "profile-slash", "command-slash"];
11
11
  export type CompletionMode = (typeof COMPLETION_MODES)[number];
12
12
  export type RequestResult = "produced" | "empty" | "rejected" | "aborted" | "error";
13
13
  export type AcceptanceOutcome = "accepted" | "accepted-partial" | "dismissed" | "stale" | "expired";
14
14
  export type QualityAttribution = "accepted" | "dismissed-attributed" | "unknown";
15
15
  export type RejectionReason = "stale-text" | "newline" | "too-long" | "assistant-phrasing" | "duplicate-suffix" | "markdown" | "empty" | "echo-token";
16
+ /**
17
+ * Diagnosed cause of an `empty` LLM verdict (session-51 todo #4). Without this
18
+ * the metrics can't distinguish a model that returned a bare newline from one
19
+ * whose output the sanitizer dropped, or a request that never reached the
20
+ * backend — that distinction previously needed synthetic probing.
21
+ *
22
+ * - `model-empty` — the model returned empty/whitespace-only text (the
23
+ * Mercury Edit "nothing to insert" case from session 51).
24
+ * - `sanitizer-null`— the model returned content but `sanitizeCompletion`
25
+ * rejected it (echo / filename-in-prose / markdown).
26
+ * - `http-error` — the request failed: missing key, non-2xx, malformed
27
+ * JSON, fetch throw, or timeout.
28
+ *
29
+ * Only set by providers that implement `consumeLastEmptyReason()`. Records from
30
+ * providers that don't (or that predate this field) omit it — bench buckets
31
+ * those as `unattributed`.
32
+ */
33
+ export type EmptyReason = "model-empty" | "sanitizer-null" | "http-error";
16
34
  export interface ProviderLatency {
17
35
  provider: string;
18
36
  latencyMs: number;
@@ -27,6 +45,8 @@ export interface MetricsRecord {
27
45
  winner?: string;
28
46
  result: RequestResult;
29
47
  rejectionReason?: RejectionReason;
48
+ /** When `result === "empty"`, the diagnosed cause (session-51 todo #4). */
49
+ emptyReason?: EmptyReason;
30
50
  completionLength?: number;
31
51
  shown?: boolean;
32
52
  acceptance?: AcceptanceOutcome;
@@ -4,7 +4,8 @@
4
4
  */
5
5
  import type { OpenAICompatChatProviderConfig } from "../config.js";
6
6
  import type { PreflightResult } from "./openai-compat-provider.js";
7
- import type { GhostProvider, GhostRequest } from "./types.js";
7
+ import { type GhostProvider, type GhostRequest } from "./types.js";
8
+ import type { EmptyReason } from "../metrics.js";
8
9
  /**
9
10
  * Chat-register provider for OpenAI-compatible `/chat/completions` backends
10
11
  * (e.g. Inception Mercury's conversational model). Sibling of
@@ -20,8 +21,12 @@ import type { GhostProvider, GhostRequest } from "./types.js";
20
21
  export declare class OpenAICompatChatProvider implements GhostProvider {
21
22
  private readonly config;
22
23
  readonly name: string;
24
+ /** Why the most recent `predict` resolved empty; consumed by the editor. */
25
+ private lastEmptyReason;
23
26
  constructor(config: OpenAICompatChatProviderConfig);
24
27
  get debounceMs(): number;
28
+ /** Return + clear the cause of the last empty completion (session-51 #4). */
29
+ consumeLastEmptyReason(): EmptyReason | null;
25
30
  /**
26
31
  * Validate the API key with a minimal chat request. A successful response
27
32
  * (even an empty completion) proves auth + connectivity.
@@ -3,7 +3,8 @@
3
3
  * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
4
  */
5
5
  import type { OpenAICompatProviderConfig } from "../config.js";
6
- import type { CandidateSet, GhostProvider, GhostRequest } from "./types.js";
6
+ import { type CandidateSet, type GhostProvider, type GhostRequest } from "./types.js";
7
+ import type { EmptyReason } from "../metrics.js";
7
8
  import { type PromptIntent } from "../intent.js";
8
9
  export interface PreflightResult {
9
10
  ok: boolean;
@@ -22,8 +23,17 @@ export interface PreflightResult {
22
23
  export declare class OpenAICompatProvider implements GhostProvider {
23
24
  private readonly config;
24
25
  readonly name: string;
26
+ /** Why the most recent `fireOne` resolved empty; consumed by the editor. */
27
+ private lastEmptyReason;
25
28
  constructor(config: OpenAICompatProviderConfig);
26
29
  get debounceMs(): number;
30
+ /**
31
+ * Return + clear the cause of the last empty completion (session-51 todo
32
+ * #4). With `parallelN > 1` this reflects the last FIM call to resolve;
33
+ * since the editor only consumes it when the whole candidate set is empty,
34
+ * every parallel call was empty and the reasons are typically identical.
35
+ */
36
+ consumeLastEmptyReason(): EmptyReason | null;
27
37
  /**
28
38
  * Validate the API key with a minimal FIM request. A successful
29
39
  * response (even an empty completion) proves auth + connectivity.
@@ -20,6 +20,7 @@
20
20
  * reachable chat backend never regresses.
21
21
  */
22
22
  import type { PromptIntent } from "../intent.js";
23
+ import type { EmptyReason } from "../metrics.js";
23
24
  import type { PreflightResult } from "./openai-compat-provider.js";
24
25
  import { type CandidateSet, type GhostProvider, type GhostRequest } from "./types.js";
25
26
  /** Pure routing predicate — exported for direct testing. */
@@ -28,6 +29,9 @@ export declare class RouterProvider implements GhostProvider {
28
29
  private readonly fim;
29
30
  private readonly chat;
30
31
  readonly name = "ghost-router";
32
+ /** Leg that served the most recent request — the one to query for the
33
+ * empty-cause diagnostic so it reflects the register that actually ran. */
34
+ private lastLeg;
31
35
  constructor(fim: GhostProvider, chat: GhostProvider | null);
32
36
  get debounceMs(): number;
33
37
  /**
@@ -38,8 +42,12 @@ export declare class RouterProvider implements GhostProvider {
38
42
  */
39
43
  legFor(intent: PromptIntent | undefined): "fim" | "chat";
40
44
  /** Choose the leg for this request. Chat leg only when present AND the
41
- * intent is natural-language; otherwise FIM. */
45
+ * intent is natural-language; otherwise FIM. Records the choice so
46
+ * `consumeLastEmptyReason` can forward to the leg that actually ran. */
42
47
  private leg;
48
+ /** Forward the empty-cause diagnostic to whichever leg last served a
49
+ * request. Null when no request has run yet or the leg doesn't track it. */
50
+ consumeLastEmptyReason(): EmptyReason | null;
43
51
  predict(req: GhostRequest): Promise<string | null>;
44
52
  /**
45
53
  * Route, then adapt the chosen leg to the multi-candidate surface. The
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { PreflightResult } from "./openai-compat-provider.js";
6
6
  import type { PromptIntent } from "../intent.js";
7
+ import type { EmptyReason } from "../metrics.js";
7
8
  export interface GhostRequest {
8
9
  editorText: string;
9
10
  editorTextBeforeCursor: string;
@@ -75,6 +76,16 @@ export interface GhostProvider {
75
76
  */
76
77
  predictStream?(req: GhostRequest): AsyncIterable<StreamDelta>;
77
78
  preflight?(): Promise<boolean | PreflightResult>;
79
+ /**
80
+ * Optional empty-cause diagnostic (session-51 todo #4). When the provider's
81
+ * last `predict`/`predictMulti` resolved to null/empty, returns *why*
82
+ * (`model-empty` / `sanitizer-null` / `http-error`) and clears it; returns
83
+ * null otherwise. The editor reads this only when it's about to log an
84
+ * `empty` result, so the value reflects the just-resolved request. Providers
85
+ * that don't implement it leave the metric's `emptyReason` unset
86
+ * (bench buckets those as `unattributed`).
87
+ */
88
+ consumeLastEmptyReason?(): EmptyReason | null;
78
89
  /**
79
90
  * PRD §M3g / §M7 capability flag — true iff the provider populates
80
91
  * per-token logprob values on streamed `StreamDelta` events (M7
@@ -87,6 +98,14 @@ export interface GhostProvider {
87
98
  */
88
99
  readonly supportsLogprobs?: boolean;
89
100
  }
101
+ /**
102
+ * Classify an empty completion from the raw model text. Shared by providers
103
+ * that run text through `sanitizeCompletion`: a whitespace-only raw response is
104
+ * the model declining to insert (`model-empty`); non-empty raw that sanitized
105
+ * to null/empty was dropped by the sanitizer (`sanitizer-null`). HTTP/transport
106
+ * failures are classified `http-error` at the call site, not here.
107
+ */
108
+ export declare function classifyEmpty(rawModelText: string): "model-empty" | "sanitizer-null";
90
109
  /**
91
110
  * Adapt a `predict()`-only provider into the `CandidateSet | null` shape.
92
111
  * Used by the editor to give every provider a uniform multi-candidate