pi-ghost-autocomplete 0.3.1 → 0.3.2
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 +101 -6
- package/dist/bench-cli.js +81 -5
- package/dist/bench-cli.js.map +1 -1
- package/dist/bench.d.ts +130 -2
- package/dist/bench.d.ts.map +1 -1
- package/dist/bench.js +285 -4
- package/dist/bench.js.map +1 -1
- package/dist/cache.d.ts +124 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +173 -0
- package/dist/cache.js.map +1 -0
- package/dist/candidate-rerank.d.ts +57 -0
- package/dist/candidate-rerank.d.ts.map +1 -0
- package/dist/candidate-rerank.js +78 -0
- package/dist/candidate-rerank.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +148 -18
- package/dist/config.js.map +1 -1
- package/dist/context-aggregator.d.ts +14 -5
- package/dist/context-aggregator.d.ts.map +1 -1
- package/dist/context-aggregator.js +38 -9
- package/dist/context-aggregator.js.map +1 -1
- package/dist/context-selector.d.ts +128 -0
- package/dist/context-selector.d.ts.map +1 -0
- package/dist/context-selector.js +300 -0
- package/dist/context-selector.js.map +1 -0
- package/dist/cycle-manager.d.ts +102 -0
- package/dist/cycle-manager.d.ts.map +1 -0
- package/dist/cycle-manager.js +168 -0
- package/dist/cycle-manager.js.map +1 -0
- package/dist/edit-history-ring.d.ts +148 -0
- package/dist/edit-history-ring.d.ts.map +1 -0
- package/dist/edit-history-ring.js +262 -0
- package/dist/edit-history-ring.js.map +1 -0
- package/dist/entropy-stop.d.ts +102 -0
- package/dist/entropy-stop.d.ts.map +1 -0
- package/dist/entropy-stop.js +141 -0
- package/dist/entropy-stop.js.map +1 -0
- package/dist/eval-dataset.d.ts +66 -0
- package/dist/eval-dataset.d.ts.map +1 -0
- package/dist/eval-dataset.js +127 -0
- package/dist/eval-dataset.js.map +1 -0
- package/dist/fast-path-engine.d.ts +286 -0
- package/dist/fast-path-engine.d.ts.map +1 -0
- package/dist/fast-path-engine.js +341 -0
- package/dist/fast-path-engine.js.map +1 -0
- package/dist/fuzzy-matcher.d.ts +8 -0
- package/dist/fuzzy-matcher.d.ts.map +1 -1
- package/dist/fuzzy-matcher.js +5 -3
- package/dist/fuzzy-matcher.js.map +1 -1
- package/dist/ghost-editor.d.ts +321 -4
- package/dist/ghost-editor.d.ts.map +1 -1
- package/dist/ghost-editor.js +1190 -144
- package/dist/ghost-editor.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -2
- package/dist/index.js.map +1 -1
- package/dist/keystroke-timer.d.ts +59 -0
- package/dist/keystroke-timer.d.ts.map +1 -0
- package/dist/keystroke-timer.js +94 -0
- package/dist/keystroke-timer.js.map +1 -0
- package/dist/metrics-builder.d.ts +71 -0
- package/dist/metrics-builder.d.ts.map +1 -0
- package/dist/metrics-builder.js +71 -0
- package/dist/metrics-builder.js.map +1 -0
- package/dist/metrics.d.ts +99 -2
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +131 -17
- package/dist/metrics.js.map +1 -1
- package/dist/path-matcher.d.ts +9 -0
- package/dist/path-matcher.d.ts.map +1 -1
- package/dist/path-matcher.js +5 -2
- package/dist/path-matcher.js.map +1 -1
- package/dist/profile-store.d.ts +82 -0
- package/dist/profile-store.d.ts.map +1 -0
- package/dist/profile-store.js +199 -0
- package/dist/profile-store.js.map +1 -0
- package/dist/profile.d.ts +185 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +434 -0
- package/dist/profile.js.map +1 -0
- package/dist/providers/cloud-provider.d.ts +10 -1
- package/dist/providers/cloud-provider.d.ts.map +1 -1
- package/dist/providers/cloud-provider.js +71 -14
- package/dist/providers/cloud-provider.js.map +1 -1
- package/dist/providers/local-provider.d.ts +8 -1
- package/dist/providers/local-provider.d.ts.map +1 -1
- package/dist/providers/local-provider.js +15 -3
- package/dist/providers/local-provider.js.map +1 -1
- package/dist/providers/openai-compat-provider.d.ts +11 -1
- package/dist/providers/openai-compat-provider.d.ts.map +1 -1
- package/dist/providers/openai-compat-provider.js +93 -6
- package/dist/providers/openai-compat-provider.js.map +1 -1
- package/dist/providers/openai-completions-runner.d.ts +25 -1
- package/dist/providers/openai-completions-runner.d.ts.map +1 -1
- package/dist/providers/openai-completions-runner.js +109 -14
- package/dist/providers/openai-completions-runner.js.map +1 -1
- package/dist/providers/race.d.ts +58 -1
- package/dist/providers/race.d.ts.map +1 -1
- package/dist/providers/race.js +317 -4
- package/dist/providers/race.js.map +1 -1
- package/dist/providers/truncate.d.ts.map +1 -1
- package/dist/providers/truncate.js +20 -2
- package/dist/providers/truncate.js.map +1 -1
- package/dist/providers/types.d.ts +96 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +59 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/provisional-manager.d.ts +93 -0
- package/dist/provisional-manager.d.ts.map +1 -0
- package/dist/provisional-manager.js +98 -0
- package/dist/provisional-manager.js.map +1 -0
- package/dist/provisional.d.ts +150 -0
- package/dist/provisional.d.ts.map +1 -0
- package/dist/provisional.js +238 -0
- package/dist/provisional.js.map +1 -0
- package/dist/regret-tracker.d.ts +84 -0
- package/dist/regret-tracker.d.ts.map +1 -0
- package/dist/regret-tracker.js +145 -0
- package/dist/regret-tracker.js.map +1 -0
- package/dist/replay-cli.d.ts +13 -0
- package/dist/replay-cli.d.ts.map +1 -0
- package/dist/replay-cli.js +167 -0
- package/dist/replay-cli.js.map +1 -0
- package/dist/replay.d.ts +107 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +225 -0
- package/dist/replay.js.map +1 -0
- package/dist/reranker.d.ts +181 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +324 -0
- package/dist/reranker.js.map +1 -0
- package/dist/session-trie.d.ts +103 -0
- package/dist/session-trie.d.ts.map +1 -0
- package/dist/session-trie.js +280 -0
- package/dist/session-trie.js.map +1 -0
- package/dist/speculative.d.ts +74 -0
- package/dist/speculative.d.ts.map +1 -0
- package/dist/speculative.js +107 -0
- package/dist/speculative.js.map +1 -0
- package/dist/tool-summarize.d.ts +40 -0
- package/dist/tool-summarize.d.ts.map +1 -0
- package/dist/tool-summarize.js +245 -0
- package/dist/tool-summarize.js.map +1 -0
- package/dist/trie-sanitize.d.ts +22 -0
- package/dist/trie-sanitize.d.ts.map +1 -0
- package/dist/trie-sanitize.js +142 -0
- package/dist/trie-sanitize.js.map +1 -0
- package/dist/versions.d.ts +23 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +31 -0
- package/dist/versions.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisional ghost lifecycle manager. Owns:
|
|
3
|
+
* - the timer that fires `pickProvisional` after `provisionalDelayMs`
|
|
4
|
+
* - the displayed-pick state (text, source, render timestamp)
|
|
5
|
+
* - cancel + reconcile-against-LLM helpers
|
|
6
|
+
*
|
|
7
|
+
* Extracted from `GhostEditor.requestPrediction` as refactor #57 slice 5.
|
|
8
|
+
* The editor supplies callbacks for the editor-state predicates and
|
|
9
|
+
* render side-effect; the manager owns timing + state transitions.
|
|
10
|
+
*
|
|
11
|
+
* Tests cover arm/cancel/clear/reconcile in isolation without spinning
|
|
12
|
+
* up a full editor. Editor wiring is a thin delegation layer.
|
|
13
|
+
*/
|
|
14
|
+
import { pickProvisional, provisionalDelayMs, type ProvisionalPick, type PickProvisionalInput, type SwapDecision } from "./provisional.js";
|
|
15
|
+
export interface ArmInput {
|
|
16
|
+
prefix: string;
|
|
17
|
+
picker: () => ProvisionalPick | null;
|
|
18
|
+
delayMs: number;
|
|
19
|
+
/**
|
|
20
|
+
* Predicate run inside the timer callback right before rendering. Must
|
|
21
|
+
* verify all editor-state invariants: not disposed, in-flight request
|
|
22
|
+
* is still ours, no ghost already shown, text + cursor unchanged. The
|
|
23
|
+
* manager calls `picker()` and `onShow` only when this returns true.
|
|
24
|
+
*/
|
|
25
|
+
canRender: () => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Called when a provisional pick is selected and ready to render. The
|
|
28
|
+
* editor wires this to `applyServedGhost(...)`. The manager has already
|
|
29
|
+
* stamped `shown` + `shownAt` by the time this fires.
|
|
30
|
+
*/
|
|
31
|
+
onShow: (pick: ProvisionalPick) => void;
|
|
32
|
+
}
|
|
33
|
+
export interface ReconcileInput {
|
|
34
|
+
llm: {
|
|
35
|
+
text: string;
|
|
36
|
+
score: number;
|
|
37
|
+
} | null;
|
|
38
|
+
swapMarginEnvRaw: string | undefined;
|
|
39
|
+
now: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ReconcileResult {
|
|
42
|
+
decision: SwapDecision;
|
|
43
|
+
/** True iff a provisional pick was rendered at reconcile-time. Lets
|
|
44
|
+
* callers distinguish "had-provisional + LLM kept" (discard LLM) from
|
|
45
|
+
* "no-provisional + LLM produced" (normal install path) — both yield
|
|
46
|
+
* `displaced === null`. */
|
|
47
|
+
hadProvisional: boolean;
|
|
48
|
+
/** Non-null iff a provisional was displayed and the LLM is replacing
|
|
49
|
+
* or extending it. Carries the metric fields the editor stamps on
|
|
50
|
+
* the pending metric. Null when keep-decision wins OR no provisional
|
|
51
|
+
* was ever shown. */
|
|
52
|
+
displaced: {
|
|
53
|
+
provisionalSwapMs: number;
|
|
54
|
+
provisionalWasCorrect: boolean;
|
|
55
|
+
swapWasVisualJump: boolean;
|
|
56
|
+
} | null;
|
|
57
|
+
}
|
|
58
|
+
export declare class ProvisionalManager {
|
|
59
|
+
private timer;
|
|
60
|
+
private shown;
|
|
61
|
+
private shownAt;
|
|
62
|
+
/** True iff a provisional is currently rendered. Drives streaming-gate
|
|
63
|
+
* in the editor (no incremental paint over a provisional). */
|
|
64
|
+
get isShown(): boolean;
|
|
65
|
+
get currentPick(): ProvisionalPick | null;
|
|
66
|
+
/**
|
|
67
|
+
* Schedule a provisional pick. Cancels any prior pending timer; does
|
|
68
|
+
* NOT clear an already-displayed pick (callers must `clear()` if they
|
|
69
|
+
* want a fresh slate before re-arming).
|
|
70
|
+
*/
|
|
71
|
+
arm(input: ArmInput): void;
|
|
72
|
+
/** Cancel a pending timer without touching the displayed pick. Safe
|
|
73
|
+
* to call when no timer is armed. */
|
|
74
|
+
cancelTimer(): void;
|
|
75
|
+
/** Reset all manager state (timer + displayed pick). Called by the
|
|
76
|
+
* editor's `clearGhost` and on dispose. */
|
|
77
|
+
clear(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Reconcile a freshly-arrived LLM result against the currently-displayed
|
|
80
|
+
* provisional. Returns the swap decision + metric fields for the
|
|
81
|
+
* displaced provisional (null when no provisional was shown).
|
|
82
|
+
*
|
|
83
|
+
* Clears the displayed pick after reconciliation. The caller decides
|
|
84
|
+
* whether to render the resulting text — for `keep`, the editor
|
|
85
|
+
* discards the LLM result entirely and emits `produced` without
|
|
86
|
+
* installing a shownMetric.
|
|
87
|
+
*/
|
|
88
|
+
reconcile(input: ReconcileInput): ReconcileResult;
|
|
89
|
+
}
|
|
90
|
+
/** Re-export for editor convenience — keeps the import surface tight. */
|
|
91
|
+
export { pickProvisional, provisionalDelayMs };
|
|
92
|
+
export type { ProvisionalPick, PickProvisionalInput };
|
|
93
|
+
//# sourceMappingURL=provisional-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisional-manager.d.ts","sourceRoot":"","sources":["../src/provisional-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAGN,eAAe,EACf,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB;;;;OAIG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB;;;gCAG4B;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB;;;0BAGsB;IACtB,SAAS,EAAE;QACV,iBAAiB,EAAE,MAAM,CAAC;QAC1B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,iBAAiB,EAAE,OAAO,CAAC;KAC3B,GAAG,IAAI,CAAC;CACT;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,OAAO,CAAK;IAEpB;mEAC+D;IAC/D,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,WAAW,IAAI,eAAe,GAAG,IAAI,CAExC;IAED;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAa1B;0CACsC;IACtC,WAAW,IAAI,IAAI;IAOnB;gDAC4C;IAC5C,KAAK,IAAI,IAAI;IAMb;;;;;;;;;OASG;IACH,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe;CAwBjD;AAED,yEAAyE;AACzE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisional ghost lifecycle manager. Owns:
|
|
3
|
+
* - the timer that fires `pickProvisional` after `provisionalDelayMs`
|
|
4
|
+
* - the displayed-pick state (text, source, render timestamp)
|
|
5
|
+
* - cancel + reconcile-against-LLM helpers
|
|
6
|
+
*
|
|
7
|
+
* Extracted from `GhostEditor.requestPrediction` as refactor #57 slice 5.
|
|
8
|
+
* The editor supplies callbacks for the editor-state predicates and
|
|
9
|
+
* render side-effect; the manager owns timing + state transitions.
|
|
10
|
+
*
|
|
11
|
+
* Tests cover arm/cancel/clear/reconcile in isolation without spinning
|
|
12
|
+
* up a full editor. Editor wiring is a thin delegation layer.
|
|
13
|
+
*/
|
|
14
|
+
import { decideSwap, parseProvisionalPreferEnv, pickProvisional, provisionalDelayMs, } from "./provisional.js";
|
|
15
|
+
export class ProvisionalManager {
|
|
16
|
+
timer = null;
|
|
17
|
+
shown = null;
|
|
18
|
+
shownAt = 0;
|
|
19
|
+
/** True iff a provisional is currently rendered. Drives streaming-gate
|
|
20
|
+
* in the editor (no incremental paint over a provisional). */
|
|
21
|
+
get isShown() {
|
|
22
|
+
return this.shown !== null;
|
|
23
|
+
}
|
|
24
|
+
get currentPick() {
|
|
25
|
+
return this.shown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Schedule a provisional pick. Cancels any prior pending timer; does
|
|
29
|
+
* NOT clear an already-displayed pick (callers must `clear()` if they
|
|
30
|
+
* want a fresh slate before re-arming).
|
|
31
|
+
*/
|
|
32
|
+
arm(input) {
|
|
33
|
+
this.cancelTimer();
|
|
34
|
+
this.timer = setTimeout(() => {
|
|
35
|
+
this.timer = null;
|
|
36
|
+
if (!input.canRender())
|
|
37
|
+
return;
|
|
38
|
+
const pick = input.picker();
|
|
39
|
+
if (!pick)
|
|
40
|
+
return;
|
|
41
|
+
this.shown = pick;
|
|
42
|
+
this.shownAt = Date.now();
|
|
43
|
+
input.onShow(pick);
|
|
44
|
+
}, input.delayMs);
|
|
45
|
+
}
|
|
46
|
+
/** Cancel a pending timer without touching the displayed pick. Safe
|
|
47
|
+
* to call when no timer is armed. */
|
|
48
|
+
cancelTimer() {
|
|
49
|
+
if (this.timer) {
|
|
50
|
+
clearTimeout(this.timer);
|
|
51
|
+
this.timer = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Reset all manager state (timer + displayed pick). Called by the
|
|
55
|
+
* editor's `clearGhost` and on dispose. */
|
|
56
|
+
clear() {
|
|
57
|
+
this.cancelTimer();
|
|
58
|
+
this.shown = null;
|
|
59
|
+
this.shownAt = 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reconcile a freshly-arrived LLM result against the currently-displayed
|
|
63
|
+
* provisional. Returns the swap decision + metric fields for the
|
|
64
|
+
* displaced provisional (null when no provisional was shown).
|
|
65
|
+
*
|
|
66
|
+
* Clears the displayed pick after reconciliation. The caller decides
|
|
67
|
+
* whether to render the resulting text — for `keep`, the editor
|
|
68
|
+
* discards the LLM result entirely and emits `produced` without
|
|
69
|
+
* installing a shownMetric.
|
|
70
|
+
*/
|
|
71
|
+
reconcile(input) {
|
|
72
|
+
this.cancelTimer();
|
|
73
|
+
const hadProvisional = this.shown !== null;
|
|
74
|
+
const decision = decideSwap({
|
|
75
|
+
provisional: this.shown,
|
|
76
|
+
llm: input.llm,
|
|
77
|
+
swapMargin: parseProvisionalPreferEnv(input.swapMarginEnvRaw),
|
|
78
|
+
});
|
|
79
|
+
let displaced = null;
|
|
80
|
+
if (hadProvisional && decision.action !== "keep") {
|
|
81
|
+
displaced = {
|
|
82
|
+
provisionalSwapMs: Math.max(0, input.now - this.shownAt),
|
|
83
|
+
provisionalWasCorrect: decision.action === "extend",
|
|
84
|
+
swapWasVisualJump: decision.action === "swap",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// `keep` leaves the provisional in place visually; clear the state
|
|
88
|
+
// anyway so a subsequent request starts fresh. The editor's
|
|
89
|
+
// `ghostText` field still carries the rendered provisional text
|
|
90
|
+
// from the original `applyServedGhost` call.
|
|
91
|
+
this.shown = null;
|
|
92
|
+
this.shownAt = 0;
|
|
93
|
+
return { decision, hadProvisional, displaced };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Re-export for editor convenience — keeps the import surface tight. */
|
|
97
|
+
export { pickProvisional, provisionalDelayMs };
|
|
98
|
+
//# sourceMappingURL=provisional-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisional-manager.js","sourceRoot":"","sources":["../src/provisional-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACN,UAAU,EACV,yBAAyB,EACzB,eAAe,EACf,kBAAkB,GAIlB,MAAM,kBAAkB,CAAC;AA6C1B,MAAM,OAAO,kBAAkB;IACtB,KAAK,GAA0B,IAAI,CAAC;IACpC,KAAK,GAA2B,IAAI,CAAC;IACrC,OAAO,GAAG,CAAC,CAAC;IAEpB;mEAC+D;IAC/D,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,KAAe;QAClB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;gBAAE,OAAO;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC1B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IAED;0CACsC;IACtC,WAAW;QACV,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IAED;gDAC4C;IAC5C,KAAK;QACJ,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;IAClB,CAAC;IAED;;;;;;;;;OASG;IACH,SAAS,CAAC,KAAqB;QAC9B,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;QAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC;YAC3B,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,UAAU,EAAE,yBAAyB,CAAC,KAAK,CAAC,gBAAgB,CAAC;SAC7D,CAAC,CAAC;QACH,IAAI,SAAS,GAAiC,IAAI,CAAC;QACnD,IAAI,cAAc,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAClD,SAAS,GAAG;gBACX,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;gBACxD,qBAAqB,EAAE,QAAQ,CAAC,MAAM,KAAK,QAAQ;gBACnD,iBAAiB,EAAE,QAAQ,CAAC,MAAM,KAAK,MAAM;aAC7C,CAAC;QACH,CAAC;QACD,mEAAmE;QACnE,4DAA4D;QAC5D,gEAAgE;QAChE,6CAA6C;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACjB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;IAChD,CAAC;CACD;AAED,yEAAyE;AACzE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisional ghost — fast local guess shown while the LLM is in-flight.
|
|
3
|
+
* PRD §M3.5b.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: when the synchronous fast paths (cache, trie, speculative
|
|
6
|
+
* warm, profile-slash) all miss and the user is waiting on a network
|
|
7
|
+
* round-trip, fill the >150 ms gap with the next-best local guess so the
|
|
8
|
+
* user isn't staring at an empty prompt. Even an imperfect provisional
|
|
9
|
+
* is better than no ghost at all: typing past it dismisses without cost,
|
|
10
|
+
* and an LLM result that matches the provisional prefix collapses to a
|
|
11
|
+
* silent append rather than a visual swap.
|
|
12
|
+
*
|
|
13
|
+
* Pure functions only — no I/O, no editor state, no timers. Callers
|
|
14
|
+
* (the editor) own the timer that gates when this runs.
|
|
15
|
+
*
|
|
16
|
+
* Pipeline:
|
|
17
|
+
* 1. `pickProvisional(...)` — consult trie, profile.acceptByPrefix
|
|
18
|
+
* (M4), and a "common-token" fallback. Score in [0, 1]; null
|
|
19
|
+
* when nothing scores above `MIN_CONFIDENCE`.
|
|
20
|
+
* 2. `decideSwap(provisionalText, llmText)` — given the original
|
|
21
|
+
* provisional and the eventually-arriving LLM result, decide:
|
|
22
|
+
* "keep + extend", "swap", or "discard llm".
|
|
23
|
+
*
|
|
24
|
+
* The two-step shape mirrors the M3c reranker: pickProvisional is the
|
|
25
|
+
* displayed-ghost source; decideSwap is the reconciler when truth
|
|
26
|
+
* (the LLM result) arrives.
|
|
27
|
+
*/
|
|
28
|
+
import type { SessionTrie } from "./session-trie.js";
|
|
29
|
+
import type { ProfileStore } from "./profile-store.js";
|
|
30
|
+
import type { LastGhostsRing } from "./speculative.js";
|
|
31
|
+
/** PRD §M3.5b — provisional accepts down to 0.4 (vs M3c.1's 0.55). */
|
|
32
|
+
export declare const MIN_CONFIDENCE = 0.4;
|
|
33
|
+
/** Score margin LLM must exceed provisional to trigger a swap. */
|
|
34
|
+
export declare const SWAP_MARGIN = 0.1;
|
|
35
|
+
/**
|
|
36
|
+
* Anti-flicker swap margin used when `PI_GHOST_PROVISIONAL_PREFER=1` is set.
|
|
37
|
+
* Production autocomplete systems (Cursor, Copilot) bias toward keeping the
|
|
38
|
+
* already-displayed candidate unless the LLM is dramatically better, since
|
|
39
|
+
* any visible swap is a perceived flicker on slow terminals.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PREFER_PROVISIONAL_SWAP_MARGIN = 0.3;
|
|
42
|
+
/** Prefix-match ratio above which we extend rather than swap. */
|
|
43
|
+
export declare const KEEP_PREFIX_MATCH = 0.8;
|
|
44
|
+
export type ProvisionalSource = "trie" | "profile-accept" | "session-cache";
|
|
45
|
+
export interface ProvisionalPick {
|
|
46
|
+
text: string;
|
|
47
|
+
score: number;
|
|
48
|
+
source: ProvisionalSource;
|
|
49
|
+
}
|
|
50
|
+
export interface PickProvisionalInput {
|
|
51
|
+
/** Active line text up to the cursor. Trie keys + profile lookups
|
|
52
|
+
* use the last single-line slice. */
|
|
53
|
+
prefix: string;
|
|
54
|
+
/** Session trie — local fast path. */
|
|
55
|
+
trie: SessionTrie | null;
|
|
56
|
+
/** Profile store — cross-session learned signal. */
|
|
57
|
+
profile: ProfileStore | null;
|
|
58
|
+
/** Speculative-warm ring — tertiary source. Same-session LLM ghosts
|
|
59
|
+
* for adjacent prefixes are high-precision provisionals because the
|
|
60
|
+
* provider+context fingerprints already matched at insert-time. */
|
|
61
|
+
lastGhosts?: LastGhostsRing | null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Walk the local fast paths in priority order and return the first that
|
|
65
|
+
* meets `MIN_CONFIDENCE`. Trie is consulted first (user's own confirmed
|
|
66
|
+
* accepts in this session — highest precision). Profile.acceptByPrefix
|
|
67
|
+
* second (cross-session frequency for the exact-prefix hash). Returns
|
|
68
|
+
* null when nothing qualifies.
|
|
69
|
+
*/
|
|
70
|
+
export declare function pickProvisional(input: PickProvisionalInput): ProvisionalPick | null;
|
|
71
|
+
/**
|
|
72
|
+
* Compute the prefix-match ratio between two strings: number of leading
|
|
73
|
+
* characters they share, divided by `max(a.length, b.length)`. Used by
|
|
74
|
+
* `decideSwap` to detect "LLM extends provisional" cases where no
|
|
75
|
+
* visual jump is needed.
|
|
76
|
+
*/
|
|
77
|
+
export declare function prefixMatchRatio(a: string, b: string): number;
|
|
78
|
+
export type SwapDecision = {
|
|
79
|
+
action: "extend";
|
|
80
|
+
text: string;
|
|
81
|
+
tail: string;
|
|
82
|
+
} | {
|
|
83
|
+
action: "swap";
|
|
84
|
+
text: string;
|
|
85
|
+
reason: "score-margin" | "no-provisional";
|
|
86
|
+
} | {
|
|
87
|
+
action: "keep";
|
|
88
|
+
text: string;
|
|
89
|
+
reason: "lossy-llm" | "identical";
|
|
90
|
+
};
|
|
91
|
+
export interface DecideSwapInput {
|
|
92
|
+
provisional: ProvisionalPick | null;
|
|
93
|
+
llm: {
|
|
94
|
+
text: string;
|
|
95
|
+
score: number;
|
|
96
|
+
} | null;
|
|
97
|
+
/**
|
|
98
|
+
* Optional override for the LLM-vs-provisional score margin. Defaults
|
|
99
|
+
* to `SWAP_MARGIN`. Callers set this to `PREFER_PROVISIONAL_SWAP_MARGIN`
|
|
100
|
+
* when `PI_GHOST_PROVISIONAL_PREFER=1` is in force.
|
|
101
|
+
*/
|
|
102
|
+
swapMargin?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Reconcile the LLM result against the provisional. Returns the action
|
|
106
|
+
* the editor should take + the text to display. PRD §M3.5b decision
|
|
107
|
+
* matrix:
|
|
108
|
+
* - prefix-match ≥ KEEP_PREFIX_MATCH → extend (keep provisional,
|
|
109
|
+
* append LLM tail).
|
|
110
|
+
* - LLM score margin > swapMargin (default SWAP_MARGIN) → swap
|
|
111
|
+
* (visual jump, log `provisionalSwapMs`, `provisionalWasCorrect=false`).
|
|
112
|
+
* - LLM produced nothing → keep provisional (silent).
|
|
113
|
+
* - LLM differs but score not better enough → keep provisional
|
|
114
|
+
* (PRD: "rare; mostly when provisional was a high-quality trie hit").
|
|
115
|
+
*/
|
|
116
|
+
export declare function decideSwap(input: DecideSwapInput): SwapDecision;
|
|
117
|
+
/**
|
|
118
|
+
* Convert a `[0, 1]` score (or raw weight) to the LLM-side comparable
|
|
119
|
+
* scale. With M3c rerank scores in `[0, 1]` and confidence-gate default
|
|
120
|
+
* 0.55, this is just an identity pass for clarity at call sites.
|
|
121
|
+
*/
|
|
122
|
+
export declare function llmScoreFromRerank(reranked: {
|
|
123
|
+
score: number;
|
|
124
|
+
}): number;
|
|
125
|
+
/** Parse `PI_GHOST_PROVISIONAL`. Returns false when set to "off". */
|
|
126
|
+
export declare function parseProvisionalEnv(raw: string | undefined): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Parse `PI_GHOST_PROVISIONAL_PREFER`. Returns the swap-margin for
|
|
129
|
+
* {@link decideSwap}. Default is `PREFER_PROVISIONAL_SWAP_MARGIN` (anti-
|
|
130
|
+
* flicker bias) — production autocomplete systems (Cursor, Copilot, Gmail
|
|
131
|
+
* Smart Compose) bias toward keeping the displayed candidate unless the
|
|
132
|
+
* LLM is dramatically better, because any visible swap registers as a
|
|
133
|
+
* perceived flicker. Set to "off" / "0" / "false" / "no" to opt back into
|
|
134
|
+
* the aggressive `SWAP_MARGIN` (0.1) for evaluation builds.
|
|
135
|
+
*/
|
|
136
|
+
export declare function parseProvisionalPreferEnv(raw: string | undefined): number;
|
|
137
|
+
/**
|
|
138
|
+
* PRD §M3.5b polish — window after a swap inside which a dismiss/abort is
|
|
139
|
+
* counted as a "flicker" (visual jump that didn't help the user). Bench
|
|
140
|
+
* uses `provisionalSwapFlicker` to track this rate so the
|
|
141
|
+
* `PI_GHOST_PROVISIONAL_PREFER` knob can be evaluated.
|
|
142
|
+
*/
|
|
143
|
+
export declare const SWAP_FLICKER_WINDOW_MS = 250;
|
|
144
|
+
/**
|
|
145
|
+
* Default timer budget for the provisional. PRD §M3.5b says
|
|
146
|
+
* `min(150, provider.debounceMs)`; expose as a helper so callers don't
|
|
147
|
+
* duplicate the math.
|
|
148
|
+
*/
|
|
149
|
+
export declare function provisionalDelayMs(providerDebounceMs: number): number;
|
|
150
|
+
//# sourceMappingURL=provisional.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisional.d.ts","sourceRoot":"","sources":["../src/provisional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,sEAAsE;AACtE,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,kEAAkE;AAClE,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAClD,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAE5E,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACpC;0CACsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,oDAAoD;IACpD,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B;;wEAEoE;IACpE,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,eAAe,GAAG,IAAI,CAoEnF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,MAAM,MAAM,YAAY,GACrB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,GAAG,gBAAgB,CAAA;CAAE,GAC3E;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAiC/D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEtE;AASD,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOzE;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAIrE"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisional ghost — fast local guess shown while the LLM is in-flight.
|
|
3
|
+
* PRD §M3.5b.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: when the synchronous fast paths (cache, trie, speculative
|
|
6
|
+
* warm, profile-slash) all miss and the user is waiting on a network
|
|
7
|
+
* round-trip, fill the >150 ms gap with the next-best local guess so the
|
|
8
|
+
* user isn't staring at an empty prompt. Even an imperfect provisional
|
|
9
|
+
* is better than no ghost at all: typing past it dismisses without cost,
|
|
10
|
+
* and an LLM result that matches the provisional prefix collapses to a
|
|
11
|
+
* silent append rather than a visual swap.
|
|
12
|
+
*
|
|
13
|
+
* Pure functions only — no I/O, no editor state, no timers. Callers
|
|
14
|
+
* (the editor) own the timer that gates when this runs.
|
|
15
|
+
*
|
|
16
|
+
* Pipeline:
|
|
17
|
+
* 1. `pickProvisional(...)` — consult trie, profile.acceptByPrefix
|
|
18
|
+
* (M4), and a "common-token" fallback. Score in [0, 1]; null
|
|
19
|
+
* when nothing scores above `MIN_CONFIDENCE`.
|
|
20
|
+
* 2. `decideSwap(provisionalText, llmText)` — given the original
|
|
21
|
+
* provisional and the eventually-arriving LLM result, decide:
|
|
22
|
+
* "keep + extend", "swap", or "discard llm".
|
|
23
|
+
*
|
|
24
|
+
* The two-step shape mirrors the M3c reranker: pickProvisional is the
|
|
25
|
+
* displayed-ghost source; decideSwap is the reconciler when truth
|
|
26
|
+
* (the LLM result) arrives.
|
|
27
|
+
*/
|
|
28
|
+
/** PRD §M3.5b — provisional accepts down to 0.4 (vs M3c.1's 0.55). */
|
|
29
|
+
export const MIN_CONFIDENCE = 0.4;
|
|
30
|
+
/** Score margin LLM must exceed provisional to trigger a swap. */
|
|
31
|
+
export const SWAP_MARGIN = 0.1;
|
|
32
|
+
/**
|
|
33
|
+
* Anti-flicker swap margin used when `PI_GHOST_PROVISIONAL_PREFER=1` is set.
|
|
34
|
+
* Production autocomplete systems (Cursor, Copilot) bias toward keeping the
|
|
35
|
+
* already-displayed candidate unless the LLM is dramatically better, since
|
|
36
|
+
* any visible swap is a perceived flicker on slow terminals.
|
|
37
|
+
*/
|
|
38
|
+
export const PREFER_PROVISIONAL_SWAP_MARGIN = 0.3;
|
|
39
|
+
/** Prefix-match ratio above which we extend rather than swap. */
|
|
40
|
+
export const KEEP_PREFIX_MATCH = 0.8;
|
|
41
|
+
/**
|
|
42
|
+
* Walk the local fast paths in priority order and return the first that
|
|
43
|
+
* meets `MIN_CONFIDENCE`. Trie is consulted first (user's own confirmed
|
|
44
|
+
* accepts in this session — highest precision). Profile.acceptByPrefix
|
|
45
|
+
* second (cross-session frequency for the exact-prefix hash). Returns
|
|
46
|
+
* null when nothing qualifies.
|
|
47
|
+
*/
|
|
48
|
+
export function pickProvisional(input) {
|
|
49
|
+
const { prefix, trie, profile, lastGhosts } = input;
|
|
50
|
+
if (!prefix)
|
|
51
|
+
return null;
|
|
52
|
+
const lastLine = prefix.split("\n").pop() ?? "";
|
|
53
|
+
if (!lastLine)
|
|
54
|
+
return null;
|
|
55
|
+
// Trie: PRD §M2 lookup already returns null below its own internal
|
|
56
|
+
// gates (min-prefix-len, min-weight, min-margin). When it returns a
|
|
57
|
+
// hit we trust it; map its weight to a [0, 1] score via a saturating
|
|
58
|
+
// transform so the swap-decision code can compare across sources.
|
|
59
|
+
if (trie) {
|
|
60
|
+
const hit = trie.lookup(lastLine);
|
|
61
|
+
if (hit) {
|
|
62
|
+
// Weight ≥ 1.5 (trie's own gate). Saturate at weight=6 → score 1.
|
|
63
|
+
// Below 1.5 → 0; saturates above.
|
|
64
|
+
const score = clamp01((hit.weight - 1.5) / 4.5 + 0.6);
|
|
65
|
+
if (score >= MIN_CONFIDENCE) {
|
|
66
|
+
return { text: hit.ghost, score, source: "trie" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Profile.acceptByPrefix: keyed by SHA-256(prefix). Direct hit means
|
|
71
|
+
// the user has accepted *this exact prefix* before. High signal, but
|
|
72
|
+
// the stored `completionTokens` are tokenized, so we render them
|
|
73
|
+
// space-joined as the ghost. Detokenization is lossy — this only
|
|
74
|
+
// fires for plain-word completions, which is fine for fallback.
|
|
75
|
+
if (profile && !profile.disabled) {
|
|
76
|
+
const hit = profile.lookupAcceptByPrefix(lastLine);
|
|
77
|
+
if (hit && hit.completionTokens.length > 0) {
|
|
78
|
+
// Weight ≥ 1 (every accept records weight 3 today). Saturate
|
|
79
|
+
// at weight=9 → score 1. Below 1 → minimum score, gated by
|
|
80
|
+
// MIN_CONFIDENCE.
|
|
81
|
+
const score = clamp01((hit.weight - 1) / 8 + 0.45);
|
|
82
|
+
if (score >= MIN_CONFIDENCE) {
|
|
83
|
+
return {
|
|
84
|
+
text: " " + hit.completionTokens.join(" "),
|
|
85
|
+
source: "profile-accept",
|
|
86
|
+
score,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Speculative-warm ring: tertiary source. Ring entries store the FULL
|
|
92
|
+
// multi-line `textBeforeCursor` as their prefix (set by the editor in
|
|
93
|
+
// `requestPrediction`), so we compare against the full `prefix`, not
|
|
94
|
+
// the last-line slice trie/profile use. Score is recency-weighted in
|
|
95
|
+
// `[MIN_CONFIDENCE, 0.7]` — below trie but above the floor.
|
|
96
|
+
//
|
|
97
|
+
// Fingerprints are intentionally NOT checked: trie + profile-accept
|
|
98
|
+
// follow the same precedent (purely text-based, session-wide). A
|
|
99
|
+
// cross-context guess that doesn't match gets dismissed by the user
|
|
100
|
+
// at next keystroke, same as a bad trie hit.
|
|
101
|
+
if (lastGhosts) {
|
|
102
|
+
const entries = lastGhosts.entries();
|
|
103
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
104
|
+
const lg = entries[i];
|
|
105
|
+
if (lg.prefix !== prefix)
|
|
106
|
+
continue;
|
|
107
|
+
if (!lg.ghost)
|
|
108
|
+
continue;
|
|
109
|
+
// Most recent entry gets 0.7, oldest entry gets MIN_CONFIDENCE.
|
|
110
|
+
const recencyFactor = entries.length === 1 ? 1 : i / (entries.length - 1);
|
|
111
|
+
const score = MIN_CONFIDENCE + recencyFactor * (0.7 - MIN_CONFIDENCE);
|
|
112
|
+
return { text: lg.ghost, source: "session-cache", score };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compute the prefix-match ratio between two strings: number of leading
|
|
119
|
+
* characters they share, divided by `max(a.length, b.length)`. Used by
|
|
120
|
+
* `decideSwap` to detect "LLM extends provisional" cases where no
|
|
121
|
+
* visual jump is needed.
|
|
122
|
+
*/
|
|
123
|
+
export function prefixMatchRatio(a, b) {
|
|
124
|
+
if (a === b)
|
|
125
|
+
return 1;
|
|
126
|
+
if (a.length === 0 || b.length === 0)
|
|
127
|
+
return 0;
|
|
128
|
+
let i = 0;
|
|
129
|
+
const n = Math.min(a.length, b.length);
|
|
130
|
+
while (i < n && a[i] === b[i])
|
|
131
|
+
i++;
|
|
132
|
+
return i / Math.max(a.length, b.length);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Reconcile the LLM result against the provisional. Returns the action
|
|
136
|
+
* the editor should take + the text to display. PRD §M3.5b decision
|
|
137
|
+
* matrix:
|
|
138
|
+
* - prefix-match ≥ KEEP_PREFIX_MATCH → extend (keep provisional,
|
|
139
|
+
* append LLM tail).
|
|
140
|
+
* - LLM score margin > swapMargin (default SWAP_MARGIN) → swap
|
|
141
|
+
* (visual jump, log `provisionalSwapMs`, `provisionalWasCorrect=false`).
|
|
142
|
+
* - LLM produced nothing → keep provisional (silent).
|
|
143
|
+
* - LLM differs but score not better enough → keep provisional
|
|
144
|
+
* (PRD: "rare; mostly when provisional was a high-quality trie hit").
|
|
145
|
+
*/
|
|
146
|
+
export function decideSwap(input) {
|
|
147
|
+
const { provisional, llm } = input;
|
|
148
|
+
const margin = typeof input.swapMargin === "number" && Number.isFinite(input.swapMargin)
|
|
149
|
+
? input.swapMargin
|
|
150
|
+
: SWAP_MARGIN;
|
|
151
|
+
if (!provisional && !llm) {
|
|
152
|
+
return { action: "keep", text: "", reason: "identical" };
|
|
153
|
+
}
|
|
154
|
+
if (!provisional && llm) {
|
|
155
|
+
return { action: "swap", text: llm.text, reason: "no-provisional" };
|
|
156
|
+
}
|
|
157
|
+
if (provisional && !llm) {
|
|
158
|
+
return { action: "keep", text: provisional.text, reason: "lossy-llm" };
|
|
159
|
+
}
|
|
160
|
+
const p = provisional;
|
|
161
|
+
const l = llm;
|
|
162
|
+
if (p.text === l.text) {
|
|
163
|
+
return { action: "keep", text: p.text, reason: "identical" };
|
|
164
|
+
}
|
|
165
|
+
const pm = prefixMatchRatio(p.text, l.text);
|
|
166
|
+
if (pm >= KEEP_PREFIX_MATCH) {
|
|
167
|
+
// LLM extended (or trimmed) the provisional. Take the longer of
|
|
168
|
+
// the two so the user sees the full LLM output without losing
|
|
169
|
+
// what they already accepted-by-eye.
|
|
170
|
+
const longer = l.text.length >= p.text.length ? l.text : p.text;
|
|
171
|
+
const tail = longer.length > p.text.length ? longer.slice(p.text.length) : "";
|
|
172
|
+
return { action: "extend", text: longer, tail };
|
|
173
|
+
}
|
|
174
|
+
if (l.score > p.score + margin) {
|
|
175
|
+
return { action: "swap", text: l.text, reason: "score-margin" };
|
|
176
|
+
}
|
|
177
|
+
return { action: "keep", text: p.text, reason: "lossy-llm" };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Convert a `[0, 1]` score (or raw weight) to the LLM-side comparable
|
|
181
|
+
* scale. With M3c rerank scores in `[0, 1]` and confidence-gate default
|
|
182
|
+
* 0.55, this is just an identity pass for clarity at call sites.
|
|
183
|
+
*/
|
|
184
|
+
export function llmScoreFromRerank(reranked) {
|
|
185
|
+
return clamp01(reranked.score);
|
|
186
|
+
}
|
|
187
|
+
function clamp01(x) {
|
|
188
|
+
if (!Number.isFinite(x))
|
|
189
|
+
return 0;
|
|
190
|
+
if (x < 0)
|
|
191
|
+
return 0;
|
|
192
|
+
if (x > 1)
|
|
193
|
+
return 1;
|
|
194
|
+
return x;
|
|
195
|
+
}
|
|
196
|
+
/** Parse `PI_GHOST_PROVISIONAL`. Returns false when set to "off". */
|
|
197
|
+
export function parseProvisionalEnv(raw) {
|
|
198
|
+
if (raw === undefined)
|
|
199
|
+
return true;
|
|
200
|
+
return raw.toLowerCase() !== "off";
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Parse `PI_GHOST_PROVISIONAL_PREFER`. Returns the swap-margin for
|
|
204
|
+
* {@link decideSwap}. Default is `PREFER_PROVISIONAL_SWAP_MARGIN` (anti-
|
|
205
|
+
* flicker bias) — production autocomplete systems (Cursor, Copilot, Gmail
|
|
206
|
+
* Smart Compose) bias toward keeping the displayed candidate unless the
|
|
207
|
+
* LLM is dramatically better, because any visible swap registers as a
|
|
208
|
+
* perceived flicker. Set to "off" / "0" / "false" / "no" to opt back into
|
|
209
|
+
* the aggressive `SWAP_MARGIN` (0.1) for evaluation builds.
|
|
210
|
+
*/
|
|
211
|
+
export function parseProvisionalPreferEnv(raw) {
|
|
212
|
+
if (raw === undefined)
|
|
213
|
+
return PREFER_PROVISIONAL_SWAP_MARGIN;
|
|
214
|
+
const norm = raw.toLowerCase();
|
|
215
|
+
if (norm === "0" || norm === "false" || norm === "off" || norm === "no") {
|
|
216
|
+
return SWAP_MARGIN;
|
|
217
|
+
}
|
|
218
|
+
return PREFER_PROVISIONAL_SWAP_MARGIN;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* PRD §M3.5b polish — window after a swap inside which a dismiss/abort is
|
|
222
|
+
* counted as a "flicker" (visual jump that didn't help the user). Bench
|
|
223
|
+
* uses `provisionalSwapFlicker` to track this rate so the
|
|
224
|
+
* `PI_GHOST_PROVISIONAL_PREFER` knob can be evaluated.
|
|
225
|
+
*/
|
|
226
|
+
export const SWAP_FLICKER_WINDOW_MS = 250;
|
|
227
|
+
/**
|
|
228
|
+
* Default timer budget for the provisional. PRD §M3.5b says
|
|
229
|
+
* `min(150, provider.debounceMs)`; expose as a helper so callers don't
|
|
230
|
+
* duplicate the math.
|
|
231
|
+
*/
|
|
232
|
+
export function provisionalDelayMs(providerDebounceMs) {
|
|
233
|
+
const cap = 150;
|
|
234
|
+
if (!Number.isFinite(providerDebounceMs) || providerDebounceMs <= 0)
|
|
235
|
+
return cap;
|
|
236
|
+
return Math.min(cap, providerDebounceMs);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=provisional.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisional.js","sourceRoot":"","sources":["../src/provisional.ts"],"names":[],"mappings":"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"}
|