agency-lang 0.14.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.
- package/dist/lib/cli/local.d.ts +14 -3
- package/dist/lib/cli/local.js +62 -14
- package/dist/lib/cli/localFlag.d.ts +12 -0
- package/dist/lib/cli/localFlag.js +16 -0
- package/dist/lib/runtime/interrupts.js +2 -0
- package/dist/lib/runtime/localProvider.d.ts +76 -0
- package/dist/lib/runtime/localProvider.js +127 -0
- package/dist/lib/runtime/node.js +2 -0
- package/dist/lib/statelogClient.d.ts +17 -0
- package/dist/lib/statelogClient.js +17 -0
- package/dist/lib/stdlib/localModelManifest.d.ts +14 -0
- package/dist/lib/stdlib/localModelManifest.js +56 -0
- package/dist/lib/stdlib/localModels.d.ts +26 -24
- package/dist/lib/stdlib/localModels.js +81 -112
- package/dist/lib/stdlib/mcpBridge.mjs +3 -2
- package/dist/lib/stdlib/mcpResolver.d.ts +1 -1
- package/dist/lib/stdlib/mcpResolver.js +2 -2
- package/dist/scripts/agency.js +26 -8
- package/package.json +2 -2
- package/stdlib/agents/data.agency +1 -1
- package/stdlib/agents/data.js +1 -1
- package/stdlib/capabilities.agency +5 -4
- package/stdlib/capabilities.js +2 -2
- package/stdlib/data/connector.agency +66 -0
- package/stdlib/data/connector.js +976 -0
- package/stdlib/data/finance/dbnomics.agency +5 -4
- package/stdlib/data/finance/dbnomics.js +17 -20
- package/stdlib/data/finance/edgar.agency +8 -7
- package/stdlib/data/finance/edgar.js +58 -73
- package/stdlib/data/finance/fred.agency +6 -5
- package/stdlib/data/finance/fred.js +31 -37
- package/stdlib/data/{finance → news}/gdelt.agency +5 -5
- package/stdlib/data/{finance → news}/gdelt.js +48 -63
- package/stdlib/data/people/littlesis.agency +6 -20
- package/stdlib/data/people/littlesis.js +75 -246
- package/stdlib/data/social/bluesky.agency +291 -0
- package/stdlib/data/social/bluesky.js +2974 -0
- package/stdlib/data/tech/hackernews.agency +12 -42
- package/stdlib/data/tech/hackernews.js +115 -591
- package/stdlib/data/tech/yc.agency +6 -20
- package/stdlib/data/tech/yc.js +102 -320
- package/stdlib/data/usaspending.agency +3 -17
- package/stdlib/data/usaspending.js +41 -205
- package/stdlib/data/wikidata.agency +4 -18
- package/stdlib/data/wikidata.js +66 -245
- package/stdlib/docs/cli/local.md +14 -11
- package/stdlib/docs/guide/using-local-models.md +188 -0
- package/stdlib/docs/stdlib/capabilities.md +17 -15
- package/stdlib/docs/stdlib/data/connector.md +139 -0
- package/stdlib/docs/stdlib/data/finance/dbnomics.md +2 -2
- package/stdlib/docs/stdlib/data/finance/edgar.md +3 -3
- package/stdlib/docs/stdlib/data/finance/fred.md +5 -5
- package/stdlib/docs/stdlib/data/{finance → news}/gdelt.md +4 -4
- package/stdlib/docs/stdlib/data/people/littlesis.md +5 -5
- package/stdlib/docs/stdlib/data/social/bluesky.md +206 -0
- package/stdlib/docs/stdlib/data/tech/hackernews.md +4 -4
- package/stdlib/docs/stdlib/data/tech/yc.md +5 -5
- package/stdlib/docs/stdlib/data/usaspending.md +2 -2
- package/stdlib/docs/stdlib/data/wikidata.md +3 -3
- package/dist/lib/stdlib/providers/llama-cpp.mjs +0 -79
- package/dist/lib/stdlib/providers/llamaModelConfig.d.ts +0 -7
- package/dist/lib/stdlib/providers/llamaModelConfig.js +0 -7
package/dist/lib/cli/local.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { type RefreshResult } from "../stdlib/localModels.js";
|
|
2
|
-
export declare function aliasList(file?: string):
|
|
1
|
+
import { type ModelNameEntry, type RefreshResult } from "../stdlib/localModels.js";
|
|
2
|
+
export declare function aliasList(file?: string): ModelNameEntry[];
|
|
3
3
|
export declare function aliasAdd(name: string, uri: string, file?: string): string;
|
|
4
4
|
export declare function aliasRemove(name: string, file?: string): string;
|
|
5
|
+
/** Deliberately ungated: browsing the catalog needs no provider package
|
|
6
|
+
* (only download/remove do), and the pre-install experience — see what is
|
|
7
|
+
* available, then get told what to install — is the point. */
|
|
5
8
|
export declare function runList(): void;
|
|
6
|
-
export declare
|
|
9
|
+
export declare const CUSTOM_CHOICE = "__custom__";
|
|
10
|
+
/** Picker rows for the no-argument `agency local download`. Metadata-less
|
|
11
|
+
* aliases get a bare name; the trailing choice lets the user type an hf: URI
|
|
12
|
+
* or .gguf path. */
|
|
13
|
+
export declare function downloadChoices(entries: ModelNameEntry[]): {
|
|
14
|
+
title: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[];
|
|
17
|
+
export declare function runDownload(value?: string): Promise<void>;
|
|
7
18
|
export declare function runRemove(name: string): void;
|
|
8
19
|
export declare function runResolve(value: string): void;
|
|
9
20
|
export declare function runAliasList(): void;
|
package/dist/lib/cli/local.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import { _resolveModelName, _downloadModel, _listDownloadedModels, _listModelNames, _modelsCacheDir, _aliasModel, _unaliasModel, _removeModel, hasLocalModelSupport, formatGB, formatModelCatalog, formatLocalList, _refreshCatalog, } from "../stdlib/localModels.js";
|
|
3
|
+
import { readDownloadManifest } from "../stdlib/localModelManifest.js";
|
|
2
4
|
import { ttyColor } from "../utils/termcolors.js";
|
|
3
5
|
/** Install-gate for I/O commands. Honors the AGENCY_LLAMA_PROVIDER_MODULE
|
|
4
6
|
* override the same way `requireSupport()` in `localModels.ts` does — a
|
|
@@ -31,26 +33,72 @@ export function aliasRemove(name, file) {
|
|
|
31
33
|
}
|
|
32
34
|
return inspected;
|
|
33
35
|
}
|
|
36
|
+
/** Deliberately ungated: browsing the catalog needs no provider package
|
|
37
|
+
* (only download/remove do), and the pre-install experience — see what is
|
|
38
|
+
* available, then get told what to install — is the point. */
|
|
34
39
|
export function runList() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const dir = _modelsCacheDir();
|
|
41
|
+
console.log(formatLocalList({
|
|
42
|
+
dir,
|
|
43
|
+
entries: _listModelNames(),
|
|
44
|
+
manifest: readDownloadManifest(dir),
|
|
45
|
+
files: _listDownloadedModels(),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
export const CUSTOM_CHOICE = "__custom__";
|
|
49
|
+
/** Picker rows for the no-argument `agency local download`. Metadata-less
|
|
50
|
+
* aliases get a bare name; the trailing choice lets the user type an hf: URI
|
|
51
|
+
* or .gguf path. */
|
|
52
|
+
export function downloadChoices(entries) {
|
|
53
|
+
const rows = entries.map((e) => ({
|
|
54
|
+
title: e.params !== undefined && e.sizeBytes !== undefined
|
|
55
|
+
? `${e.name} (${e.params}, ${formatGB(e.sizeBytes)})`
|
|
56
|
+
: e.name,
|
|
57
|
+
value: e.name,
|
|
58
|
+
}));
|
|
59
|
+
return [...rows, { title: "custom (hf: URI or .gguf path)…", value: CUSTOM_CHOICE }];
|
|
46
60
|
}
|
|
47
61
|
export async function runDownload(value) {
|
|
48
62
|
gate();
|
|
63
|
+
let picked = value;
|
|
64
|
+
if (picked === undefined) {
|
|
65
|
+
// Prompting needs BOTH ends of the terminal: a TTY stdout to draw on and
|
|
66
|
+
// a TTY stdin to read from (`agency local download < /dev/null` from a
|
|
67
|
+
// terminal has a TTY stdout but nothing to read).
|
|
68
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
69
|
+
// A script that reaches this point asked for a download and did not
|
|
70
|
+
// get one — print what is available and fail.
|
|
71
|
+
console.log(formatModelCatalog());
|
|
72
|
+
console.error("Pass a model: agency local download <name>");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const answer = await prompts({
|
|
76
|
+
type: "select",
|
|
77
|
+
name: "model",
|
|
78
|
+
message: "Which model do you want to download?",
|
|
79
|
+
choices: downloadChoices(_listModelNames()),
|
|
80
|
+
});
|
|
81
|
+
// Cancellation can surface as a missing key or as null — treat both as
|
|
82
|
+
// "exit 0, nothing downloaded".
|
|
83
|
+
if (answer.model == null)
|
|
84
|
+
return;
|
|
85
|
+
picked = answer.model;
|
|
86
|
+
if (picked === CUSTOM_CHOICE) {
|
|
87
|
+
const custom = await prompts({
|
|
88
|
+
type: "text",
|
|
89
|
+
name: "value",
|
|
90
|
+
message: "hf: URI or .gguf path:",
|
|
91
|
+
});
|
|
92
|
+
if (custom.value == null || custom.value === "")
|
|
93
|
+
return;
|
|
94
|
+
picked = custom.value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
49
97
|
// Show the source it resolved to (the hf: URI for a name/alias) and the
|
|
50
98
|
// local path it landed at. For a .gguf-path input the two are the same, so
|
|
51
99
|
// the source line is skipped.
|
|
52
|
-
const source = _resolveModelName(
|
|
53
|
-
const modelPath = await _downloadModel(
|
|
100
|
+
const source = _resolveModelName(picked);
|
|
101
|
+
const modelPath = await _downloadModel(picked);
|
|
54
102
|
if (source !== modelPath) {
|
|
55
103
|
console.log(`source: ${source}`);
|
|
56
104
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolvedModelFlag } from "../config.js";
|
|
2
|
+
/** Turn `agency run --local <value>` into the shared model-flag shape:
|
|
3
|
+
* resolve the name (curated / alias / hf: URI / .gguf path), download and
|
|
4
|
+
* verify if needed (progress prints here, in the parent, before the program
|
|
5
|
+
* starts), and pin the llama-cpp provider. Errors (package missing, unknown
|
|
6
|
+
* name, failed download) carry user-ready messages from localModels.
|
|
7
|
+
*
|
|
8
|
+
* The path is absolutized before it is baked into config: LlamaCPP rejects a
|
|
9
|
+
* bare separator-less filename (ambiguous with a model name), which is what
|
|
10
|
+
* a user-supplied `--local model.gguf` would otherwise arrive as, and an
|
|
11
|
+
* absolute path also keeps the child process independent of cwd drift. */
|
|
12
|
+
export declare function resolveLocalRunFlag(value: string): Promise<ResolvedModelFlag>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { _registerLocalModel } from "../stdlib/localModels.js";
|
|
3
|
+
/** Turn `agency run --local <value>` into the shared model-flag shape:
|
|
4
|
+
* resolve the name (curated / alias / hf: URI / .gguf path), download and
|
|
5
|
+
* verify if needed (progress prints here, in the parent, before the program
|
|
6
|
+
* starts), and pin the llama-cpp provider. Errors (package missing, unknown
|
|
7
|
+
* name, failed download) carry user-ready messages from localModels.
|
|
8
|
+
*
|
|
9
|
+
* The path is absolutized before it is baked into config: LlamaCPP rejects a
|
|
10
|
+
* bare separator-less filename (ambiguous with a model name), which is what
|
|
11
|
+
* a user-supplied `--local model.gguf` would otherwise arrive as, and an
|
|
12
|
+
* absolute path also keeps the child process independent of cwd drift. */
|
|
13
|
+
export async function resolveLocalRunFlag(value) {
|
|
14
|
+
const modelPath = await _registerLocalModel(value);
|
|
15
|
+
return { model: path.resolve(modelPath), explicitProvider: "llama-cpp" };
|
|
16
|
+
}
|
|
@@ -11,6 +11,7 @@ import { isAborted } from "./abortedResult.js";
|
|
|
11
11
|
import { mergeFor, mergeForIpc } from "./effectMerge.js";
|
|
12
12
|
import { applyOverrides } from "./rewind.js";
|
|
13
13
|
import { loadProviderModules } from "./providerModules.js";
|
|
14
|
+
import { ensureConfiguredLocalProvider } from "./localProvider.js";
|
|
14
15
|
import { installRunPolicyHandler } from "./runPolicyHandler.js";
|
|
15
16
|
import { unwrapServedInvocationOutcome, } from "./invocationUsage.js";
|
|
16
17
|
import { finishServedInvocation } from "./servedInvocationLifecycle.js";
|
|
@@ -613,6 +614,7 @@ async function respondToInterruptsCore(args) {
|
|
|
613
614
|
// is process-global, not part of serialized checkpoint state), so re-register
|
|
614
615
|
// before resuming. Idempotent in-process via loadProviderModules' guard.
|
|
615
616
|
await loadProviderModules(execCtx);
|
|
617
|
+
await ensureConfiguredLocalProvider(execCtx);
|
|
616
618
|
// This is the first restore on this execCtx — record it as such.
|
|
617
619
|
execCtx._restoreCount++;
|
|
618
620
|
execCtx.statelogClient.checkpointRestored({
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type LlamaCppModule } from "smoltalk";
|
|
2
|
+
/** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
|
|
3
|
+
* order. Each entry is the directory printed by `<tool> root -g` (which is
|
|
4
|
+
* itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
|
|
5
|
+
* Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
|
|
6
|
+
* (tool not installed, exit non-zero, dir missing) are silently skipped. */
|
|
7
|
+
export declare function globalNodeModulesRoots(): string[];
|
|
8
|
+
/** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
|
|
9
|
+
* roots. Each `root` is itself a `node_modules` directory (the convention
|
|
10
|
+
* `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
|
|
11
|
+
* `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
|
|
12
|
+
* createRequire base must live in the root's PARENT directory — from
|
|
13
|
+
* `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
|
|
14
|
+
* Exported for unit-testing with a controllable list of roots. */
|
|
15
|
+
export declare function resolveSmoltalkLlamaCppFromRoots(roots: string[]): string | null;
|
|
16
|
+
/** Local require paths first, then the global npm/pnpm roots. Used by the
|
|
17
|
+
* install gate (`hasLocalModelSupport`), which only cares whether the
|
|
18
|
+
* package is reachable at all. */
|
|
19
|
+
export declare function resolveSmoltalkLlamaCppEntry(): string | null;
|
|
20
|
+
/** The chooseEntryPath decision: what to hand smoltalk, and how the package
|
|
21
|
+
* was found (for the `localModelLoaded` statelog event). `entryPath` is
|
|
22
|
+
* undefined for a bare import — sources "local" and "none". */
|
|
23
|
+
export type LlamaCppEntryChoice = {
|
|
24
|
+
entryPath: string | undefined;
|
|
25
|
+
source: "override" | "local" | "global" | "none";
|
|
26
|
+
};
|
|
27
|
+
/** What to hand smoltalk's loadLlamaCpp: an explicit entry path, or
|
|
28
|
+
* undefined for a bare import. Pure — the probes come in as values/thunks so
|
|
29
|
+
* tests pin each branch. Precedence:
|
|
30
|
+
* 1. AGENCY_LLAMA_PROVIDER_MODULE (the test/advanced escape hatch; an ENTRY
|
|
31
|
+
* PATH to a plugin-shaped module — a LlamaCPP class + resolveModel),
|
|
32
|
+
* absolutized against cwd.
|
|
33
|
+
* 2. Locally resolvable → undefined: smoltalk imports the bare specifier
|
|
34
|
+
* itself (under pnpm its peer instancing picks the right copy).
|
|
35
|
+
* 3. A global-roots hit → that path (smoltalk cannot see global installs).
|
|
36
|
+
* 4. Nothing → undefined: smoltalk's import fails with its install hint. */
|
|
37
|
+
export declare function chooseEntryPath(args: {
|
|
38
|
+
override: string | undefined;
|
|
39
|
+
cwd: string;
|
|
40
|
+
localEntry: string | null;
|
|
41
|
+
globalEntry: () => string | null;
|
|
42
|
+
}): LlamaCppEntryChoice;
|
|
43
|
+
/** The one field of StatelogClient this module needs. Structural so the
|
|
44
|
+
* runtime bootstrap can pass its client without this file importing the
|
|
45
|
+
* statelog module. */
|
|
46
|
+
type LocalModelEventSink = {
|
|
47
|
+
localModelLoaded(args: {
|
|
48
|
+
model?: string;
|
|
49
|
+
entryPath?: string;
|
|
50
|
+
entrySource: string;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
};
|
|
53
|
+
/** Bootstrap hook: when the baked/overridden config routes calls to the
|
|
54
|
+
* llama-cpp provider, load it eagerly with the probe-assisted entry path.
|
|
55
|
+
* smoltalk auto-loads on text() for resolvable installs; this covers the
|
|
56
|
+
* global-CLI + global-plugin layout its bare import cannot see. Emits a
|
|
57
|
+
* `localModelLoaded` statelog event naming the pinned model and where the
|
|
58
|
+
* provider package came from. */
|
|
59
|
+
export declare function ensureConfiguredLocalProvider(execCtx: {
|
|
60
|
+
smoltalkDefaults?: {
|
|
61
|
+
provider?: string;
|
|
62
|
+
model?: string;
|
|
63
|
+
};
|
|
64
|
+
statelogClient?: LocalModelEventSink;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
/** Like loadLocalProvider, also returning HOW the package was found, for
|
|
67
|
+
* callers that emit the `localModelLoaded` event. */
|
|
68
|
+
export declare function loadLocalProviderDetailed(): Promise<{
|
|
69
|
+
module: LlamaCppModule;
|
|
70
|
+
choice: LlamaCppEntryChoice;
|
|
71
|
+
}>;
|
|
72
|
+
/** Load smoltalk's optional llama-cpp provider, resolving the package for
|
|
73
|
+
* layouts smoltalk cannot see on its own (see chooseEntryPath). Caching,
|
|
74
|
+
* idempotency, and registration live in smoltalk's loader, not here. */
|
|
75
|
+
export declare function loadLocalProvider(): Promise<LlamaCppModule>;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { loadLlamaCpp } from "smoltalk";
|
|
6
|
+
// Cached so we don't shell out repeatedly per process.
|
|
7
|
+
let cachedGlobalRoots = null;
|
|
8
|
+
/** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
|
|
9
|
+
* order. Each entry is the directory printed by `<tool> root -g` (which is
|
|
10
|
+
* itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
|
|
11
|
+
* Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
|
|
12
|
+
* (tool not installed, exit non-zero, dir missing) are silently skipped. */
|
|
13
|
+
export function globalNodeModulesRoots() {
|
|
14
|
+
if (cachedGlobalRoots !== null) {
|
|
15
|
+
return cachedGlobalRoots;
|
|
16
|
+
}
|
|
17
|
+
const roots = [];
|
|
18
|
+
for (const cmd of ["npm", "pnpm"]) {
|
|
19
|
+
try {
|
|
20
|
+
const out = execFileSync(cmd, ["root", "-g"], {
|
|
21
|
+
encoding: "utf-8",
|
|
22
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23
|
+
}).trim();
|
|
24
|
+
if (out && fs.existsSync(out) && !roots.includes(out)) {
|
|
25
|
+
roots.push(out);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* tool not installed or failed — skip */
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
cachedGlobalRoots = roots;
|
|
33
|
+
return roots;
|
|
34
|
+
}
|
|
35
|
+
/** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
|
|
36
|
+
* roots. Each `root` is itself a `node_modules` directory (the convention
|
|
37
|
+
* `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
|
|
38
|
+
* `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
|
|
39
|
+
* createRequire base must live in the root's PARENT directory — from
|
|
40
|
+
* `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
|
|
41
|
+
* Exported for unit-testing with a controllable list of roots. */
|
|
42
|
+
export function resolveSmoltalkLlamaCppFromRoots(roots) {
|
|
43
|
+
for (const root of roots) {
|
|
44
|
+
try {
|
|
45
|
+
const req = createRequire(path.join(root, "..", "_resolver.js"));
|
|
46
|
+
return req.resolve("smoltalk-llama-cpp");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* not in this root — try the next */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/** The package entry resolvable from agency's own require paths (in-workspace
|
|
55
|
+
* and user-project installs). */
|
|
56
|
+
function localEntry() {
|
|
57
|
+
try {
|
|
58
|
+
return createRequire(import.meta.url).resolve("smoltalk-llama-cpp");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Local require paths first, then the global npm/pnpm roots. Used by the
|
|
65
|
+
* install gate (`hasLocalModelSupport`), which only cares whether the
|
|
66
|
+
* package is reachable at all. */
|
|
67
|
+
export function resolveSmoltalkLlamaCppEntry() {
|
|
68
|
+
return localEntry() ?? resolveSmoltalkLlamaCppFromRoots(globalNodeModulesRoots());
|
|
69
|
+
}
|
|
70
|
+
/** What to hand smoltalk's loadLlamaCpp: an explicit entry path, or
|
|
71
|
+
* undefined for a bare import. Pure — the probes come in as values/thunks so
|
|
72
|
+
* tests pin each branch. Precedence:
|
|
73
|
+
* 1. AGENCY_LLAMA_PROVIDER_MODULE (the test/advanced escape hatch; an ENTRY
|
|
74
|
+
* PATH to a plugin-shaped module — a LlamaCPP class + resolveModel),
|
|
75
|
+
* absolutized against cwd.
|
|
76
|
+
* 2. Locally resolvable → undefined: smoltalk imports the bare specifier
|
|
77
|
+
* itself (under pnpm its peer instancing picks the right copy).
|
|
78
|
+
* 3. A global-roots hit → that path (smoltalk cannot see global installs).
|
|
79
|
+
* 4. Nothing → undefined: smoltalk's import fails with its install hint. */
|
|
80
|
+
export function chooseEntryPath(args) {
|
|
81
|
+
if (args.override !== undefined && args.override !== "") {
|
|
82
|
+
const abs = path.isAbsolute(args.override)
|
|
83
|
+
? args.override
|
|
84
|
+
: path.resolve(args.cwd, args.override);
|
|
85
|
+
return { entryPath: abs, source: "override" };
|
|
86
|
+
}
|
|
87
|
+
if (args.localEntry !== null)
|
|
88
|
+
return { entryPath: undefined, source: "local" };
|
|
89
|
+
const globalPath = args.globalEntry();
|
|
90
|
+
if (globalPath !== null)
|
|
91
|
+
return { entryPath: globalPath, source: "global" };
|
|
92
|
+
return { entryPath: undefined, source: "none" };
|
|
93
|
+
}
|
|
94
|
+
/** Bootstrap hook: when the baked/overridden config routes calls to the
|
|
95
|
+
* llama-cpp provider, load it eagerly with the probe-assisted entry path.
|
|
96
|
+
* smoltalk auto-loads on text() for resolvable installs; this covers the
|
|
97
|
+
* global-CLI + global-plugin layout its bare import cannot see. Emits a
|
|
98
|
+
* `localModelLoaded` statelog event naming the pinned model and where the
|
|
99
|
+
* provider package came from. */
|
|
100
|
+
export async function ensureConfiguredLocalProvider(execCtx) {
|
|
101
|
+
if (execCtx.smoltalkDefaults?.provider !== "llama-cpp")
|
|
102
|
+
return;
|
|
103
|
+
const { choice } = await loadLocalProviderDetailed();
|
|
104
|
+
void execCtx.statelogClient?.localModelLoaded({
|
|
105
|
+
model: execCtx.smoltalkDefaults?.model,
|
|
106
|
+
entryPath: choice.entryPath,
|
|
107
|
+
entrySource: choice.source,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Like loadLocalProvider, also returning HOW the package was found, for
|
|
111
|
+
* callers that emit the `localModelLoaded` event. */
|
|
112
|
+
export async function loadLocalProviderDetailed() {
|
|
113
|
+
const choice = chooseEntryPath({
|
|
114
|
+
override: process.env.AGENCY_LLAMA_PROVIDER_MODULE,
|
|
115
|
+
cwd: process.cwd(),
|
|
116
|
+
localEntry: localEntry(),
|
|
117
|
+
globalEntry: () => resolveSmoltalkLlamaCppFromRoots(globalNodeModulesRoots()),
|
|
118
|
+
});
|
|
119
|
+
const module = await loadLlamaCpp(choice.entryPath === undefined ? undefined : { entryPath: choice.entryPath });
|
|
120
|
+
return { module, choice };
|
|
121
|
+
}
|
|
122
|
+
/** Load smoltalk's optional llama-cpp provider, resolving the package for
|
|
123
|
+
* layouts smoltalk cannot see on its own (see chooseEntryPath). Caching,
|
|
124
|
+
* idempotency, and registration live in smoltalk's loader, not here. */
|
|
125
|
+
export async function loadLocalProvider() {
|
|
126
|
+
return (await loadLocalProviderDetailed()).module;
|
|
127
|
+
}
|
package/dist/lib/runtime/node.js
CHANGED
|
@@ -6,6 +6,7 @@ import { AgencyCancelledError, CheckpointError, RestoreSignal, } from "./errors.
|
|
|
6
6
|
import { ThreadStore } from "./state/threadStore.js";
|
|
7
7
|
import { __initAllRegistered, __initAllRegisteredCallbacks } from "./crossModuleInitRegistry.js";
|
|
8
8
|
import { loadProviderModules } from "./providerModules.js";
|
|
9
|
+
import { ensureConfiguredLocalProvider } from "./localProvider.js";
|
|
9
10
|
import { resolveTraceFilePath } from "./trace/traceWriter.js";
|
|
10
11
|
import { getSubprocessRunInfo } from "./subprocessRunInfo.js";
|
|
11
12
|
import { resolveInvocation } from "./invocationOptions.js";
|
|
@@ -146,6 +147,7 @@ async function initFreshExecCtx(execCtx, opts) {
|
|
|
146
147
|
// Process-global + idempotent (see loadProviderModules), so it is safe and
|
|
147
148
|
// cheap to call on every fresh run.
|
|
148
149
|
await loadProviderModules(execCtx);
|
|
150
|
+
await ensureConfiguredLocalProvider(execCtx);
|
|
149
151
|
await runInBootstrapFrame(execCtx, () => __initAllRegistered(execCtx));
|
|
150
152
|
if (initializeGlobals) {
|
|
151
153
|
await runInBootstrapFrame(execCtx, () => initializeGlobals(execCtx));
|
|
@@ -266,6 +266,23 @@ export declare class StatelogClient {
|
|
|
266
266
|
timeTaken?: number;
|
|
267
267
|
cost?: TokenCost;
|
|
268
268
|
}): Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* Emit a `localModelLoaded` leaf event: which local model the run is
|
|
271
|
+
* pinned to and where the llama-cpp provider package was resolved from.
|
|
272
|
+
* `entrySource` is the chooseEntryPath decision — "override"
|
|
273
|
+
* (AGENCY_LLAMA_PROVIDER_MODULE), "local" (bare import from the local
|
|
274
|
+
* require paths), or "global" (a global npm/pnpm install root). Emitted
|
|
275
|
+
* once per process at bootstrap when the baked config names the llama-cpp
|
|
276
|
+
* provider, and when agency code registers a local model explicitly.
|
|
277
|
+
*/
|
|
278
|
+
localModelLoaded({ model, entryPath, entrySource, }: {
|
|
279
|
+
/** The .gguf path (or model name) calls are pinned to, when known. */
|
|
280
|
+
model?: string;
|
|
281
|
+
/** Resolved entry file of the provider package, when not a bare import. */
|
|
282
|
+
entryPath?: string;
|
|
283
|
+
/** How the provider package was found (see chooseEntryPath). */
|
|
284
|
+
entrySource: string;
|
|
285
|
+
}): Promise<void>;
|
|
269
286
|
/**
|
|
270
287
|
* Memory umbrella-span marker events.
|
|
271
288
|
*
|
|
@@ -433,6 +433,23 @@ export class StatelogClient {
|
|
|
433
433
|
cost,
|
|
434
434
|
});
|
|
435
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Emit a `localModelLoaded` leaf event: which local model the run is
|
|
438
|
+
* pinned to and where the llama-cpp provider package was resolved from.
|
|
439
|
+
* `entrySource` is the chooseEntryPath decision — "override"
|
|
440
|
+
* (AGENCY_LLAMA_PROVIDER_MODULE), "local" (bare import from the local
|
|
441
|
+
* require paths), or "global" (a global npm/pnpm install root). Emitted
|
|
442
|
+
* once per process at bootstrap when the baked config names the llama-cpp
|
|
443
|
+
* provider, and when agency code registers a local model explicitly.
|
|
444
|
+
*/
|
|
445
|
+
async localModelLoaded({ model, entryPath, entrySource, }) {
|
|
446
|
+
await this.post({
|
|
447
|
+
type: "localModelLoaded",
|
|
448
|
+
model,
|
|
449
|
+
entryPath,
|
|
450
|
+
entrySource,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
436
453
|
/**
|
|
437
454
|
* Memory umbrella-span marker events.
|
|
438
455
|
*
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** downloads.json in the models cache dir: resolved model URI → the .gguf
|
|
2
|
+
* basename node-llama-cpp stored it under. Written on successful (verified)
|
|
3
|
+
* download; read only by the `agency local list` view. Display metadata:
|
|
4
|
+
* resolution, downloading, and verification never consult it, so a missing
|
|
5
|
+
* or corrupt manifest can mislabel the list and nothing else — and for the
|
|
6
|
+
* same reason `recordDownload` NEVER throws (a bookkeeping failure must not
|
|
7
|
+
* turn a successful download into a failed command). Writes go through a
|
|
8
|
+
* uniquely-named sibling temp file + rename: an interrupted write keeps the
|
|
9
|
+
* previous valid manifest, and two concurrent downloaders cannot collide on
|
|
10
|
+
* the temp file — they race whole-file on the rename (last writer wins),
|
|
11
|
+
* accepted for display metadata. */
|
|
12
|
+
export declare const MANIFEST_FILE = "downloads.json";
|
|
13
|
+
export declare function readDownloadManifest(dir: string): Record<string, string>;
|
|
14
|
+
export declare function recordDownload(dir: string, uri: string, file: string): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
/** downloads.json in the models cache dir: resolved model URI → the .gguf
|
|
5
|
+
* basename node-llama-cpp stored it under. Written on successful (verified)
|
|
6
|
+
* download; read only by the `agency local list` view. Display metadata:
|
|
7
|
+
* resolution, downloading, and verification never consult it, so a missing
|
|
8
|
+
* or corrupt manifest can mislabel the list and nothing else — and for the
|
|
9
|
+
* same reason `recordDownload` NEVER throws (a bookkeeping failure must not
|
|
10
|
+
* turn a successful download into a failed command). Writes go through a
|
|
11
|
+
* uniquely-named sibling temp file + rename: an interrupted write keeps the
|
|
12
|
+
* previous valid manifest, and two concurrent downloaders cannot collide on
|
|
13
|
+
* the temp file — they race whole-file on the rename (last writer wins),
|
|
14
|
+
* accepted for display metadata. */
|
|
15
|
+
export const MANIFEST_FILE = "downloads.json";
|
|
16
|
+
export function readDownloadManifest(dir) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, MANIFEST_FILE), "utf-8"));
|
|
19
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
// Null-prototype: on-disk JSON is untrusted, and keys like "__proto__" /
|
|
23
|
+
// "toString" must behave as plain data (same convention as smoltalk's
|
|
24
|
+
// provider registry).
|
|
25
|
+
const out = Object.create(null);
|
|
26
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
27
|
+
if (typeof v === "string")
|
|
28
|
+
out[k] = v;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function recordDownload(dir, uri, file) {
|
|
37
|
+
// Unique per writer: a fixed name would make two concurrent downloads race
|
|
38
|
+
// on the SAME temp file — the loser's rename throws ENOENT after its model
|
|
39
|
+
// downloaded fine.
|
|
40
|
+
const tmp = path.join(dir, `${MANIFEST_FILE}.${process.pid}-${crypto.randomBytes(4).toString("hex")}.tmp`);
|
|
41
|
+
try {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
const next = { ...readDownloadManifest(dir), [uri]: file };
|
|
44
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n");
|
|
45
|
+
fs.renameSync(tmp, path.join(dir, MANIFEST_FILE));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
try {
|
|
49
|
+
fs.unlinkSync(tmp);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* never written, or already renamed */
|
|
53
|
+
}
|
|
54
|
+
console.warn(`Could not record the download in ${MANIFEST_FILE} (the list view may not mark this model):`, err.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -35,6 +35,10 @@ export declare const CURATED_LOCAL_MODELS: Record<string, ModelInfo>;
|
|
|
35
35
|
* `agency local download` pre-populates what `agency agent --local-model`
|
|
36
36
|
* reuses). */
|
|
37
37
|
export declare function defaultCacheDir(): string;
|
|
38
|
+
/** The resolved models cache dir (AGENCY_MODELS_DIR env → agency.json
|
|
39
|
+
* client.modelsDir → ~/.agency-agent/models). Exported so `agency local
|
|
40
|
+
* list` can name where downloads land. */
|
|
41
|
+
export declare function _modelsCacheDir(cacheDir?: string): string;
|
|
38
42
|
/** The agency.json that owns aliases: nearest `agency.json` walking up from
|
|
39
43
|
* `startDir` (cwd by default); falls back to `~/agency.json` when none is
|
|
40
44
|
* found. Exported so the CLI can echo it on every write. */
|
|
@@ -139,29 +143,6 @@ export declare function _refreshCatalog(opts?: {
|
|
|
139
143
|
fetcher?: (url: string) => Promise<string>;
|
|
140
144
|
file?: string;
|
|
141
145
|
}): Promise<RefreshResult>;
|
|
142
|
-
/** Discover global `node_modules` roots reported by `npm` and `pnpm`, in that
|
|
143
|
-
* order. Each entry is the directory printed by `<tool> root -g` (which is
|
|
144
|
-
* itself a `node_modules` dir, e.g. `/opt/homebrew/lib/node_modules` for
|
|
145
|
-
* Homebrew npm, `~/Library/pnpm/global/5/node_modules` for pnpm). Failures
|
|
146
|
-
* (tool not installed, exit non-zero, dir missing) are silently skipped. */
|
|
147
|
-
export declare function globalNodeModulesRoots(): string[];
|
|
148
|
-
/** Try to resolve `smoltalk-llama-cpp` from the given global `node_modules`
|
|
149
|
-
* roots. Each `root` is itself a `node_modules` directory (the convention
|
|
150
|
-
* `npm root -g` / `pnpm root -g` uses). Node's resolver looks for
|
|
151
|
-
* `<parent>/node_modules/<pkg>` for each parent dir it walks up, so the
|
|
152
|
-
* createRequire base must live in the root's PARENT directory — from
|
|
153
|
-
* `<root>/../_resolver.js` it correctly finds `<root>/smoltalk-llama-cpp/...`.
|
|
154
|
-
* Exported for unit-testing with a controllable list of roots. */
|
|
155
|
-
export declare function resolveSmoltalkLlamaCppFromRoots(roots: string[]): string | null;
|
|
156
|
-
/** Resolve `smoltalk-llama-cpp` to the absolute path of its main entry,
|
|
157
|
-
* searching:
|
|
158
|
-
* 1. The local `require` paths walking up from this file (covers in-workspace
|
|
159
|
-
* `pnpm add` and a user-project install).
|
|
160
|
-
* 2. Each global `node_modules` root reported by `npm root -g` / `pnpm root -g`
|
|
161
|
-
* (covers `npm i -g` and `pnpm add -g` — the documented install methods).
|
|
162
|
-
*
|
|
163
|
-
* Returns `null` if the package isn't reachable from any of those. */
|
|
164
|
-
export declare function resolveSmoltalkLlamaCppEntry(): string | null;
|
|
165
146
|
/** True if smoltalk-llama-cpp is reachable from the local require paths OR
|
|
166
147
|
* from a global node_modules root (npm or pnpm). */
|
|
167
148
|
export declare function _localModelsSupported(): boolean;
|
|
@@ -172,7 +153,11 @@ export declare function _localModelsSupported(): boolean;
|
|
|
172
153
|
* works in the override scenario (otherwise the CLI would exit 1 even though
|
|
173
154
|
* the underlying TS functions would happily run). */
|
|
174
155
|
export declare function hasLocalModelSupport(): boolean;
|
|
175
|
-
/** Register the llama-cpp provider into agency's own smoltalk.
|
|
156
|
+
/** Register the llama-cpp provider into agency's own smoltalk. When called
|
|
157
|
+
* from inside a run (the agent's --local path), emit the `localModelLoaded`
|
|
158
|
+
* statelog event saying where the provider package came from; the plain CLI
|
|
159
|
+
* has no runtime frame, so `__ctx()` is undefined there and nothing is
|
|
160
|
+
* emitted. */
|
|
176
161
|
export declare function _registerLocalProvider(): Promise<void>;
|
|
177
162
|
/** Stream-hash a file's SHA-256 (hex), never buffering the whole file. The
|
|
178
163
|
* `update` is guarded so a synchronous throw in the data handler rejects the
|
|
@@ -203,6 +188,23 @@ export declare function _registerLocalModel(value: string, cacheDir?: string): P
|
|
|
203
188
|
export declare function formatGB(bytes: number): string;
|
|
204
189
|
/** Context window in compact units: 8192 → "8K", 131072 → "128K", 1e7 → "10M". */
|
|
205
190
|
export declare function formatCtx(tokens: number): string;
|
|
191
|
+
/** The `agency local list` view: every usable model (curated + aliases) with
|
|
192
|
+
* a downloaded marker, then cache-dir files no catalog entry claims. A row
|
|
193
|
+
* is "downloaded" when the manifest maps its target URI to a file that still
|
|
194
|
+
* exists in the cache dir; its SIZE column then shows the on-disk size
|
|
195
|
+
* rather than the catalog estimate. Compact and operational — `alias list`
|
|
196
|
+
* keeps the verbose per-model catalog with descriptions. Returns the block
|
|
197
|
+
* with no trailing newline (the caller's `console.log` adds exactly one). */
|
|
198
|
+
export declare function formatLocalList(args: {
|
|
199
|
+
dir: string;
|
|
200
|
+
entries: ModelNameEntry[];
|
|
201
|
+
manifest: Record<string, string>;
|
|
202
|
+
files: {
|
|
203
|
+
name: string;
|
|
204
|
+
path: string;
|
|
205
|
+
sizeBytes: number;
|
|
206
|
+
}[];
|
|
207
|
+
}): string;
|
|
206
208
|
/** Render the usable-model list as an aligned table: a header row plus one
|
|
207
209
|
* fact row per curated model (params, category, size, context window,
|
|
208
210
|
* license), the description on a dimmed line below, a blank line between
|