File size: 4,797 Bytes
1944112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
 * A single question, with nothing kept afterwards.
 *
 * Its own task and no session, so two `ask()`s in flight never supersede each
 * other and never queue behind one another for the same engine — which is what
 * would happen if this shared a session key with anything else.
 *
 * @param {import("./engine.js").ScheduledEngine} engine
 * @param {string | Array<{role: string, content: string}>} input
 * @param {object} [opts] anything `complete()` takes; `onDelta` to stream
 * @returns {Promise<string>} the reply text
 */
export function ask(engine: import("./engine.js").ScheduledEngine, input: string | Array<{
    role: string;
    content: string;
}>, { onDelta, ...opts }?: object): Promise<string>;
/**
 * A multi-turn conversation that remembers its own history.
 *
 * Two things this gets right that hand-rolled history usually does not:
 *
 * **One task for the whole conversation.** Every turn carries the same `task`,
 * so a conversation holds at most one engine and a long reply can never occupy
 * the pool while another conversation waits. Turns within it are serialised,
 * which is what a conversation means anyway.
 *
 * **A bounded history, by default.** There is no cross-turn KV reuse on this
 * stack (AI.md: every turn re-prefills the whole history at ~5.27 ms/token), so
 * an unbounded conversation gets quadratically slower and a turn near the 4096
 * limit waits ~22 s for its first token. `keep` bounds it. Set `keep: Infinity`
 * to opt out, having read that sentence.
 *
 * @param {import("./engine.js").ScheduledEngine} engine
 * @param {object} [opts]
 * @param {string} [opts.system] a system message, prepended and never trimmed
 * @param {number} [opts.keep] how many *exchanges* of history to carry
 * @param {string} [opts.task] defaults to a fresh id
 */
export function conversation(engine: import("./engine.js").ScheduledEngine, { system, keep, task, ...defaults }?: {
    system?: string;
    keep?: number;
    task?: string;
}): {
    /** The messages as they would be sent, including the system message. */
    readonly messages: any[];
    /** Exchanges currently retained. */
    readonly length: number;
    /**
     * Say something and get the reply, with the exchange appended to history.
     *
     * Serialised against the previous turn: a conversation whose turns
     * overlapped would interleave history and produce replies to the wrong
     * question.
     *
     * @param {string} content
     * @param {(delta: string) => void} [onDelta]
     */
    say(content: string, onDelta?: (delta: string) => void): Promise<{
        text: string;
        finishReason: "length" | "stop" | "abort";
    }>;
    /** Forget the history. The system message and settings survive. */
    reset(): /*elided*/ any;
    /** Drop the history in, e.g. when restoring a saved conversation. */
    restore(messages: any): /*elided*/ any;
};
/**
 * Ghost text: the scheduling discipline, with the prompt left to the caller.
 *
 * Every part of this exists because of a specific way ghost text goes wrong:
 *
 * | | |
 * | --- | --- |
 * | one stable `session` | a fresh id per keystroke makes every stale request still run — AI.md's first "getting these wrong" row |
 * | `interactive` priority | it is the one band that may preempt work that opted in |
 * | debounce | a request per keystroke queues faster than the GPU drains |
 * | short `max_tokens` | ghost text is a few words; paying for more is pure latency |
 * | resolves `null` when stale | so a superseded suggestion **cannot** be rendered by mistake |
 *
 * That last one is the difference between this and a wrapper. The engine
 * already supersedes stale requests; what a caller still has to remember is not
 * to paint the answer that comes back. Returning `null` removes the choice.
 *
 * @param {import("./engine.js").ScheduledEngine} engine
 * @param {object} opts
 * @param {(context: any) => string | Array<object>} opts.prompt **required** —
 *   builds the messages. Never defaulted: see the module header.
 * @param {number} [opts.debounceMs]
 * @param {number} [opts.maxTokens]
 * @param {string} [opts.session]
 */
export function ghostText(engine: import("./engine.js").ScheduledEngine, { prompt, debounceMs, maxTokens, session, ...defaults }?: {
    prompt: (context: any) => string | Array<object>;
    debounceMs?: number;
    maxTokens?: number;
    session?: string;
}): {
    /**
     * Ask for a suggestion. Debounced, superseding, and `null` when stale.
     *
     * @param {any} context whatever `prompt` takes
     * @returns {Promise<string | null>} `null` if superseded or cancelled
     */
    suggest(context: any): Promise<string | null>;
    /** On blur, or on accept. Cancels in flight and invalidates anything pending. */
    cancel(): number;
};