@astrosheep/pi-context 0.13.0 → 0.15.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.
@@ -1,8 +1,24 @@
1
1
  import { Type } from "@earendil-works/pi-ai";
2
2
  export const nullableString = () => Type.Optional(Type.Union([Type.String(), Type.Null()]));
3
- export const nullableInteger = () => Type.Optional(Type.Union([Type.Integer(), Type.Null()]));
4
3
  export const positiveInteger = () => Type.Optional(Type.Integer({ minimum: 1 }));
5
4
  export const cursor = () => Type.Optional(Type.Integer({ minimum: 0, description: "Continuation cursor: pass the previous next_cursor back unchanged, with the same filters and ordering. Omit to start. next_cursor is null only when the set is exhausted." }));
6
5
  export const recentFirst = () => Type.Optional(Type.Boolean({ description: "Return newest-first. Only an explicit false returns oldest-first. Defaults to true." }));
7
- export const role = Type.Union([Type.Literal("user"), Type.Literal("assistant"), Type.Literal("tool"), Type.Literal("system"), Type.Literal("developer"), Type.Null()]);
6
+ /** Role filter. `developer` is the known author for this extension's own custom entries. */
7
+ export const role = Type.Union([Type.Literal("user"), Type.Literal("assistant"), Type.Literal("tool"), Type.Literal("system"), Type.Literal("developer"), Type.Null()], { description: "Filter by the entry's known author: user/assistant/tool from the conversation, system for native Pi compaction summaries, developer for entries this extension authored (its boot, guidance, warning, and continuation messages, its reset-window compaction summaries, and any other pi-context/* entry)." });
8
+
9
+ /** Search query parameter: one literal, or several literals combined with OR. */
10
+ export const searchQuery = () => Type.Union([Type.String(), Type.Array(Type.String(), { minItems: 1 })]);
11
+
12
+ /**
13
+ * Normalize a search `query` parameter into the literal needles to match.
14
+ * A bare string is a one-element list, so single-query behavior is unchanged.
15
+ * An empty list or a non-string element is refused rather than silently searching
16
+ * for nothing: an empty array is an argument error, not an empty result set.
17
+ */
18
+ export function searchQueries(query: unknown): string[] {
19
+ if (typeof query === "string") return [query];
20
+ if (!Array.isArray(query) || query.length === 0) throw new Error("query must be a string or a non-empty array of strings");
21
+ if (!query.every((candidate) => typeof candidate === "string")) throw new Error("query array elements must be strings");
22
+ return query as string[];
23
+ }
8
24
 
package/src/warning.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { WARNING_TYPE, GUIDANCE_OPEN_TAG, GUIDANCE_CLOSE_TAG, WARNING_PROMPT } from "./protocol.js";
3
+ import { thresholdsFor, resetThresholds, type ResolvedThresholds } from "./thresholds.js";
4
+ import { hasWindowMessage, currentWindowId } from "./history.js";
5
+
6
+ /**
7
+ * The final checkpoint warning, steered to the model once per window. Like the early
8
+ * reminder, the steer text is model-facing only (display: false); the human learns
9
+ * about it from the warning-level notify, not from a chat-visible message.
10
+ */
11
+
12
+ /** Trigger: does the steer fire at this remaining-token count? Pure. */
13
+ export function warningDue(remaining: number, thresholds: ResolvedThresholds): boolean {
14
+ return remaining <= thresholds.warning;
15
+ }
16
+
17
+ /** Delivery: what happens when it fires. */
18
+ export function steerWarning(pi: ExtensionAPI, ctx: ExtensionContext, thresholds: ResolvedThresholds, remaining: number): void {
19
+ pi.sendMessage({ customType: WARNING_TYPE, content: `${GUIDANCE_OPEN_TAG}\n${WARNING_PROMPT}\n${GUIDANCE_CLOSE_TAG}`, display: false }, { triggerTurn: true });
20
+ ctx.ui.notify(`pi-context: context budget critical (${Math.max(0, remaining - thresholds.reserve)} tokens before reserve) — final checkpoint warning steered to the model.`, "warning");
21
+ }
22
+
23
+ /** Registration: once-per-window guard plus trigger+delivery on the context hook. */
24
+ export function registerWarning(pi: ExtensionAPI, isEnabled: () => boolean): void {
25
+ let firedInWindow: string | undefined;
26
+ // Threshold resolution is owned by budget.ts; this module only consumes the shared
27
+ // cache (lazily on the context hook) so session_start never warns twice.
28
+ pi.on("session_start", () => { firedInWindow = undefined; });
29
+ pi.on("session_tree", () => { firedInWindow = undefined; resetThresholds(); });
30
+ pi.on("context", (_event, ctx) => {
31
+ const windowId = currentWindowId(ctx);
32
+ if (!isEnabled() || firedInWindow === windowId || hasWindowMessage(ctx, WARNING_TYPE)) return undefined;
33
+ const usage = ctx.getContextUsage();
34
+ if (!usage || usage.tokens === null) return undefined;
35
+ const remaining = Math.max(0, usage.contextWindow - usage.tokens);
36
+ const thresholds = thresholdsFor(ctx);
37
+ if (!warningDue(remaining, thresholds)) return undefined;
38
+ firedInWindow = windowId;
39
+ // The steer reaches the model at the next sampling step with at most the runway
40
+ // of invisible budget left. After it, the model decides for itself: end the
41
+ // window, or ride it into Pi's automatic compaction, which resets on the spot
42
+ // with no turn (see reset-lifecycle).
43
+ steerWarning(pi, ctx, thresholds, remaining);
44
+ return undefined;
45
+ });
46
+ }