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
package/dist/cache.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { combinedVersionFingerprint } from "./versions.js";
|
|
3
|
+
import { tryWarm } from "./speculative.js";
|
|
4
|
+
export const DEFAULT_CAPACITY = 256;
|
|
5
|
+
export const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
6
|
+
/**
|
|
7
|
+
* Build a stable cache key. Combines prefix, context fingerprint, provider
|
|
8
|
+
* fingerprint, the global behavior fingerprint, and (optionally) the engine
|
|
9
|
+
* config fingerprint so that prompt-template, sanitizer, context-aggregator,
|
|
10
|
+
* reranker, profile-hint, AND env-knob changes all invalidate stale entries.
|
|
11
|
+
*/
|
|
12
|
+
export function makeKey(input) {
|
|
13
|
+
const sep = "\x00";
|
|
14
|
+
const raw = [
|
|
15
|
+
input.prefix,
|
|
16
|
+
input.contextFingerprint,
|
|
17
|
+
input.providerFingerprint,
|
|
18
|
+
combinedVersionFingerprint(),
|
|
19
|
+
input.configFingerprint ?? "",
|
|
20
|
+
].join(sep);
|
|
21
|
+
return createHash("sha1").update(raw).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fingerprint over the final provider-visible input. The PRD requires this
|
|
25
|
+
* to be computed over the rendered final input (after pruning/truncation)
|
|
26
|
+
* rather than a raw branch slice. Callers should pass the same messages
|
|
27
|
+
* array the provider will receive.
|
|
28
|
+
*
|
|
29
|
+
* Tool-call metadata (`toolName`) participates in the hash so distinct tool
|
|
30
|
+
* results collapse to distinct fingerprints.
|
|
31
|
+
*/
|
|
32
|
+
export function fingerprintRecentMessages(messages) {
|
|
33
|
+
const h = createHash("sha1");
|
|
34
|
+
for (const m of messages) {
|
|
35
|
+
h.update(m.role);
|
|
36
|
+
h.update("\x00");
|
|
37
|
+
h.update(m.text);
|
|
38
|
+
h.update("\x00");
|
|
39
|
+
h.update(m.toolName ?? "");
|
|
40
|
+
h.update("\x01");
|
|
41
|
+
}
|
|
42
|
+
return h.digest("hex");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fingerprint identifying a provider configuration. Anything that, if
|
|
46
|
+
* changed, would produce a different completion belongs here.
|
|
47
|
+
*/
|
|
48
|
+
export function fingerprintProvider(parts) {
|
|
49
|
+
const e = parts.extras ?? {};
|
|
50
|
+
const fields = [
|
|
51
|
+
["kind", parts.kind],
|
|
52
|
+
["providerName", parts.providerName],
|
|
53
|
+
["modelId", parts.modelId],
|
|
54
|
+
["temperature", String(parts.temperature)],
|
|
55
|
+
["maxTokens", String(parts.maxTokens)],
|
|
56
|
+
["maxRecentMessages", String(parts.maxRecentMessages)],
|
|
57
|
+
...Object.keys(e)
|
|
58
|
+
.sort()
|
|
59
|
+
.map((k) => [k, String(e[k] ?? "")]),
|
|
60
|
+
];
|
|
61
|
+
const h = createHash("sha1");
|
|
62
|
+
for (const [k, v] of fields) {
|
|
63
|
+
h.update(k);
|
|
64
|
+
h.update("=");
|
|
65
|
+
h.update(v);
|
|
66
|
+
h.update("\x00");
|
|
67
|
+
}
|
|
68
|
+
return h.digest("hex");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Small LRU cache for ghost completions.
|
|
72
|
+
*
|
|
73
|
+
* - Capacity-bounded; oldest entry is evicted on insert overflow.
|
|
74
|
+
* - TTL-bounded; expired entries are treated as miss and removed lazily on
|
|
75
|
+
* access.
|
|
76
|
+
* - `delete()` is exposed so callers can explicitly evict on divergent
|
|
77
|
+
* dismiss (see PRD §6 M1a).
|
|
78
|
+
*
|
|
79
|
+
* Not thread-safe, but the ghost editor is single-threaded.
|
|
80
|
+
*/
|
|
81
|
+
export class CompletionCache {
|
|
82
|
+
capacity;
|
|
83
|
+
ttlMs;
|
|
84
|
+
store = new Map();
|
|
85
|
+
hits = 0;
|
|
86
|
+
misses = 0;
|
|
87
|
+
constructor(capacity = DEFAULT_CAPACITY, ttlMs = DEFAULT_TTL_MS) {
|
|
88
|
+
this.capacity = capacity;
|
|
89
|
+
this.ttlMs = ttlMs;
|
|
90
|
+
}
|
|
91
|
+
get size() {
|
|
92
|
+
return this.store.size;
|
|
93
|
+
}
|
|
94
|
+
/** PRD §M3g — exposed via `/ghost` diagnostics. */
|
|
95
|
+
get stats() {
|
|
96
|
+
const total = this.hits + this.misses;
|
|
97
|
+
return {
|
|
98
|
+
size: this.store.size,
|
|
99
|
+
hits: this.hits,
|
|
100
|
+
misses: this.misses,
|
|
101
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
get(key, now = Date.now()) {
|
|
105
|
+
const v = this.store.get(key);
|
|
106
|
+
if (!v) {
|
|
107
|
+
this.misses++;
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (now - v.ts > this.ttlMs) {
|
|
111
|
+
this.store.delete(key);
|
|
112
|
+
this.misses++;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// LRU touch: re-insert to move to end.
|
|
116
|
+
this.store.delete(key);
|
|
117
|
+
this.store.set(key, v);
|
|
118
|
+
this.hits++;
|
|
119
|
+
return v;
|
|
120
|
+
}
|
|
121
|
+
set(key, value) {
|
|
122
|
+
if (this.store.has(key))
|
|
123
|
+
this.store.delete(key);
|
|
124
|
+
this.store.set(key, value);
|
|
125
|
+
while (this.store.size > this.capacity) {
|
|
126
|
+
const oldest = this.store.keys().next().value;
|
|
127
|
+
if (oldest === undefined)
|
|
128
|
+
break;
|
|
129
|
+
this.store.delete(oldest);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
delete(key) {
|
|
133
|
+
return this.store.delete(key);
|
|
134
|
+
}
|
|
135
|
+
clear() {
|
|
136
|
+
this.store.clear();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function decideWarmPath(input) {
|
|
140
|
+
const cacheKey = makeKey({
|
|
141
|
+
prefix: input.prefix,
|
|
142
|
+
contextFingerprint: input.contextFingerprint,
|
|
143
|
+
providerFingerprint: input.providerFingerprint,
|
|
144
|
+
...(input.configFingerprint !== undefined ? { configFingerprint: input.configFingerprint } : {}),
|
|
145
|
+
});
|
|
146
|
+
const hit = input.cache.get(cacheKey, input.now);
|
|
147
|
+
if (hit && hit.ghost) {
|
|
148
|
+
return {
|
|
149
|
+
ghost: hit.ghost,
|
|
150
|
+
mode: "cache",
|
|
151
|
+
providerName: hit.providerName,
|
|
152
|
+
cacheKey,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const match = tryWarm(input.ring, {
|
|
156
|
+
prefix: input.prefix,
|
|
157
|
+
contextFingerprint: input.contextFingerprint,
|
|
158
|
+
providerFingerprint: input.providerFingerprint,
|
|
159
|
+
versionFingerprint: input.versionFingerprint,
|
|
160
|
+
...(input.configFingerprint !== undefined ? { configFingerprint: input.configFingerprint } : {}),
|
|
161
|
+
cooldownActive: input.cooldownActive,
|
|
162
|
+
});
|
|
163
|
+
if (match) {
|
|
164
|
+
return {
|
|
165
|
+
ghost: match.ghost,
|
|
166
|
+
mode: "speculative-warm",
|
|
167
|
+
providerName: match.source.providerName,
|
|
168
|
+
cacheKey,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rerank pipeline for an LLM `CandidateSet`. Extracted from
|
|
3
|
+
* `GhostEditor.requestPrediction` as part of refactor #57 so the editor
|
|
4
|
+
* stays thin and the rerank-+-confidence-gate path is unit-testable in
|
|
5
|
+
* isolation.
|
|
6
|
+
*
|
|
7
|
+
* PRD §M3c (weighted reranker) + §M3c.1 (confidence gate) + §M4
|
|
8
|
+
* (profile-bias signal). Single-candidate sets bypass the reranker
|
|
9
|
+
* entirely — there is no reordering to do and the confidence gate is
|
|
10
|
+
* not applicable (it operates on the reranked top-vs-second margin).
|
|
11
|
+
*
|
|
12
|
+
* No `this`, no I/O, no editor state — caller supplies env strings and
|
|
13
|
+
* an optional `profileBias` function.
|
|
14
|
+
*/
|
|
15
|
+
export interface RerankCandidateSetInput {
|
|
16
|
+
/** Provider's raw candidate list (post-dedup). At least one entry expected. */
|
|
17
|
+
candidates: {
|
|
18
|
+
text: string;
|
|
19
|
+
avgLogprob?: number;
|
|
20
|
+
}[];
|
|
21
|
+
/** Provider name — drives the local-vs-cloud bias in the reranker. */
|
|
22
|
+
providerName: string;
|
|
23
|
+
/** Full text-before-cursor at request time. Used to slice the
|
|
24
|
+
* last-line prefix for the lengthPrior curve. */
|
|
25
|
+
prefix: string;
|
|
26
|
+
/** Raw `PI_GHOST_RERANK_WEIGHTS` env value. */
|
|
27
|
+
weightsEnvRaw: string | undefined;
|
|
28
|
+
/** Raw `PI_GHOST_MIN_CONFIDENCE` env value. `undefined` disables
|
|
29
|
+
* the gate entirely; an invalid string is treated as "no gate". */
|
|
30
|
+
minConfidenceEnvRaw: string | undefined;
|
|
31
|
+
/** Optional cross-session profile-bias hook (M4). */
|
|
32
|
+
profileBias?: (text: string) => number;
|
|
33
|
+
/**
|
|
34
|
+
* Enhancement #1 — optional edit-history proximity scorer. Receives raw
|
|
35
|
+
* candidate text; returns a real number (negative permitted for tokens
|
|
36
|
+
* recently deleted). Missing → all candidates score 0 in that feature.
|
|
37
|
+
*/
|
|
38
|
+
editProximityScorer?: (text: string) => number;
|
|
39
|
+
}
|
|
40
|
+
export type RerankCandidateSetResult = {
|
|
41
|
+
gated: false;
|
|
42
|
+
candidateTexts: string[];
|
|
43
|
+
topText: string;
|
|
44
|
+
/** Top-candidate raw editProximity score (un-normalized). Populated
|
|
45
|
+
* only when `editProximityScorer` was supplied. */
|
|
46
|
+
topEditProximityScore?: number;
|
|
47
|
+
} | {
|
|
48
|
+
gated: true;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Rerank a candidate set + apply the optional confidence gate. Returns
|
|
52
|
+
* `{ gated: true }` when the gate rejected the result (caller should emit
|
|
53
|
+
* a `rejected` metric and skip rendering). Otherwise returns the reordered
|
|
54
|
+
* candidate texts plus the top pick.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rerankCandidateSet(input: RerankCandidateSetInput): RerankCandidateSetResult;
|
|
57
|
+
//# sourceMappingURL=candidate-rerank.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidate-rerank.d.ts","sourceRoot":"","sources":["../src/candidate-rerank.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,MAAM,WAAW,uBAAuB;IACvC,+EAA+E;IAC/E,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACpD,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB;sDACkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;wEACoE;IACpE,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,qDAAqD;IACrD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C;AAED,MAAM,MAAM,wBAAwB,GACjC;IACA,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB;wDACoD;IACpD,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC9B,GACD;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,wBAAwB,CAsD3F"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rerank pipeline for an LLM `CandidateSet`. Extracted from
|
|
3
|
+
* `GhostEditor.requestPrediction` as part of refactor #57 so the editor
|
|
4
|
+
* stays thin and the rerank-+-confidence-gate path is unit-testable in
|
|
5
|
+
* isolation.
|
|
6
|
+
*
|
|
7
|
+
* PRD §M3c (weighted reranker) + §M3c.1 (confidence gate) + §M4
|
|
8
|
+
* (profile-bias signal). Single-candidate sets bypass the reranker
|
|
9
|
+
* entirely — there is no reordering to do and the confidence gate is
|
|
10
|
+
* not applicable (it operates on the reranked top-vs-second margin).
|
|
11
|
+
*
|
|
12
|
+
* No `this`, no I/O, no editor state — caller supplies env strings and
|
|
13
|
+
* an optional `profileBias` function.
|
|
14
|
+
*/
|
|
15
|
+
import { applyConfidenceGate, parseMinConfidenceEnv, parseWeightsEnv, rerank, } from "./reranker.js";
|
|
16
|
+
/**
|
|
17
|
+
* Rerank a candidate set + apply the optional confidence gate. Returns
|
|
18
|
+
* `{ gated: true }` when the gate rejected the result (caller should emit
|
|
19
|
+
* a `rejected` metric and skip rendering). Otherwise returns the reordered
|
|
20
|
+
* candidate texts plus the top pick.
|
|
21
|
+
*/
|
|
22
|
+
export function rerankCandidateSet(input) {
|
|
23
|
+
// Empty set is structurally invalid for this function — the caller
|
|
24
|
+
// must guard for `!result || !set` BEFORE invoking. Treat as gated so
|
|
25
|
+
// any future re-use can't silently render an empty ghost.
|
|
26
|
+
if (input.candidates.length === 0) {
|
|
27
|
+
return { gated: true };
|
|
28
|
+
}
|
|
29
|
+
// Single-candidate path: nothing to reorder, gate doesn't apply
|
|
30
|
+
// (it needs a top-vs-second margin). Still surface editProximity raw
|
|
31
|
+
// score for metric capture when scorer was supplied.
|
|
32
|
+
if (input.candidates.length === 1) {
|
|
33
|
+
const only = input.candidates[0];
|
|
34
|
+
const ep = input.editProximityScorer ? input.editProximityScorer(only.text) : undefined;
|
|
35
|
+
return {
|
|
36
|
+
gated: false,
|
|
37
|
+
candidateTexts: [only.text],
|
|
38
|
+
topText: only.text,
|
|
39
|
+
...(ep !== undefined ? { topEditProximityScore: ep } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const local = input.providerName === "ghost-local";
|
|
43
|
+
const inputs = input.candidates.map((c) => ({
|
|
44
|
+
text: c.text,
|
|
45
|
+
provider: input.providerName,
|
|
46
|
+
local,
|
|
47
|
+
...(c.avgLogprob !== undefined ? { avgLogprob: c.avgLogprob } : {}),
|
|
48
|
+
}));
|
|
49
|
+
// PRD-adjacent (#5): pass last-line prefix length so the lengthPrior
|
|
50
|
+
// curve scales for long-prefix queries. Document-level length would
|
|
51
|
+
// over-extend the curve on multi-line buffers; the active line is
|
|
52
|
+
// what the user is currently typing.
|
|
53
|
+
const lastLinePrefix = input.prefix.split("\n").pop() ?? "";
|
|
54
|
+
const ranked = rerank(inputs, {
|
|
55
|
+
weights: parseWeightsEnv(input.weightsEnvRaw),
|
|
56
|
+
prefixLen: lastLinePrefix.length,
|
|
57
|
+
...(input.profileBias ? { profileBias: input.profileBias } : {}),
|
|
58
|
+
...(input.editProximityScorer ? { editProximityScorer: input.editProximityScorer } : {}),
|
|
59
|
+
});
|
|
60
|
+
if (input.minConfidenceEnvRaw !== undefined) {
|
|
61
|
+
const gated = applyConfidenceGate(ranked, {
|
|
62
|
+
minConfidence: parseMinConfidenceEnv(input.minConfidenceEnvRaw),
|
|
63
|
+
});
|
|
64
|
+
if (!gated)
|
|
65
|
+
return { gated: true };
|
|
66
|
+
}
|
|
67
|
+
const topText = ranked[0].text;
|
|
68
|
+
const topEditProximityScore = input.editProximityScorer
|
|
69
|
+
? input.editProximityScorer(topText)
|
|
70
|
+
: undefined;
|
|
71
|
+
return {
|
|
72
|
+
gated: false,
|
|
73
|
+
candidateTexts: ranked.map((r) => r.text),
|
|
74
|
+
topText,
|
|
75
|
+
...(topEditProximityScore !== undefined ? { topEditProximityScore } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=candidate-rerank.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidate-rerank.js","sourceRoot":"","sources":["../src/candidate-rerank.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -10,8 +10,17 @@ export interface OpenAICompatProviderConfig {
|
|
|
10
10
|
debounceMs: number;
|
|
11
11
|
maxTokens: number;
|
|
12
12
|
maxRecentMessages: number;
|
|
13
|
+
/** Sampling temperature. 0 for deterministic-ish output (best-effort). */
|
|
14
|
+
temperature: number;
|
|
13
15
|
/** Optional context-window hint passed to pi-ai. Defaults to 8192. */
|
|
14
16
|
contextWindow?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Number of parallel candidate completions to fire when `predictMulti`
|
|
19
|
+
* is called. 1 means no parallelism. Useful only at temperature > 0
|
|
20
|
+
* (otherwise all parallel calls return the same greedy result).
|
|
21
|
+
* Default 1. Override via `PI_GHOST_MERCURY_PARALLEL_N`.
|
|
22
|
+
*/
|
|
23
|
+
parallelN: number;
|
|
15
24
|
}
|
|
16
25
|
export interface CloudProviderConfig {
|
|
17
26
|
kind: "cloud";
|
|
@@ -21,6 +30,7 @@ export interface CloudProviderConfig {
|
|
|
21
30
|
debounceMs: number;
|
|
22
31
|
maxTokens: number;
|
|
23
32
|
maxRecentMessages: number;
|
|
33
|
+
temperature: number;
|
|
24
34
|
}
|
|
25
35
|
export interface LocalProviderConfig {
|
|
26
36
|
kind: "local";
|
|
@@ -29,6 +39,7 @@ export interface LocalProviderConfig {
|
|
|
29
39
|
debounceMs: number;
|
|
30
40
|
maxTokens: number;
|
|
31
41
|
maxRecentMessages: number;
|
|
42
|
+
temperature: number;
|
|
32
43
|
/** Optional context-window hint passed to pi-ai. Defaults to 8192. */
|
|
33
44
|
contextWindow?: number;
|
|
34
45
|
}
|
|
@@ -52,4 +63,36 @@ export declare const DEFAULT_LOCAL_CONFIG: LocalProviderConfig;
|
|
|
52
63
|
export declare const DEFAULT_MERCURY_CONFIG: OpenAICompatProviderConfig;
|
|
53
64
|
export declare const DEFAULT_CONFIG: GhostConfig;
|
|
54
65
|
export declare function readConfigFromEnv(): GhostConfig;
|
|
66
|
+
/**
|
|
67
|
+
* Strip CR/LF and surrounding whitespace from an API key (SEC-7). Prevents
|
|
68
|
+
* HTTP header injection via `Authorization: Bearer ${apiKey}` when a
|
|
69
|
+
* malformed `.env` file or environment manipulation injects newlines.
|
|
70
|
+
* Returns undefined for empty results so callers fall back to env lookup.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sanitizeApiKey(raw: string | undefined): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Produce a deep copy of a config with all `apiKey` fields removed (SEC-8).
|
|
75
|
+
* Use before any logging, diagnostics dump, telemetry, or serialization.
|
|
76
|
+
* The original config remains live with its keys for actual provider use.
|
|
77
|
+
*
|
|
78
|
+
* Defensive: even fields not currently typed as containing keys are scrubbed
|
|
79
|
+
* via a recursive walk that drops any property named `apiKey` (case-sensitive).
|
|
80
|
+
*
|
|
81
|
+
* Caveats: class instances become plain objects (methods lost). Cyclic
|
|
82
|
+
* structures would recurse infinitely — caller must ensure acyclic input.
|
|
83
|
+
* Both caveats are fine for `GhostConfig` which is plain JSON-shaped.
|
|
84
|
+
*/
|
|
85
|
+
export declare function redactConfig<T>(cfg: T): T;
|
|
86
|
+
/**
|
|
87
|
+
* Validate a provider baseUrl (SEC-1). Returns the URL on success, null
|
|
88
|
+
* on failure with a stderr warning. Rules:
|
|
89
|
+
* - Must be a parseable absolute URL.
|
|
90
|
+
* - `https:` always allowed.
|
|
91
|
+
* - `http:` only allowed for `localhost` / `127.0.0.1` / `::1` — sending
|
|
92
|
+
* API keys + prompt context in cleartext to anything else is almost
|
|
93
|
+
* certainly a misconfiguration. Setting `PI_GHOST_ALLOW_INSECURE=1`
|
|
94
|
+
* bypasses this check.
|
|
95
|
+
* - Other schemes (`file:`, `data:`, etc.) rejected unconditionally.
|
|
96
|
+
*/
|
|
97
|
+
export declare function validateBaseUrl(raw: string, fieldName?: string): string | null;
|
|
55
98
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,GAAG,MAAM,CAAC;AAExE,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,eAAe,CAAC;IACtB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,GAAG,MAAM,CAAC;AAExE,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,eAAe,CAAC;IACtB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,mBAAmB,GAAG,mBAAmB,GAAG,0BAA0B,CAAC,CAAC;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GACvB,mBAAmB,GACnB,mBAAmB,GACnB,0BAA0B,GAC1B,kBAAkB,CAAC;AAEtB,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,oBAAoB,EAAE,mBAQlC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,mBAQlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,0BAUpC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAK5B,CAAC;AAqBF,wBAAgB,iBAAiB,IAAI,WAAW,CAwD/C;AAsHD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEzC;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAsB,GAAG,MAAM,GAAG,IAAI,CAsB3F"}
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ export const DEFAULT_CLOUD_CONFIG = {
|
|
|
6
6
|
debounceMs: 150,
|
|
7
7
|
maxTokens: 64,
|
|
8
8
|
maxRecentMessages: 4,
|
|
9
|
+
temperature: 0,
|
|
9
10
|
};
|
|
10
11
|
export const DEFAULT_LOCAL_CONFIG = {
|
|
11
12
|
kind: "local",
|
|
@@ -14,6 +15,7 @@ export const DEFAULT_LOCAL_CONFIG = {
|
|
|
14
15
|
debounceMs: 400,
|
|
15
16
|
maxTokens: 48,
|
|
16
17
|
maxRecentMessages: 2,
|
|
18
|
+
temperature: 0,
|
|
17
19
|
};
|
|
18
20
|
export const DEFAULT_MERCURY_CONFIG = {
|
|
19
21
|
kind: "openai-compat",
|
|
@@ -23,6 +25,8 @@ export const DEFAULT_MERCURY_CONFIG = {
|
|
|
23
25
|
debounceMs: 150,
|
|
24
26
|
maxTokens: 64,
|
|
25
27
|
maxRecentMessages: 4,
|
|
28
|
+
temperature: 0,
|
|
29
|
+
parallelN: 1,
|
|
26
30
|
};
|
|
27
31
|
export const DEFAULT_CONFIG = {
|
|
28
32
|
enabled: true,
|
|
@@ -67,35 +71,50 @@ export function readConfigFromEnv() {
|
|
|
67
71
|
cfg.provider.provider = process.env.PI_GHOST_PROVIDER_NAME;
|
|
68
72
|
if (process.env.PI_GHOST_MODEL)
|
|
69
73
|
cfg.provider.modelId = process.env.PI_GHOST_MODEL;
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
const apiKey = sanitizeApiKey(process.env.PI_GHOST_API_KEY);
|
|
75
|
+
if (apiKey !== undefined)
|
|
76
|
+
cfg.provider.apiKey = apiKey;
|
|
72
77
|
cfg.provider.debounceMs = numEnv("PI_GHOST_DEBOUNCE_MS", cfg.provider.debounceMs);
|
|
73
78
|
cfg.provider.maxTokens = numEnv("PI_GHOST_MAX_TOKENS", cfg.provider.maxTokens);
|
|
74
79
|
cfg.provider.maxRecentMessages = numEnv("PI_GHOST_RECENT", cfg.provider.maxRecentMessages);
|
|
80
|
+
cfg.provider.temperature = tempEnv(cfg.provider.temperature);
|
|
75
81
|
}
|
|
76
82
|
else if (cfg.provider.kind === "openai-compat") {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
const baseUrlRaw = process.env.PI_GHOST_BASE_URL ?? process.env.INCEPTION_BASE_URL;
|
|
84
|
+
if (baseUrlRaw) {
|
|
85
|
+
const fieldName = process.env.PI_GHOST_BASE_URL ? "PI_GHOST_BASE_URL" : "INCEPTION_BASE_URL";
|
|
86
|
+
const validated = validateBaseUrl(baseUrlRaw, fieldName);
|
|
87
|
+
if (validated)
|
|
88
|
+
cfg.provider.baseUrl = validated;
|
|
89
|
+
}
|
|
81
90
|
if (process.env.PI_GHOST_MODEL)
|
|
82
91
|
cfg.provider.modelId = process.env.PI_GHOST_MODEL;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cfg.provider.apiKey = process.env.INCEPTION_API_KEY;
|
|
92
|
+
const apiKey = sanitizeApiKey(process.env.PI_GHOST_API_KEY ?? process.env.INCEPTION_API_KEY);
|
|
93
|
+
if (apiKey !== undefined)
|
|
94
|
+
cfg.provider.apiKey = apiKey;
|
|
87
95
|
cfg.provider.debounceMs = numEnv("PI_GHOST_DEBOUNCE_MS", cfg.provider.debounceMs);
|
|
88
96
|
cfg.provider.maxTokens = numEnv("PI_GHOST_MAX_TOKENS", cfg.provider.maxTokens);
|
|
89
97
|
cfg.provider.maxRecentMessages = numEnv("PI_GHOST_RECENT", cfg.provider.maxRecentMessages);
|
|
98
|
+
cfg.provider.temperature = tempEnv(cfg.provider.temperature);
|
|
99
|
+
cfg.provider.parallelN = parallelNEnv(cfg.provider.parallelN);
|
|
90
100
|
}
|
|
91
101
|
else if (cfg.provider.kind === "local") {
|
|
92
|
-
if (process.env.PI_GHOST_BASE_URL)
|
|
93
|
-
|
|
102
|
+
if (process.env.PI_GHOST_BASE_URL) {
|
|
103
|
+
const validated = validateBaseUrl(process.env.PI_GHOST_BASE_URL, "PI_GHOST_BASE_URL");
|
|
104
|
+
if (validated)
|
|
105
|
+
cfg.provider.baseUrl = validated;
|
|
106
|
+
}
|
|
94
107
|
if (process.env.PI_GHOST_MODEL)
|
|
95
108
|
cfg.provider.modelId = process.env.PI_GHOST_MODEL;
|
|
96
109
|
cfg.provider.debounceMs = numEnv("PI_GHOST_DEBOUNCE_MS", cfg.provider.debounceMs);
|
|
97
110
|
cfg.provider.maxTokens = numEnv("PI_GHOST_MAX_TOKENS", cfg.provider.maxTokens);
|
|
98
111
|
cfg.provider.maxRecentMessages = numEnv("PI_GHOST_RECENT", cfg.provider.maxRecentMessages);
|
|
112
|
+
cfg.provider.temperature = tempEnv(cfg.provider.temperature);
|
|
113
|
+
}
|
|
114
|
+
else if (cfg.provider.kind === "race") {
|
|
115
|
+
for (const m of cfg.provider.members) {
|
|
116
|
+
m.temperature = tempEnv(m.temperature);
|
|
117
|
+
}
|
|
99
118
|
}
|
|
100
119
|
return cfg;
|
|
101
120
|
}
|
|
@@ -125,18 +144,23 @@ function buildRaceFromEnv(raceList) {
|
|
|
125
144
|
function buildRaceMember(name) {
|
|
126
145
|
const upper = name.replace(/[^A-Za-z0-9]/g, "_").toUpperCase();
|
|
127
146
|
if (name === "local" || name === "ollama") {
|
|
147
|
+
const rawBase = process.env[`PI_GHOST_RACE_${upper}_BASE_URL`];
|
|
148
|
+
const baseUrl = rawBase
|
|
149
|
+
? (validateBaseUrl(rawBase, `PI_GHOST_RACE_${upper}_BASE_URL`) ?? DEFAULT_LOCAL_CONFIG.baseUrl)
|
|
150
|
+
: DEFAULT_LOCAL_CONFIG.baseUrl;
|
|
128
151
|
return {
|
|
129
152
|
...DEFAULT_LOCAL_CONFIG,
|
|
130
|
-
baseUrl
|
|
153
|
+
baseUrl,
|
|
131
154
|
modelId: process.env[`PI_GHOST_RACE_${upper}_MODEL`] ?? DEFAULT_LOCAL_CONFIG.modelId,
|
|
132
155
|
};
|
|
133
156
|
}
|
|
134
157
|
if (MERCURY_ALIASES.has(name)) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
MERCURY_DEFAULT_BASE_URL
|
|
158
|
+
const rawBase = process.env[`PI_GHOST_RACE_${upper}_BASE_URL`] ?? process.env.INCEPTION_BASE_URL;
|
|
159
|
+
const baseUrl = rawBase
|
|
160
|
+
? (validateBaseUrl(rawBase, `PI_GHOST_RACE_${upper}_BASE_URL`) ?? MERCURY_DEFAULT_BASE_URL)
|
|
161
|
+
: MERCURY_DEFAULT_BASE_URL;
|
|
138
162
|
const modelId = process.env[`PI_GHOST_RACE_${upper}_MODEL`] ?? MERCURY_DEFAULT_MODEL;
|
|
139
|
-
const apiKey = process.env[`PI_GHOST_RACE_${upper}_API_KEY`] ?? process.env.INCEPTION_API_KEY;
|
|
163
|
+
const apiKey = sanitizeApiKey(process.env[`PI_GHOST_RACE_${upper}_API_KEY`] ?? process.env.INCEPTION_API_KEY);
|
|
140
164
|
return {
|
|
141
165
|
kind: "openai-compat",
|
|
142
166
|
providerName: "mercury-edit",
|
|
@@ -146,6 +170,8 @@ function buildRaceMember(name) {
|
|
|
146
170
|
debounceMs: DEFAULT_CLOUD_CONFIG.debounceMs,
|
|
147
171
|
maxTokens: DEFAULT_CLOUD_CONFIG.maxTokens,
|
|
148
172
|
maxRecentMessages: DEFAULT_CLOUD_CONFIG.maxRecentMessages,
|
|
173
|
+
temperature: DEFAULT_MERCURY_CONFIG.temperature,
|
|
174
|
+
parallelN: DEFAULT_MERCURY_CONFIG.parallelN,
|
|
149
175
|
};
|
|
150
176
|
}
|
|
151
177
|
const provider = name;
|
|
@@ -155,7 +181,7 @@ function buildRaceMember(name) {
|
|
|
155
181
|
}
|
|
156
182
|
const modelEnv = process.env[`PI_GHOST_RACE_${upper}_MODEL`];
|
|
157
183
|
const modelId = modelEnv ?? RACE_DEFAULT_MODELS[provider] ?? DEFAULT_CLOUD_CONFIG.modelId;
|
|
158
|
-
const apiKey = process.env[`PI_GHOST_RACE_${upper}_API_KEY`];
|
|
184
|
+
const apiKey = sanitizeApiKey(process.env[`PI_GHOST_RACE_${upper}_API_KEY`]);
|
|
159
185
|
return {
|
|
160
186
|
...DEFAULT_CLOUD_CONFIG,
|
|
161
187
|
provider,
|
|
@@ -184,4 +210,108 @@ function numEnv(name, fallback) {
|
|
|
184
210
|
const v = Number(raw);
|
|
185
211
|
return Number.isFinite(v) && v >= 0 ? v : fallback;
|
|
186
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Parse PI_GHOST_TEMPERATURE. Returns fallback when unset or out of [0, 2].
|
|
215
|
+
* The provider implementations use 0 by default; users can opt back in to
|
|
216
|
+
* sampling for cache-miss diversity. Note: temperature=0 is best-effort
|
|
217
|
+
* across vendors and does NOT guarantee identical outputs across calls
|
|
218
|
+
* (kernel scheduling / batching cause variance). The completion cache is
|
|
219
|
+
* the real determinism guarantee.
|
|
220
|
+
*/
|
|
221
|
+
function tempEnv(fallback) {
|
|
222
|
+
const raw = process.env.PI_GHOST_TEMPERATURE;
|
|
223
|
+
if (raw === undefined)
|
|
224
|
+
return fallback;
|
|
225
|
+
const v = Number(raw);
|
|
226
|
+
return Number.isFinite(v) && v >= 0 && v <= 2 ? v : fallback;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Strip CR/LF and surrounding whitespace from an API key (SEC-7). Prevents
|
|
230
|
+
* HTTP header injection via `Authorization: Bearer ${apiKey}` when a
|
|
231
|
+
* malformed `.env` file or environment manipulation injects newlines.
|
|
232
|
+
* Returns undefined for empty results so callers fall back to env lookup.
|
|
233
|
+
*/
|
|
234
|
+
export function sanitizeApiKey(raw) {
|
|
235
|
+
if (raw === undefined)
|
|
236
|
+
return undefined;
|
|
237
|
+
const cleaned = raw.replace(/[\r\n]/g, "").trim();
|
|
238
|
+
return cleaned.length > 0 ? cleaned : undefined;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Produce a deep copy of a config with all `apiKey` fields removed (SEC-8).
|
|
242
|
+
* Use before any logging, diagnostics dump, telemetry, or serialization.
|
|
243
|
+
* The original config remains live with its keys for actual provider use.
|
|
244
|
+
*
|
|
245
|
+
* Defensive: even fields not currently typed as containing keys are scrubbed
|
|
246
|
+
* via a recursive walk that drops any property named `apiKey` (case-sensitive).
|
|
247
|
+
*
|
|
248
|
+
* Caveats: class instances become plain objects (methods lost). Cyclic
|
|
249
|
+
* structures would recurse infinitely — caller must ensure acyclic input.
|
|
250
|
+
* Both caveats are fine for `GhostConfig` which is plain JSON-shaped.
|
|
251
|
+
*/
|
|
252
|
+
export function redactConfig(cfg) {
|
|
253
|
+
return redactWalk(cfg);
|
|
254
|
+
}
|
|
255
|
+
function redactWalk(value) {
|
|
256
|
+
if (value === null || typeof value !== "object")
|
|
257
|
+
return value;
|
|
258
|
+
if (Array.isArray(value))
|
|
259
|
+
return value.map(redactWalk);
|
|
260
|
+
const out = {};
|
|
261
|
+
for (const [k, v] of Object.entries(value)) {
|
|
262
|
+
if (k === "apiKey")
|
|
263
|
+
continue;
|
|
264
|
+
out[k] = redactWalk(v);
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Validate a provider baseUrl (SEC-1). Returns the URL on success, null
|
|
270
|
+
* on failure with a stderr warning. Rules:
|
|
271
|
+
* - Must be a parseable absolute URL.
|
|
272
|
+
* - `https:` always allowed.
|
|
273
|
+
* - `http:` only allowed for `localhost` / `127.0.0.1` / `::1` — sending
|
|
274
|
+
* API keys + prompt context in cleartext to anything else is almost
|
|
275
|
+
* certainly a misconfiguration. Setting `PI_GHOST_ALLOW_INSECURE=1`
|
|
276
|
+
* bypasses this check.
|
|
277
|
+
* - Other schemes (`file:`, `data:`, etc.) rejected unconditionally.
|
|
278
|
+
*/
|
|
279
|
+
export function validateBaseUrl(raw, fieldName = "PI_GHOST_BASE_URL") {
|
|
280
|
+
let url;
|
|
281
|
+
try {
|
|
282
|
+
url = new URL(raw);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
process.stderr.write(`Pi Ghost: ignoring invalid ${fieldName}=${raw}\n`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
if (url.protocol === "https:")
|
|
289
|
+
return raw;
|
|
290
|
+
if (url.protocol === "http:") {
|
|
291
|
+
const host = url.hostname;
|
|
292
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1")
|
|
293
|
+
return raw;
|
|
294
|
+
if (process.env.PI_GHOST_ALLOW_INSECURE === "1")
|
|
295
|
+
return raw;
|
|
296
|
+
process.stderr.write(`Pi Ghost: refusing to use insecure ${fieldName}=${raw} (set PI_GHOST_ALLOW_INSECURE=1 to override)\n`);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
process.stderr.write(`Pi Ghost: rejecting ${fieldName} with unsupported scheme '${url.protocol}'\n`);
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Parse PI_GHOST_MERCURY_PARALLEL_N. Returns fallback when unset, non-integer,
|
|
304
|
+
* or outside [1, 8]. The upper bound prevents accidental cost explosions —
|
|
305
|
+
* each parallel call costs the same as a normal completion.
|
|
306
|
+
*/
|
|
307
|
+
function parallelNEnv(fallback) {
|
|
308
|
+
const raw = process.env.PI_GHOST_MERCURY_PARALLEL_N;
|
|
309
|
+
if (raw === undefined)
|
|
310
|
+
return fallback;
|
|
311
|
+
const v = Number(raw);
|
|
312
|
+
if (!Number.isFinite(v))
|
|
313
|
+
return fallback;
|
|
314
|
+
const n = Math.floor(v);
|
|
315
|
+
return n >= 1 && n <= 8 ? n : fallback;
|
|
316
|
+
}
|
|
187
317
|
//# sourceMappingURL=config.js.map
|