@buildaharness/aielia 0.3.9 → 0.3.10
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/dist/_memory-core-generated.d.ts +6 -0
- package/dist/assistant.d.ts +11 -0
- package/dist/{bin-CCwDFxDe.js → bin-Cz3FY6Ew.js} +30 -11
- package/dist/bin-Cz3FY6Ew.js.map +1 -0
- package/dist/claude-cli-llm-client.d.ts +2 -0
- package/dist/claude-cli-prompt.d.ts +7 -0
- package/dist/cli.js +1 -1
- package/dist/fact-repository.contract.d.ts +2 -0
- package/dist/fact-repository.d.ts +44 -0
- package/dist/file-tools-mcp-server.mjs +29 -0
- package/dist/harness-bridge.d.ts +5 -5
- package/dist/html-to-markdown.d.ts +11 -0
- package/dist/index.d.ts +82 -2
- package/dist/index.js +4975 -173
- package/dist/index.js.map +1 -1
- package/dist/layer-settings.d.ts +16 -3
- package/dist/memory-events.d.ts +177 -0
- package/dist/memory-service.d.ts +15 -0
- package/dist/model-pricing.d.ts +3 -0
- package/dist/{provider-setup-Cy9dwqrd.js → provider-setup-DG8iYm_F.js} +5756 -4787
- package/dist/provider-setup-DG8iYm_F.js.map +1 -0
- package/dist/scripted-llm-client.d.ts +1 -1
- package/dist/{tui-app-Dt72u8vZ.js → tui-app-oWbIUcBW.js} +3 -3
- package/dist/{tui-app-Dt72u8vZ.js.map → tui-app-oWbIUcBW.js.map} +1 -1
- package/dist/turn-abort.d.ts +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/web-fetch-core.d.ts +0 -14
- package/dist/writer/article-autosave.d.ts +12 -0
- package/dist/writer/article-change-summary.d.ts +28 -0
- package/dist/writer/article-claims.d.ts +49 -0
- package/dist/writer/article-comments.d.ts +20 -0
- package/dist/writer/article-consistency.d.ts +27 -0
- package/dist/writer/article-decorations.d.ts +23 -0
- package/dist/writer/article-dependencies.d.ts +44 -0
- package/dist/writer/article-draft.d.ts +23 -0
- package/dist/writer/article-edit-outline.d.ts +16 -0
- package/dist/writer/article-edit-suggest.d.ts +30 -0
- package/dist/writer/article-folder.d.ts +29 -0
- package/dist/writer/article-history.d.ts +73 -0
- package/dist/writer/article-ideas.d.ts +34 -0
- package/dist/writer/article-issues.d.ts +47 -0
- package/dist/writer/article-marks.d.ts +25 -0
- package/dist/writer/article-memory-store.d.ts +3 -0
- package/dist/writer/article-model.d.ts +1337 -0
- package/dist/writer/article-outline.d.ts +43 -0
- package/dist/writer/article-resolver.d.ts +55 -0
- package/dist/writer/article-revisions.d.ts +30 -0
- package/dist/writer/article-rules.d.ts +60 -0
- package/dist/writer/article-sections.d.ts +26 -0
- package/dist/writer/article-selection.d.ts +26 -0
- package/dist/writer/article-service.d.ts +542 -0
- package/dist/writer/article-source-links.d.ts +42 -0
- package/dist/writer/article-sources.d.ts +82 -0
- package/dist/writer/article-style-derive.d.ts +26 -0
- package/dist/writer/article-style.d.ts +31 -0
- package/dist/writer/article-suggestions.d.ts +33 -0
- package/dist/writer/article-support.d.ts +33 -0
- package/dist/writer/article-verify.d.ts +42 -0
- package/dist/writer/local-folder.d.ts +49 -0
- package/dist/writer/model-failure-hint.d.ts +7 -0
- package/dist/writer/repo-reader.d.ts +29 -0
- package/dist/writer/repo-search.d.ts +33 -0
- package/dist/writer/repo-verify-loop.d.ts +61 -0
- package/dist/writer/text-tool-calls.d.ts +11 -0
- package/dist/writer/work-progress.d.ts +51 -0
- package/dist/writer/write-activity.d.ts +61 -0
- package/dist/writer/write-models.d.ts +12 -0
- package/dist/writer/write-parameters.d.ts +31 -0
- package/dist/writer/write-permissions.d.ts +89 -0
- package/dist/writer/write-spend.d.ts +16 -0
- package/dist/writer/writer-chat-capture.d.ts +25 -0
- package/dist/writer/writer-chat-history.d.ts +17 -0
- package/dist/writer/writer-chat-state.d.ts +358 -0
- package/dist/writer/writer-chat-turn.d.ts +52 -0
- package/dist/writer/writer-intent.d.ts +20 -0
- package/package.json +3 -3
- package/dist/bin-CCwDFxDe.js.map +0 -1
- package/dist/provider-setup-Cy9dwqrd.js.map +0 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { ArticleMeta } from './article-model.js';
|
|
3
|
+
/**
|
|
4
|
+
* A first style sheet drawn from the text of an article that was just loaded: who it seems written for, what it does, how it sounds, how long
|
|
5
|
+
* it is, how the author writes, and the terms it uses consistently. One model call describes what the text shows. The result only fills what the
|
|
6
|
+
* writer has not set: a brief field that already has text is kept, and a writing profile already on the article is kept. Everything it writes is
|
|
7
|
+
* the writer's to edit, and it is never given back to the model as if the writer had said it.
|
|
8
|
+
*/
|
|
9
|
+
/** The most of the article sent, in characters. */
|
|
10
|
+
export declare const STYLE_DERIVE_MAX_CHARS = 12000;
|
|
11
|
+
export interface DerivedStyle {
|
|
12
|
+
status: 'derived' | 'not_derived';
|
|
13
|
+
meta: ArticleMeta;
|
|
14
|
+
/** What was filled in, for a plain sentence to the writer: brief field names, and "writing profile". */
|
|
15
|
+
filled: string[];
|
|
16
|
+
/** Why nothing was derived, when the model call failed or its answer could not be read. */
|
|
17
|
+
reason?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function deriveStyle(input: {
|
|
20
|
+
meta: ArticleMeta;
|
|
21
|
+
markdown: string;
|
|
22
|
+
}, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<DerivedStyle>;
|
|
23
|
+
/** About one page of text. */
|
|
24
|
+
export declare const STYLE_DERIVE_MIN_WORDS = 300;
|
|
25
|
+
/** Whether the style sheet should be drawn from the text now: there is a page of text and the writer has set nothing yet. */
|
|
26
|
+
export declare function shouldDeriveStyle(meta: Pick<ArticleMeta, 'brief' | 'style'>, markdown: string): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ArticleMeta, StyleProfile } from './article-model.js';
|
|
2
|
+
import { SettingsStore } from './write-permissions.js';
|
|
3
|
+
declare const BRIEF_FIELDS: readonly ["audience", "purpose", "tone", "length", "notes"];
|
|
4
|
+
/** Replaces the article's brief. Empty fields are dropped; an empty brief removes it. */
|
|
5
|
+
export declare function setBrief(meta: ArticleMeta, input: Partial<Record<(typeof BRIEF_FIELDS)[number], string | undefined>>): ArticleMeta;
|
|
6
|
+
/** Normalises a profile: trims text, drops empty terms and repeats, caps the list. */
|
|
7
|
+
export declare function cleanProfile(p: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
voice?: string;
|
|
11
|
+
terms?: {
|
|
12
|
+
use: string;
|
|
13
|
+
instead?: string;
|
|
14
|
+
}[];
|
|
15
|
+
}): StyleProfile | undefined;
|
|
16
|
+
/** Gives the article a copy of a profile, or removes it with null. */
|
|
17
|
+
export declare function applyProfile(meta: ArticleMeta, profile: StyleProfile | null): ArticleMeta;
|
|
18
|
+
/** The plain text the assistant is given, or '' when the writer set neither a brief nor a profile. */
|
|
19
|
+
export declare function renderStyleGuide(meta: Pick<ArticleMeta, 'brief' | 'style'>): string;
|
|
20
|
+
export declare function loadProfiles(store: SettingsStore): Promise<StyleProfile[]>;
|
|
21
|
+
export declare function saveProfile(store: SettingsStore, profile: {
|
|
22
|
+
id?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
voice?: string;
|
|
25
|
+
terms?: {
|
|
26
|
+
use: string;
|
|
27
|
+
instead?: string;
|
|
28
|
+
}[];
|
|
29
|
+
}, newId?: () => string): Promise<StyleProfile | undefined>;
|
|
30
|
+
export declare function deleteProfile(store: SettingsStore, id: string): Promise<void>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ArticleMeta } from './article-model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Suggested changes (T9). A suggestion never touches the Markdown until the writer accepts it. It stores the exact
|
|
4
|
+
* quoted text it replaces; if that text is no longer where it was (or at all), the suggestion is stale and cannot be
|
|
5
|
+
* accepted as it stands. All functions are pure.
|
|
6
|
+
*/
|
|
7
|
+
export type SuggestionResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
meta: ArticleMeta;
|
|
10
|
+
markdown: string;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function proposeSuggestion(meta: ArticleMeta, markdown: string, input: {
|
|
16
|
+
quote: string;
|
|
17
|
+
replacement: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
by: 'assistant' | 'writer';
|
|
20
|
+
now: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
ideaIds?: string[];
|
|
23
|
+
commentIds?: string[];
|
|
24
|
+
sectionIds?: string[]; /** Look for the quote from here on, so the first of several equal passages is not always the one meant. */
|
|
25
|
+
from?: number;
|
|
26
|
+
}): SuggestionResult;
|
|
27
|
+
/** Marks pending suggestions stale when their quoted text no longer exists; refreshes the span of those that moved. */
|
|
28
|
+
export declare function refreshSuggestions(meta: ArticleMeta, markdown: string): ArticleMeta;
|
|
29
|
+
/** Accepts a pending suggestion, optionally with the writer's own replacement text. Returns the new Markdown. */
|
|
30
|
+
export declare function acceptSuggestion(meta: ArticleMeta, markdown: string, id: string, edited?: string): SuggestionResult;
|
|
31
|
+
export declare function rejectSuggestion(meta: ArticleMeta, id: string): SuggestionResult & {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { ArticleMeta, Claim, Source, SourceStatus } from './article-model.js';
|
|
3
|
+
/**
|
|
4
|
+
* Per-statement support check (T7), built on the pattern of grounding-check.ts: one bounded call that compares a claim
|
|
5
|
+
* with the stored passages attached to it. The model judges; this module stores the verdict together with what it ran
|
|
6
|
+
* on (the claim text and the source fingerprints), so effectiveSourceStatus can later refuse to show a verdict that no
|
|
7
|
+
* longer applies. A check that could not run is never a pass: the claim keeps its status and the result says not_checked.
|
|
8
|
+
*/
|
|
9
|
+
export type SupportVerdict = Extract<SourceStatus, 'supported' | 'partly_supported' | 'not_supported' | 'contradicted'>;
|
|
10
|
+
export type SupportCheck = {
|
|
11
|
+
status: 'checked';
|
|
12
|
+
meta: ArticleMeta;
|
|
13
|
+
verdict: SupportVerdict;
|
|
14
|
+
reason: string;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'no_sources';
|
|
17
|
+
meta: ArticleMeta;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'not_checked';
|
|
20
|
+
meta: ArticleMeta;
|
|
21
|
+
reason: string;
|
|
22
|
+
};
|
|
23
|
+
/** The sources linked to a claim by a supported_by link, in link order. */
|
|
24
|
+
export declare function sourcesForClaim(meta: ArticleMeta, claimId: string): Source[];
|
|
25
|
+
/** Text the model wrote or co-wrote is checked automatically (T7); a claim the writer alone wrote is checked on request. */
|
|
26
|
+
export declare function needsAutoCheck(claim: Claim): boolean;
|
|
27
|
+
export declare function checkClaimSupport(input: {
|
|
28
|
+
meta: ArticleMeta;
|
|
29
|
+
markdown: string;
|
|
30
|
+
claimId: string;
|
|
31
|
+
modelName: string;
|
|
32
|
+
now: string;
|
|
33
|
+
}, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<SupportCheck>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ArticleMeta, Claim } from './article-model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifying claims (pure parts): which claims to take, and how a search that found nothing is said. The search, the reading and the model
|
|
4
|
+
* check themselves are run by ArticleService.verifyClaims; whether a page backs a claim is the support check's judgement, never decided here.
|
|
5
|
+
*/
|
|
6
|
+
export type VerifyOutcome = 'supported' | 'partly_supported' | 'contradicted' | 'not_supported' | 'no_source_found' | 'cannot_search' | 'search_failed' | 'could_not_check';
|
|
7
|
+
export interface VerifyEntry {
|
|
8
|
+
claimId: string;
|
|
9
|
+
quote: string;
|
|
10
|
+
outcome: VerifyOutcome;
|
|
11
|
+
/** Addresses of pages found and kept as sources for the claim. */
|
|
12
|
+
found: string[];
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface VerifyReport {
|
|
16
|
+
entries: VerifyEntry[];
|
|
17
|
+
/** Factual claims left for another run because of the limit. */
|
|
18
|
+
leftOver: number;
|
|
19
|
+
/** The writer stopped the run: the entries are the claims checked before that. */
|
|
20
|
+
stopped?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_VERIFY_LIMIT = 10;
|
|
23
|
+
export declare const DEFAULT_VERIFY_PAGES = 3;
|
|
24
|
+
/**
|
|
25
|
+
* The claims to verify, in text order: the factual ones that are not already supported by a check that still counts. Named claims are taken
|
|
26
|
+
* even when supported (the writer asked for them). At most `limit` are taken; the count of the rest is returned.
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyTargets(meta: ArticleMeta, claimIds: readonly string[] | undefined, limit: number): {
|
|
29
|
+
ids: string[];
|
|
30
|
+
leftOver: number;
|
|
31
|
+
};
|
|
32
|
+
/** The lookup that still applies to the claim (its text has not changed since), or undefined. */
|
|
33
|
+
export declare function currentLookup(claim: Claim): NonNullable<Claim['lookup']> | undefined;
|
|
34
|
+
/** What the page says about a claim that was looked for and not backed; undefined when there was no lookup or sources have since been attached. */
|
|
35
|
+
export declare function lookupNote(claim: Claim, hasSources: boolean): string | undefined;
|
|
36
|
+
/** One sentence for the chat or a notice about a finished run. */
|
|
37
|
+
export declare function summarizeVerify(report: VerifyReport): string;
|
|
38
|
+
/**
|
|
39
|
+
* Where the whole article stands, from its records: a run only looks at some claims (a limit, or the ones already backed are skipped), so a later
|
|
40
|
+
* run's summary alone would read as if it were the result for the whole article. Empty when the article has no factual claims.
|
|
41
|
+
*/
|
|
42
|
+
export declare function summarizeArticleSources(meta: ArticleMeta): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A folder on the writer's own computer (a checkout of a repository, say) that the writer has allowed the page to read.
|
|
3
|
+
* The page reads from it only what the writer picks, and never writes to it. The reader is held for the session; nothing about it is stored
|
|
4
|
+
* in the article except the file's address, written as `<folder name>/<path>`, which is how a file is found again.
|
|
5
|
+
*/
|
|
6
|
+
export interface FolderReader {
|
|
7
|
+
/** The folder's own name, the first part of every file's address. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Paths of the readable files under the folder, relative and with `/` separators. */
|
|
10
|
+
files(): Promise<string[]>;
|
|
11
|
+
/** The file's text, or undefined when it is gone or cannot be read. */
|
|
12
|
+
read(path: string): Promise<string | undefined>;
|
|
13
|
+
}
|
|
14
|
+
/** The most files a listing keeps; a repository with more is listed level by level and the deepest are left out. */
|
|
15
|
+
export declare const MAX_FILES = 20000;
|
|
16
|
+
export declare function isSkippedFolder(name: string): boolean;
|
|
17
|
+
/** The address a folder file is stored under. */
|
|
18
|
+
export declare function folderFileAddress(folderName: string, path: string): string;
|
|
19
|
+
/** The path inside the folder for an address stored by folderFileAddress, or undefined when the address belongs to another folder. */
|
|
20
|
+
export declare function pathInFolder(folderName: string, address: string): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Walks a folder through a lister that says, for a directory path, its entries and which are folders. Skipped folders are left out, only files the
|
|
23
|
+
* Write page can read as text or code are kept, and the walk stops at a fixed depth and file count.
|
|
24
|
+
*/
|
|
25
|
+
export declare function walkFolder(list: (dir: string) => Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
directory: boolean;
|
|
28
|
+
}[]>): Promise<string[]>;
|
|
29
|
+
/**
|
|
30
|
+
* A reader over a file system that can list a directory with each entry marked as a file or a folder (the desktop app's `workspace_list_dir`),
|
|
31
|
+
* so subfolders are walked into without guessing which names are folders. `root` is the folder's absolute path.
|
|
32
|
+
*/
|
|
33
|
+
export declare function folderFromListing(fs: {
|
|
34
|
+
readTextFile(path: string): Promise<string | undefined>;
|
|
35
|
+
listDir(path: string): Promise<{
|
|
36
|
+
name: string;
|
|
37
|
+
directory: boolean;
|
|
38
|
+
}[]>;
|
|
39
|
+
}, root: string, name: string): FolderReader;
|
|
40
|
+
/**
|
|
41
|
+
* A reader over a file-system backend that has only readTextFile and readDir (the desktop app's). It cannot tell a folder from a file by name, so a
|
|
42
|
+
* path that cannot be listed as a directory is taken to be a file.
|
|
43
|
+
*/
|
|
44
|
+
export declare function folderFromFsBackend(fs: {
|
|
45
|
+
readTextFile(path: string): Promise<string | undefined>;
|
|
46
|
+
readDir(path: string): Promise<string[]>;
|
|
47
|
+
}, root: string, name: string): FolderReader;
|
|
48
|
+
/** The files whose path contains every word typed (any case), shortest path first; at most `limit`. A plain filter, no meaning is read from the words. */
|
|
49
|
+
export declare function matchFolderFiles(files: string[], query: string, limit?: number): string[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to try after a model call failed, chosen by the backend and not by the wording of the error. The Claude CLI backend has no key or model
|
|
3
|
+
* to check: its calls fail when the CLI is not signed in, so the hint says to sign in. Every other backend is told to check the key and model.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_MODEL_HINT = "Check your key and the model in Settings, or pick another model next to the chat, then try again.";
|
|
6
|
+
export declare const CLAUDE_SIGN_IN_HINT = "Sign in to Claude (run \"claude\" in a terminal and use /login), or pick another model next to the chat, then try again.";
|
|
7
|
+
export declare function modelFailureHint(backend: string | undefined): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FolderReader } from './local-folder.js';
|
|
2
|
+
/**
|
|
3
|
+
* A repository the writer hands to Write so claims about code can be found in it and verified: a folder on the writer's computer (the same
|
|
4
|
+
* reader as for folder files) or a GitHub repository. Both only list and read; neither writes. A GitHub repository is read through GitHub's
|
|
5
|
+
* own interfaces (the file list from the API, file text from raw.githubusercontent.com), which a browser may call directly. A private
|
|
6
|
+
* repository needs the writer's own token, kept in this browser.
|
|
7
|
+
*/
|
|
8
|
+
export interface RepoReader extends FolderReader {
|
|
9
|
+
/** The address a file of this repository is stored under as a source (a link for GitHub, `<folder>/<path>` for a folder). */
|
|
10
|
+
addressOf?(path: string): string;
|
|
11
|
+
}
|
|
12
|
+
export interface GithubRepoSpec {
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
/** A branch, tag or commit; the repository's default branch when absent. */
|
|
16
|
+
ref?: string;
|
|
17
|
+
/** Only the files under this folder (from an address like github.com/o/r/tree/main/packages/x). */
|
|
18
|
+
path?: string;
|
|
19
|
+
}
|
|
20
|
+
/** The repository named by a GitHub address (github.com/o/r, with an optional /tree/<ref>), or `o/r`. Anything else is undefined. */
|
|
21
|
+
export declare function parseGithubRepoAddress(input: string): GithubRepoSpec | undefined;
|
|
22
|
+
export interface GithubReaderOptions {
|
|
23
|
+
fetchImpl?: typeof fetch;
|
|
24
|
+
/** The writer's own GitHub token, for a private repository. Sent only to GitHub. */
|
|
25
|
+
token?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function githubRepoReader(spec: GithubRepoSpec, options?: GithubReaderOptions): RepoReader;
|
|
28
|
+
/** Several repositories read as one: each file is listed as `<repository name>/<path>`, and reads and addresses are routed back to the repository it came from. */
|
|
29
|
+
export declare function combineRepos(readers: readonly RepoReader[]): RepoReader;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FolderReader } from './local-folder.js';
|
|
2
|
+
/**
|
|
3
|
+
* A searchable copy of a repository held in memory for one session, so a model can look things up (search, read some lines, list a folder) instead of being
|
|
4
|
+
* shown a path list and asked to guess. Built from any reader (a local folder or a GitHub repository); nothing is written anywhere. Pure: no node or DOM imports.
|
|
5
|
+
*/
|
|
6
|
+
/** Files bigger than this are left out (generated or data files that would only drown the search). */
|
|
7
|
+
export declare const MAX_INDEXED_FILE_CHARS = 300000;
|
|
8
|
+
/** The most characters of text a single search or read answers with. */
|
|
9
|
+
export declare const MAX_ANSWER_CHARS = 4000;
|
|
10
|
+
export declare const MAX_MATCHES = 30;
|
|
11
|
+
export interface RepoIndex {
|
|
12
|
+
/** Number of files held. */
|
|
13
|
+
readonly fileCount: number;
|
|
14
|
+
/** Lines matching a pattern (case-insensitive regular expression; an invalid one is searched as plain text), as `path:line: text`. `glob` narrows the paths (`*.ts`, a folder, or a part of a path). */
|
|
15
|
+
search(pattern: string, glob?: string): string;
|
|
16
|
+
/** The lines `from`..`to` (1-based, inclusive) of a file, numbered; at most MAX_ANSWER_CHARS characters. */
|
|
17
|
+
readLines(path: string, from?: number, to?: number): string;
|
|
18
|
+
/** The files and folders directly under a folder ('' for the top). */
|
|
19
|
+
list(folder?: string): string;
|
|
20
|
+
/** The whole text of a file, or undefined. */
|
|
21
|
+
text(path: string): string | undefined;
|
|
22
|
+
/** Whether the file exists in the index. */
|
|
23
|
+
has(path: string): boolean;
|
|
24
|
+
/** How many files the reader listed, and how many of them could not be read at all (a failed or rate-limited fetch, not an empty or oversized file). */
|
|
25
|
+
readonly listed: number;
|
|
26
|
+
readonly unreadable: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function createRepoIndex(files: ReadonlyMap<string, string>, stats?: {
|
|
29
|
+
listed?: number;
|
|
30
|
+
unreadable?: number;
|
|
31
|
+
}): RepoIndex;
|
|
32
|
+
/** Reads every file the reader lists (skipping the oversized and the empty) into an index. */
|
|
33
|
+
export declare function buildRepoIndex(reader: FolderReader): Promise<RepoIndex>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ILLMClient, TokenUsage, ToolDefinition } from '@buildaharness/runtime';
|
|
2
|
+
import { RepoIndex } from './repo-search.js';
|
|
3
|
+
/**
|
|
4
|
+
* Checking a batch of claims against a repository with a model that can look things up: it searches, reads some lines, and answers each claim with
|
|
5
|
+
* a verdict and the file lines and exact words that decide it. The loop owns everything mechanical: it runs the lookups, refuses answers whose quoted
|
|
6
|
+
* words are not in the named file, and sends the claims that came back unsettled or unsourced to the model once more in the same conversation
|
|
7
|
+
* (the earlier turns are the shared start of that call, so a provider's prompt cache serves them). The model judges; it is never trusted on a quote.
|
|
8
|
+
*/
|
|
9
|
+
export type LoopVerdict = 'supported' | 'partly_supported' | 'contradicted' | 'not_supported' | 'not_about_code';
|
|
10
|
+
export interface LoopEvidence {
|
|
11
|
+
/** Repository path. */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Line range of the excerpt in the file as found (1-based), recomputed from the file, not taken from the model. */
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
excerpt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LoopResult {
|
|
19
|
+
id: string;
|
|
20
|
+
verdict: LoopVerdict;
|
|
21
|
+
reason: string;
|
|
22
|
+
/** Only excerpts that were found word for word in their file. */
|
|
23
|
+
evidence: LoopEvidence[];
|
|
24
|
+
/** What was searched, when the claim stayed unsettled. */
|
|
25
|
+
searched: string[];
|
|
26
|
+
/** Why this result is not trustworthy, when it is not (no answer, no usable quote); absent for a settled claim. */
|
|
27
|
+
problem?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface LoopStep {
|
|
30
|
+
tool: string;
|
|
31
|
+
input: Record<string, unknown>;
|
|
32
|
+
chars: number;
|
|
33
|
+
}
|
|
34
|
+
export interface LoopOptions {
|
|
35
|
+
model?: string;
|
|
36
|
+
onUsage?: (usage: TokenUsage) => void;
|
|
37
|
+
onStep?: (step: LoopStep) => void;
|
|
38
|
+
/** The most lookups for the first pass and again for the follow-up pass. Past it the model is told to answer. */
|
|
39
|
+
maxToolCalls?: number;
|
|
40
|
+
/** The most model turns for one pass (a safety net against a model that never answers). */
|
|
41
|
+
maxTurns?: number;
|
|
42
|
+
/** The most tokens one model turn may produce (default 12000: a 25-claim answer is about 6000). A turn that runs away costs a whole run otherwise. */
|
|
43
|
+
maxTokens?: number;
|
|
44
|
+
/** Send unsettled claims back once (default true). */
|
|
45
|
+
gapPass?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare const REPO_TOOLS: ToolDefinition[];
|
|
48
|
+
/** Where the excerpt sits in the file: its first and last line, or undefined when its words are not there. `...` inside an excerpt joins separate pieces. */
|
|
49
|
+
export declare function locateExcerpt(text: string, excerpt: string): {
|
|
50
|
+
from: number;
|
|
51
|
+
to: number;
|
|
52
|
+
} | undefined;
|
|
53
|
+
/** Turns the model's answer into one result per claim; anything missing or unquotable is marked with its problem. */
|
|
54
|
+
export declare function checkAnswers(claimIds: readonly string[], rows: unknown, index: RepoIndex): LoopResult[];
|
|
55
|
+
export declare function verifyBatchWithTools(input: {
|
|
56
|
+
claims: readonly {
|
|
57
|
+
id: string;
|
|
58
|
+
quote: string;
|
|
59
|
+
}[];
|
|
60
|
+
articleText: string;
|
|
61
|
+
}, index: RepoIndex, llm: ILLMClient, opts?: LoopOptions): Promise<LoopResult[]>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ChatMessage, ChatOptions, ILLMClient, LLMStructuredResponse, ToolDefinition } from '@buildaharness/runtime';
|
|
2
|
+
/**
|
|
3
|
+
* Tool use for a backend that cannot take tool definitions itself (the local `claude -p` client runs its own tool loop and hands back only text).
|
|
4
|
+
* The tools are described in the system text, the conversation so far is written out as one prompt, and the model asks for lookups with a JSON
|
|
5
|
+
* object, which is turned back into the same tool calls an API backend returns. The caller's loop then runs the lookups as for any backend.
|
|
6
|
+
* Pure: no node or DOM imports.
|
|
7
|
+
*/
|
|
8
|
+
/** Whether the client takes tool definitions and returns tool calls itself (every API backend does; `claude -p` does not). */
|
|
9
|
+
export declare function hasNativeToolCalls(llm: ILLMClient): boolean;
|
|
10
|
+
/** One model turn with tools, over plain text. Returns tool calls when the reply asks for lookups, else the reply as the content. */
|
|
11
|
+
export declare function callWithTextTools(llm: ILLMClient, messages: ChatMessage[], tools: ToolDefinition[], options: ChatOptions): Promise<LLMStructuredResponse>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Write tells the writer while a model call runs in the background: a short checklist for the kind of job, and the sentence for
|
|
3
|
+
* the stage it is at. Pure, so the browser and desktop builds render the same words. The stages are the real boundaries of the work;
|
|
4
|
+
* nothing here cycles on a timer.
|
|
5
|
+
*/
|
|
6
|
+
export type WorkKind = 'edit' | 'draft' | 'suggest' | 'answer' | 'verify' | 'check'
|
|
7
|
+
/** Any change to the article that saves the text first and reloads it after; the middle step is the action's own label. */
|
|
8
|
+
| 'change' | 'resolve' | 'files' | 'publish' | 'prepare' | 'style' | 'import' | 'page' | 'other';
|
|
9
|
+
export interface WorkPlan {
|
|
10
|
+
/** The stages of this kind of job, in order, each a phrase for a checklist ("Reading your request"). */
|
|
11
|
+
steps: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
/** The checklist for a kind of job; empty when the job is too short to need one. A 'change' is named by the action's own `label`. */
|
|
14
|
+
export declare function workPlan(kind: WorkKind, label?: string): WorkPlan;
|
|
15
|
+
export type StepState = 'done' | 'current' | 'waiting';
|
|
16
|
+
export interface ChecklistItem {
|
|
17
|
+
text: string;
|
|
18
|
+
state: StepState;
|
|
19
|
+
}
|
|
20
|
+
/** The checklist with `current` as the step in progress: earlier ones done, later ones waiting. An index past the end marks all done. */
|
|
21
|
+
export declare function checklistAt(plan: WorkPlan, current: number): ChecklistItem[];
|
|
22
|
+
/** The sentence under the checklist for the current stage, in the first person ("Reading your request…"). */
|
|
23
|
+
export declare function stageSentence(plan: WorkPlan, current: number): string | undefined;
|
|
24
|
+
/** A running time for a job that has been going a while; nothing for the first few seconds, when it would only be noise. */
|
|
25
|
+
export declare function elapsedText(ms: number, after?: number): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* The stretch of `after` that differs from `before`, found from the text both start and end with; null when nothing was added or changed
|
|
28
|
+
* (a pure deletion leaves nothing to light). Used to light the passage a job has just written.
|
|
29
|
+
*/
|
|
30
|
+
export declare function changedRange(before: string, after: string): {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
} | null;
|
|
34
|
+
/**
|
|
35
|
+
* The passage to light after a job changed the text: the changed range, unless it is most of the text (a first draft, a full rewrite),
|
|
36
|
+
* where lighting it would only tint the whole article and point at nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function litRange(before: string, after: string, maxShare?: number): {
|
|
39
|
+
start: number;
|
|
40
|
+
end: number;
|
|
41
|
+
} | null;
|
|
42
|
+
export type ResultTone = 'good' | 'warn' | 'bad';
|
|
43
|
+
/** One finished piece of a counted job, shown under the bar as it settles ("Backed: the p99 was 18 ms"). */
|
|
44
|
+
export interface ResultLine {
|
|
45
|
+
text: string;
|
|
46
|
+
tone: ResultTone;
|
|
47
|
+
}
|
|
48
|
+
/** The line for one checked claim: what came of it, then the claim cut to `max` characters. */
|
|
49
|
+
export declare function claimResultLine(outcome: string, quote: string, max?: number): ResultLine;
|
|
50
|
+
/** The lines kept on show: the newest few, so a long run does not push the rest of the block away. */
|
|
51
|
+
export declare function recentResults(lines: readonly ResultLine[], line: ResultLine, keep?: number): ResultLine[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the writer has done with Write, kept in this browser (or app) only and never sent anywhere: one small record per background job, so
|
|
3
|
+
* how Write is used (which jobs, how often they finish, how long they take, what they cost) can be seen without asking anyone.
|
|
4
|
+
* A record holds the kind of job, how it ended, the time and the cost; it never holds the writer's words, an article's name or a label.
|
|
5
|
+
*/
|
|
6
|
+
export type JobOutcome = 'done' | 'stopped' | 'failed';
|
|
7
|
+
export interface JobRecord {
|
|
8
|
+
/** ISO time the job started. */
|
|
9
|
+
at: string;
|
|
10
|
+
/** The kind of job (a WorkKind: edit, draft, verify, publish...). */
|
|
11
|
+
kind: string;
|
|
12
|
+
outcome: JobOutcome;
|
|
13
|
+
ms: number;
|
|
14
|
+
usd: number;
|
|
15
|
+
calls: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const ACTIVITY_KEY = "writer:activity";
|
|
18
|
+
/** The newest records kept; older ones fall away so the record cannot grow without end. */
|
|
19
|
+
export declare const MAX_ACTIVITY_RECORDS = 1000;
|
|
20
|
+
export declare const isJobRecord: (x: unknown) => x is JobRecord;
|
|
21
|
+
export declare function addRecord(records: readonly JobRecord[], record: JobRecord): JobRecord[];
|
|
22
|
+
export interface KindSummary {
|
|
23
|
+
kind: string;
|
|
24
|
+
runs: number;
|
|
25
|
+
done: number;
|
|
26
|
+
stopped: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
medianMs: number;
|
|
29
|
+
usd: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ActivitySummary {
|
|
32
|
+
jobs: number;
|
|
33
|
+
firstAt?: string;
|
|
34
|
+
lastAt?: string;
|
|
35
|
+
/** Distinct days with at least one job. */
|
|
36
|
+
days: number;
|
|
37
|
+
usd: number;
|
|
38
|
+
/** Share of jobs that ended in 'done' (0 to 1); undefined with no jobs. */
|
|
39
|
+
doneRate?: number;
|
|
40
|
+
kinds: KindSummary[];
|
|
41
|
+
}
|
|
42
|
+
/** Counts per kind of job, most used first. */
|
|
43
|
+
export declare function summarizeActivity(records: readonly JobRecord[]): ActivitySummary;
|
|
44
|
+
/** The summary as plain text the writer can read, or copy to tell someone how Write went. */
|
|
45
|
+
export declare function activityReport(s: ActivitySummary): string;
|
|
46
|
+
/** Finished runs of a kind needed before a typical time is worth saying. */
|
|
47
|
+
export declare const MIN_RUNS_FOR_TYPICAL = 3;
|
|
48
|
+
/** How long a kind of job usually takes: the median of its finished runs, or undefined until there are enough of them. */
|
|
49
|
+
export declare function typicalMs(records: readonly JobRecord[], kind: string): number | undefined;
|
|
50
|
+
/** "Usually about 20 s": rounded so it reads as an expectation, not a measurement (to 5 s under a minute, to 15 s above). Nothing for a job that is quick anyway. */
|
|
51
|
+
export declare function usuallyText(ms: number | undefined): string | undefined;
|
|
52
|
+
export interface ActivityStore {
|
|
53
|
+
getSetting(key: string): Promise<unknown>;
|
|
54
|
+
setSetting(key: string, value: unknown): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export interface ActivityLog {
|
|
57
|
+
record(job: JobRecord): Promise<void>;
|
|
58
|
+
read(): Promise<JobRecord[]>;
|
|
59
|
+
}
|
|
60
|
+
/** The log over a settings store. A failure to save never reaches the job that was being recorded. */
|
|
61
|
+
export declare function createActivityLog(store: ActivityStore): ActivityLog;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** What to call a model id in the page. The Claude CLI backend has no default id of its own (the CLI picks the model), so its id is empty: say so rather than show nothing. */
|
|
2
|
+
export declare const CLI_DEFAULT_LABEL = "Claude default";
|
|
3
|
+
export declare const modelLabel: (model: string | undefined) => string;
|
|
4
|
+
export declare function suggestedModels(backend: string): string[];
|
|
5
|
+
export interface WriteModelDefaults {
|
|
6
|
+
/** For the chat, edit suggestions and checks the writer starts from the chat. */
|
|
7
|
+
chat: string;
|
|
8
|
+
/** For checks that run by themselves after typing. */
|
|
9
|
+
auto: string;
|
|
10
|
+
}
|
|
11
|
+
/** `configured` is the model from the visitor's settings, when they set one. */
|
|
12
|
+
export declare function defaultWriteModels(backend: string, configured?: string): WriteModelDefaults;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The adjustments a writer can ask for in the chat. The first menu holds a handful of categories; picking one opens a single field whose
|
|
3
|
+
* suggestions are "Aspect: option" pairs (type to filter, or type your own words). Whatever is sent becomes an ordinary chat message.
|
|
4
|
+
* Two kinds, which only change the wording of the instruction: "setting" (how the article should be written: tone, length, audience ...)
|
|
5
|
+
* and "revision" (a one-off action on the text: shorten the introduction, add examples ...).
|
|
6
|
+
* Settings that are not about the text itself live elsewhere (Style tab, Sources tab, the Do actions) and are deliberately not repeated here.
|
|
7
|
+
*/
|
|
8
|
+
export type ParameterGroup = 'tone' | 'audience' | 'length' | 'structure' | 'seo' | 'sources' | 'promotion' | 'brand' | 'revise';
|
|
9
|
+
export type ParameterKind = 'setting' | 'revision';
|
|
10
|
+
export interface WriteParameter {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
options: string[];
|
|
14
|
+
group: ParameterGroup;
|
|
15
|
+
kind: ParameterKind;
|
|
16
|
+
}
|
|
17
|
+
export declare const WRITE_PARAMETERS: WriteParameter[];
|
|
18
|
+
export declare function findParameter(id: string): WriteParameter | undefined;
|
|
19
|
+
export declare const PARAMETER_GROUPS: {
|
|
20
|
+
id: ParameterGroup;
|
|
21
|
+
label: string;
|
|
22
|
+
}[];
|
|
23
|
+
/** The suggestions of a category, each one "Aspect: option". */
|
|
24
|
+
export declare function adjustmentChoices(group: ParameterGroup): string[];
|
|
25
|
+
/** The chat message an adjustment turns into. Any aspect and any value are accepted, listed or not. */
|
|
26
|
+
export declare function adjustmentInstruction(aspect: string, value: string, kind: ParameterKind): string;
|
|
27
|
+
/**
|
|
28
|
+
* What the writer typed in a category's field. A suggestion ("Tone of voice: Witty", or its aspect with a value of their own) keeps its
|
|
29
|
+
* aspect; any other words are taken as the request itself. `group` is absent for "Something else".
|
|
30
|
+
*/
|
|
31
|
+
export declare function adjustmentFromText(group: ParameterGroup | undefined, text: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ChatMessage, ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
3
|
+
/**
|
|
4
|
+
* The permission list (T11): what the assistant may do on its own in Aielia Write, with defaults the writer can change, and
|
|
5
|
+
* a spending cap that stops model calls once reached. Changes the text only ever as suggestions (T9), so there is no
|
|
6
|
+
* "edit directly" permission to grant. Pure data plus a client wrapper; storage goes through any `get`/`set` of settings.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WritePermissionsSchema: z.ZodObject<{
|
|
9
|
+
/** Read each message for reviewer comments, decisions and corrections (T13). On by default; off means nothing is recorded from chat. */
|
|
10
|
+
captureChat: z.ZodBoolean;
|
|
11
|
+
/** Run the claim check and the effects check on its own after the writer stops typing. Off by default: it spends the writer's money without being asked. */
|
|
12
|
+
autoCheck: z.ZodBoolean;
|
|
13
|
+
/** Read the web sources again just before publishing, and say which changed or could not be reached. On by default: it costs a model call only for a page that changed. Publishing is never blocked by it. */
|
|
14
|
+
recheckBeforePublish: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
/** Stop all model calls once this much has been spent in this browser, in US dollars. Unset means no cap. */
|
|
16
|
+
spendCapUsd: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
captureChat: boolean;
|
|
19
|
+
autoCheck: boolean;
|
|
20
|
+
recheckBeforePublish: boolean;
|
|
21
|
+
spendCapUsd?: number | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
captureChat: boolean;
|
|
24
|
+
autoCheck: boolean;
|
|
25
|
+
recheckBeforePublish?: boolean | undefined;
|
|
26
|
+
spendCapUsd?: number | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export type WritePermissions = z.infer<typeof WritePermissionsSchema>;
|
|
29
|
+
export declare const DEFAULT_WRITE_PERMISSIONS: WritePermissions;
|
|
30
|
+
export interface SettingsStore {
|
|
31
|
+
getSetting(key: string): Promise<unknown>;
|
|
32
|
+
setSetting(key: string, value: unknown): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare function loadPermissions(store: SettingsStore): Promise<WritePermissions>;
|
|
35
|
+
export declare function savePermissions(store: SettingsStore, permissions: WritePermissions): Promise<void>;
|
|
36
|
+
export declare class SpendCapReachedError extends Error {
|
|
37
|
+
readonly capUsd: number;
|
|
38
|
+
readonly spentUsd: number;
|
|
39
|
+
constructor(capUsd: number, spentUsd: number);
|
|
40
|
+
}
|
|
41
|
+
/** Running total of what the model calls cost, persisted so a reload does not reset the cap. */
|
|
42
|
+
export interface SpendMeter {
|
|
43
|
+
spentUsd(): number;
|
|
44
|
+
/** `model` is the model that made the call; it is priced by that model, falling back to the meter's default. */
|
|
45
|
+
record(usage: CountedUsage, callType?: string, model?: string): Promise<void>;
|
|
46
|
+
/** Dollars and number of calls so far for each kind of call (the chat, the claim check, ...). Calls made before this was kept are in the total only. */
|
|
47
|
+
byType(): Record<string, {
|
|
48
|
+
usd: number;
|
|
49
|
+
calls: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** Throws SpendCapReachedError when the cap is set and reached. */
|
|
52
|
+
assertWithinCap(capUsd: number | undefined): void;
|
|
53
|
+
/** Back to nothing spent, after the stored totals were deleted. */
|
|
54
|
+
reset(): void;
|
|
55
|
+
/** Dollars, tokens and calls counted since the page was opened (the dollars include what earlier visits spent). A caller takes one before and one after a request to know what that request cost. */
|
|
56
|
+
snapshot(): SpendSnapshot;
|
|
57
|
+
/** Called after every counted call and after a reset, so what is shown follows the real total. Returns the way to stop listening. */
|
|
58
|
+
subscribe(listener: () => void): () => void;
|
|
59
|
+
}
|
|
60
|
+
/** `cachedTokens` is the part of the input that came from the provider's cache; absent until some call has reported it (a backend that does not report it is not the same as none cached). */
|
|
61
|
+
/** `estimatedInputTokens` is the part of `inputTokens` that was worked out from the size of the requests because the backend reported far fewer than they held. */
|
|
62
|
+
export interface SpendSnapshot {
|
|
63
|
+
usd: number;
|
|
64
|
+
inputTokens: number;
|
|
65
|
+
outputTokens: number;
|
|
66
|
+
calls: number;
|
|
67
|
+
cachedTokens?: number;
|
|
68
|
+
estimatedInputTokens?: number;
|
|
69
|
+
}
|
|
70
|
+
/** Token counts as the meter takes them: `estimated` marks an input count that was worked out from the request, not reported. */
|
|
71
|
+
export type CountedUsage = TokenUsage & {
|
|
72
|
+
estimated?: boolean;
|
|
73
|
+
};
|
|
74
|
+
/** About four characters to a token: a rough size for a request, used only when the backend's own count cannot be believed. */
|
|
75
|
+
export declare function estimateInputTokens(messages: readonly ChatMessage[]): number;
|
|
76
|
+
/**
|
|
77
|
+
* A backend (a proxy that drops them, say) can report a handful of input tokens for a request that holds thousands. Then the input is counted by the size
|
|
78
|
+
* of the request, so the cost and the cap are not understated, and the count is marked as estimated. A count that is plausible is left as reported.
|
|
79
|
+
*/
|
|
80
|
+
export declare function withEstimatedInput(usage: TokenUsage, messages: readonly ChatMessage[]): CountedUsage;
|
|
81
|
+
export declare function createSpendMeter(store: SettingsStore, pricing: {
|
|
82
|
+
backend?: string;
|
|
83
|
+
model?: string;
|
|
84
|
+
}): Promise<SpendMeter>;
|
|
85
|
+
/**
|
|
86
|
+
* Wraps a client so every call is counted, and no call starts once the cap is reached. Every model step in the article
|
|
87
|
+
* service fails closed on an error (nothing is marked checked), so hitting the cap degrades to "not checked", never to a wrong result.
|
|
88
|
+
*/
|
|
89
|
+
export declare function withSpendCap(inner: ILLMClient, meter: SpendMeter, getCap: () => number | undefined): ILLMClient;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChatMessage } from '@buildaharness/runtime';
|
|
2
|
+
/**
|
|
3
|
+
* What a model call was for, so the page can show where the writer's money went. The call type is read from the opening words of the
|
|
4
|
+
* call's system prompt (each step of the article service has its own); a call with none of these is the chat answering the writer.
|
|
5
|
+
* A test runs every step and fails if one is not recognised here, so a reworded prompt cannot silently move its cost to "chat".
|
|
6
|
+
*/
|
|
7
|
+
export interface CallType {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
/** The start of the step's system prompt. */
|
|
11
|
+
marker: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const CALL_TYPES: readonly CallType[];
|
|
14
|
+
export declare const CHAT_CALL: CallType;
|
|
15
|
+
export declare function classifyCall(messages: readonly ChatMessage[]): CallType;
|
|
16
|
+
export declare function callTypeLabel(id: string): string;
|