pi-ghost-autocomplete 0.10.0 → 0.12.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/README.md +88 -3
- package/dist/bench-cli.js +1 -1
- package/dist/bench.js +1 -1
- package/dist/config.d.ts +33 -3
- package/dist/ghost-editor.d.ts +17 -1
- package/dist/ghost-utils.d.ts +19 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +1 -1
- package/dist/intent-classifier.d.ts +90 -0
- package/dist/intent.d.ts +12 -0
- package/dist/providers/pi-model-provider.d.ts +78 -0
- package/dist/replay-cli.js +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-License-Identifier: SSPL-1.0
|
|
3
|
+
* SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
|
|
4
|
+
*/
|
|
5
|
+
import type { CompletionRegister } from "./intent.js";
|
|
6
|
+
export interface IntentClassifierOptions {
|
|
7
|
+
/** Per-consult abort timeout. Default 400 ms (`PI_GHOST_INTENT_CLASSIFIER_TIMEOUT_MS`). */
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
/** Minimum answer confidence to apply a verdict. Default 0.6 (`PI_GHOST_INTENT_CLASSIFIER_MIN_CONFIDENCE`). */
|
|
10
|
+
minConfidence: number;
|
|
11
|
+
/** Verdict cache TTL. Default 60 s. */
|
|
12
|
+
ttlMs: number;
|
|
13
|
+
/** Verdict cache capacity (insertion-order eviction). Default 64. */
|
|
14
|
+
capacity: number;
|
|
15
|
+
/** Draft-prefix length the cache keys on. Default 32 chars. */
|
|
16
|
+
keyPrefixLen: number;
|
|
17
|
+
/**
|
|
18
|
+
* Debounce for the consult itself — a keystroke burst collapses into one
|
|
19
|
+
* classify call on the latest draft (the pending-set alone can't dedupe:
|
|
20
|
+
* every keystroke changes the cache key). Default 120 ms
|
|
21
|
+
* (`PI_GHOST_INTENT_CLASSIFIER_DEBOUNCE_MS`, clamped [0, 1000]).
|
|
22
|
+
*/
|
|
23
|
+
consultDebounceMs: number;
|
|
24
|
+
now?: () => number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Gate for consulting the classifier. Pure. True only when the heuristic
|
|
28
|
+
* marked the classification low-confidence AND the draft is long enough to
|
|
29
|
+
* classify meaningfully. Uses display width (string-width), matching the
|
|
30
|
+
* editor's completion gate — a 2-char CJK draft at the default `minChars=4`
|
|
31
|
+
* displays 4 columns wide, reaches the LLM, and must also be classifiable.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shouldConsultClassifier(confidence: "high" | "low" | undefined, draft: string, minChars: number): boolean;
|
|
34
|
+
/** Truncated, trimmed draft the verdict cache keys on. Exported for tests. */
|
|
35
|
+
export declare function verdictCacheKey(draft: string, keyPrefixLen: number): string;
|
|
36
|
+
/** The classification question handed to the classifier model. Pure. */
|
|
37
|
+
export declare function buildClassifierContext(draft: string): unknown;
|
|
38
|
+
/** Parse `PI_GHOST_INTENT_CLASSIFIER_TIMEOUT_MS` (clamped to [50, 5000]). */
|
|
39
|
+
export declare function classifierTimeoutFromEnv(raw: string | undefined, fallback: number): number;
|
|
40
|
+
/** Parse `PI_GHOST_INTENT_CLASSIFIER_MIN_CONFIDENCE` (clamped to [0, 1]). */
|
|
41
|
+
export declare function classifierMinConfidenceFromEnv(raw: string | undefined, fallback: number): number;
|
|
42
|
+
/** Parse `PI_GHOST_INTENT_CLASSIFIER_DEBOUNCE_MS` (clamped to [0, 1000]). */
|
|
43
|
+
export declare function classifierConsultDebounceFromEnv(raw: string | undefined, fallback: number): number;
|
|
44
|
+
export declare class IntentClassifier {
|
|
45
|
+
readonly spec: string;
|
|
46
|
+
private readonly registry;
|
|
47
|
+
private readonly opts;
|
|
48
|
+
private readonly classifyFn;
|
|
49
|
+
/** Resolved classifier model (any provider), or null when unresolvable. */
|
|
50
|
+
private modelPromise;
|
|
51
|
+
private readonly verdicts;
|
|
52
|
+
private readonly pending;
|
|
53
|
+
/** Consult debounce: keystroke bursts collapse into one classify call. */
|
|
54
|
+
private consultTimer;
|
|
55
|
+
private consultDraft;
|
|
56
|
+
private constructor();
|
|
57
|
+
/**
|
|
58
|
+
* Build from a config spec. Returns null when disabled (`off`/empty),
|
|
59
|
+
* when the host registry has no `classify` (pi < 0.99), or — for an
|
|
60
|
+
* explicit `provider/model-id` spec — when the typed lookup methods are
|
|
61
|
+
* absent. Model *resolution* stays lazy: `auto` needs the async
|
|
62
|
+
* `getAvailableOfType`, so an unavailable model degrades per-consult
|
|
63
|
+
* (no verdict) rather than disabling the feature at startup.
|
|
64
|
+
*/
|
|
65
|
+
static create(spec: string, registry: unknown, opts?: Partial<IntentClassifierOptions>): IntentClassifier | null;
|
|
66
|
+
/** Resolve (once) the classifier model the spec names or auto-picks. */
|
|
67
|
+
private resolveModel;
|
|
68
|
+
private doResolveModel;
|
|
69
|
+
/**
|
|
70
|
+
* Fire-and-forget consult request. Rapid calls (one per keystroke)
|
|
71
|
+
* collapse into a single classify on the latest draft after
|
|
72
|
+
* {@link IntentClassifierOptions.consultDebounceMs} of quiet — the
|
|
73
|
+
* pending-set alone cannot dedupe because every keystroke changes the
|
|
74
|
+
* cache key. Never blocks, never throws.
|
|
75
|
+
*/
|
|
76
|
+
maybeConsult(draft: string): void;
|
|
77
|
+
private fireConsult;
|
|
78
|
+
private storeVerdict;
|
|
79
|
+
/**
|
|
80
|
+
* Fresh verdict for the draft's cache key, or null. Prefix-aware: a
|
|
81
|
+
* verdict consulted on an earlier prefix of the same line applies as
|
|
82
|
+
* the line grows (and, symmetrically, after backspacing into it), so
|
|
83
|
+
* the classification of a line's opening sticks without re-consulting
|
|
84
|
+
* per keystroke. Strong-classification gating in the editor bounds any
|
|
85
|
+
* stale-prefix reuse.
|
|
86
|
+
*/
|
|
87
|
+
verdictFor(draft: string): CompletionRegister | null;
|
|
88
|
+
/** Number of live verdicts — surfaced in `/ghost` diagnostics. */
|
|
89
|
+
get verdictCount(): number;
|
|
90
|
+
}
|
package/dist/intent.d.ts
CHANGED
|
@@ -221,6 +221,18 @@ export interface IntentClassification {
|
|
|
221
221
|
intent: PromptIntent;
|
|
222
222
|
register: CompletionRegister;
|
|
223
223
|
proseReason?: ProseReason;
|
|
224
|
+
/**
|
|
225
|
+
* Heuristic signal strength (track C). `"low"` marks the two weak spots
|
|
226
|
+
* of the regex pipeline — single-line prose (persona split between
|
|
227
|
+
* `general-prose` and `agent-message` is guesswork below 4 lines of
|
|
228
|
+
* context) and the final fallback `instruction` (no verb, marker, or
|
|
229
|
+
* question shape matched — the draft could equally be code like
|
|
230
|
+
* `npm install`). The editor consults the optional intent classifier
|
|
231
|
+
* (pi ≥ 0.99 `registry.classify`, e.g. Jev or a llama.cpp classifier)
|
|
232
|
+
* only for `"low"` classifications, so strong signals never pay the
|
|
233
|
+
* round-trip. Absent means `"high"` (pre-track-C callers).
|
|
234
|
+
*/
|
|
235
|
+
confidence?: "high" | "low";
|
|
224
236
|
}
|
|
225
237
|
/**
|
|
226
238
|
* Detailed variant of `classifyPromptIntent`. Same classification order,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-License-Identifier: SSPL-1.0
|
|
3
|
+
* SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
|
|
4
|
+
*/
|
|
5
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { EmptyReason } from "../metrics.js";
|
|
7
|
+
import type { PiModelProviderConfig } from "../config.js";
|
|
8
|
+
import { type GhostProvider, type GhostRequest, type StreamDelta } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Pi-registry backend: complete through the session's own `ModelRegistry`.
|
|
11
|
+
*
|
|
12
|
+
* Unlike {@link CloudProvider} — which can only resolve models from pi-ai's
|
|
13
|
+
* *static builtin* catalog and must hand-resolve auth (skipping registries
|
|
14
|
+
* with a custom `baseUrl`) — this backend delegates both concerns to pi:
|
|
15
|
+
*
|
|
16
|
+
* - **Model resolution** via `registry.find(provider, id)` / first match in
|
|
17
|
+
* `registry.getAvailable()`. Works for dynamic providers (e.g. "radius")
|
|
18
|
+
* and any model registered by other extensions, none of which exist in
|
|
19
|
+
* the static catalog.
|
|
20
|
+
* - **Auth + endpoint** via `registry.complete()`, which resolves
|
|
21
|
+
* request-time auth (models.json keys, stored credentials, OAuth) and the
|
|
22
|
+
* provider's real `baseUrl` exactly as the main agent sees them
|
|
23
|
+
* (`ModelRuntime.prepareRequest`). No ghost API key needed.
|
|
24
|
+
*
|
|
25
|
+
* Available on pi 0.84+; streaming additionally requires 0.99's
|
|
26
|
+
* `registry.stream` (feature-detected, falls back to the pipeline's
|
|
27
|
+
* non-streaming `predict()` path on 0.84).
|
|
28
|
+
*/
|
|
29
|
+
export declare class PiModelProvider implements GhostProvider {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
private readonly registry;
|
|
32
|
+
private readonly config;
|
|
33
|
+
private readonly streamFn;
|
|
34
|
+
private resolved;
|
|
35
|
+
private resolvedSpec;
|
|
36
|
+
private lastEmpty;
|
|
37
|
+
/**
|
|
38
|
+
* Streaming variant — attached in the constructor **only** when the
|
|
39
|
+
* registry exposes `stream()` (pi ≥ 0.99). The request pipeline selects
|
|
40
|
+
* the streaming path whenever `predictStream` is present, so a class
|
|
41
|
+
* method would make every host look streaming-capable and silently starve
|
|
42
|
+
* pi 0.84–0.98 of ghosts instead of falling back to `predict()`.
|
|
43
|
+
*/
|
|
44
|
+
predictStream?: (req: GhostRequest) => AsyncIterable<StreamDelta>;
|
|
45
|
+
constructor(config: PiModelProviderConfig, registry: ModelRegistry);
|
|
46
|
+
get debounceMs(): number;
|
|
47
|
+
consumeLastEmptyReason(): EmptyReason | null;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve `modelSpec` (`"provider/model-id"` or bare `"model-id"`) to a
|
|
50
|
+
* registry model. Successful lookups are cached per spec; failures retry
|
|
51
|
+
* on the next call (the registry may still be loading models.json early
|
|
52
|
+
* in a session, and a failed scan of the model list is cheap).
|
|
53
|
+
*/
|
|
54
|
+
private resolveModel;
|
|
55
|
+
/**
|
|
56
|
+
* One completion through the registry. Auth, headers, and endpoint are
|
|
57
|
+
* resolved by pi at request time; we only supply sampling knobs, the
|
|
58
|
+
* abort signal, and the SEC-3 timeout.
|
|
59
|
+
*/
|
|
60
|
+
predict(req: GhostRequest): Promise<string | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Streaming implementation behind the conditionally-attached
|
|
63
|
+
* {@link predictStream} — only reachable when the registry exposes
|
|
64
|
+
* `stream()` (pi ≥ 0.99; see {@link StreamingRegistry}). Mirrors
|
|
65
|
+
* {@link CloudProvider.predictStream}: sanitize once on the final
|
|
66
|
+
* assembled text, never per delta.
|
|
67
|
+
*/
|
|
68
|
+
private streamPredict;
|
|
69
|
+
/**
|
|
70
|
+
* Preflight for /ghost diagnostics and router chat-leg warnings: resolves
|
|
71
|
+
* the spec and (when the registry supports it) checks that the model's
|
|
72
|
+
* provider has configured auth.
|
|
73
|
+
*/
|
|
74
|
+
preflight(): Promise<{
|
|
75
|
+
ok: boolean;
|
|
76
|
+
message: string;
|
|
77
|
+
}>;
|
|
78
|
+
}
|