feat: add local agent MCP tools and optional source-grounded AI answers
Core CI / core (ubuntu-latest) (push) Failing after 3m6s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m41s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
Core CI / core (ubuntu-latest) (push) Failing after 3m6s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m41s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
This commit is contained in:
1 parent
70746ef428
commit
f5b03c6fb0
27 files changed
+2988
-13
No files matched your search
@@ -0,0 +1,86 @@
|
||||
/** Optional generation is separate from local search and never starts itself. */
|
||||
export interface AiConfiguration {
|
||||
enabled: boolean;
|
||||
base_url: string;
|
||||
model: string;
|
||||
api_key: string | null;
|
||||
}
|
||||
const bytes = (text: string) => new TextEncoder().encode(text).length;
|
||||
export function aiConfigurationProblem(config: AiConfiguration): string | null {
|
||||
if (!config.enabled) return "Optional AI is disabled.";
|
||||
let endpoint: URL;
|
||||
try {
|
||||
endpoint = new URL(config.base_url);
|
||||
} catch {
|
||||
return "Enter your OpenAI-compatible endpoint URL.";
|
||||
}
|
||||
if (
|
||||
!["http:", "https:"].includes(endpoint.protocol) ||
|
||||
endpoint.username ||
|
||||
endpoint.password ||
|
||||
endpoint.search ||
|
||||
endpoint.hash ||
|
||||
bytes(config.base_url) > 2048
|
||||
)
|
||||
return "Use an HTTP(S) endpoint without URL credentials, query or fragment.";
|
||||
if (
|
||||
!config.model.trim() ||
|
||||
bytes(config.model) > 200 ||
|
||||
/[\u0000-\u001f\u007f]/u.test(config.model)
|
||||
)
|
||||
return "Enter a model name (up to 200 bytes).";
|
||||
if (
|
||||
config.api_key &&
|
||||
(bytes(config.api_key) > 2048 ||
|
||||
/[\u0000-\u001f\u007f]/u.test(config.api_key))
|
||||
)
|
||||
return "The session API key is invalid or too long.";
|
||||
return null;
|
||||
}
|
||||
export function aiQuestionProblem(question: string): string | null {
|
||||
return !question.trim()
|
||||
? "Enter a question about this passage."
|
||||
: bytes(question) > 2048
|
||||
? "The question exceeds 2048 bytes."
|
||||
: null;
|
||||
}
|
||||
export function aiEndpointLabel(baseURL: string) {
|
||||
try {
|
||||
return new URL(baseURL).origin;
|
||||
} catch {
|
||||
return "your configured endpoint";
|
||||
}
|
||||
}
|
||||
export function aiSourceEligible(
|
||||
source?: { kind: string; number: number },
|
||||
media = false,
|
||||
) {
|
||||
return (
|
||||
!media &&
|
||||
Boolean(
|
||||
source &&
|
||||
["page", "paragraph", "line"].includes(source.kind) &&
|
||||
Number.isInteger(source.number) &&
|
||||
source.number > 0,
|
||||
)
|
||||
);
|
||||
}
|
||||
export class AiRevision {
|
||||
private revision = 0;
|
||||
private selection = "";
|
||||
invalidate() {
|
||||
this.revision++;
|
||||
}
|
||||
select(selection: string) {
|
||||
if (selection !== this.selection) {
|
||||
this.selection = selection;
|
||||
this.invalidate();
|
||||
}
|
||||
}
|
||||
current() {
|
||||
return this.revision;
|
||||
}
|
||||
accepts(revision: number) {
|
||||
return revision === this.revision;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user