@oharness/engine 0.1.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/agent/agent.d.ts +196 -0
- package/dist/agent/agent.js +758 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/compaction.d.ts +68 -0
- package/dist/agent/compaction.js +179 -0
- package/dist/agent/compaction.js.map +1 -0
- package/dist/agent/hooks.d.ts +77 -0
- package/dist/agent/hooks.js +152 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/memory.d.ts +45 -0
- package/dist/agent/memory.js +147 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/overflow.d.ts +35 -0
- package/dist/agent/overflow.js +69 -0
- package/dist/agent/overflow.js.map +1 -0
- package/dist/agent/permissions.d.ts +181 -0
- package/dist/agent/permissions.js +289 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/prompt.d.ts +25 -0
- package/dist/agent/prompt.js +63 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/session.d.ts +108 -0
- package/dist/agent/session.js +231 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/settings.d.ts +37 -0
- package/dist/agent/settings.js +113 -0
- package/dist/agent/settings.js.map +1 -0
- package/dist/agent/skills.d.ts +58 -0
- package/dist/agent/skills.js +225 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/store.d.ts +308 -0
- package/dist/agent/store.js +596 -0
- package/dist/agent/store.js.map +1 -0
- package/dist/agent/subagent.d.ts +44 -0
- package/dist/agent/subagent.js +138 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/titles.d.ts +8 -0
- package/dist/agent/titles.js +23 -0
- package/dist/agent/titles.js.map +1 -0
- package/dist/agent/workspace.d.ts +60 -0
- package/dist/agent/workspace.js +156 -0
- package/dist/agent/workspace.js.map +1 -0
- package/dist/artifacts/publish.d.ts +94 -0
- package/dist/artifacts/publish.js +104 -0
- package/dist/artifacts/publish.js.map +1 -0
- package/dist/artifacts/store.d.ts +324 -0
- package/dist/artifacts/store.js +500 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/index.js +10 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/manager.d.ts +83 -0
- package/dist/auth/manager.js +266 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth.d.ts +83 -0
- package/dist/auth/oauth.js +327 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/providers/account.d.ts +59 -0
- package/dist/auth/providers/account.js +282 -0
- package/dist/auth/providers/account.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +24 -0
- package/dist/auth/providers/anthropic.js +209 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/generic.d.ts +57 -0
- package/dist/auth/providers/generic.js +155 -0
- package/dist/auth/providers/generic.js.map +1 -0
- package/dist/auth/providers/official.d.ts +3 -0
- package/dist/auth/providers/official.js +90 -0
- package/dist/auth/providers/official.js.map +1 -0
- package/dist/auth/providers/ottoport.d.ts +3 -0
- package/dist/auth/providers/ottoport.js +60 -0
- package/dist/auth/providers/ottoport.js.map +1 -0
- package/dist/auth/store.d.ts +22 -0
- package/dist/auth/store.js +138 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/types.d.ts +167 -0
- package/dist/auth/types.js +16 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/config.d.ts +197 -0
- package/dist/config.js +195 -0
- package/dist/config.js.map +1 -0
- package/dist/core/errors.d.ts +46 -0
- package/dist/core/errors.js +83 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/tokens.d.ts +12 -0
- package/dist/core/tokens.js +56 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/core/types.d.ts +91 -0
- package/dist/core/types.js +49 -0
- package/dist/core/types.js.map +1 -0
- package/dist/harness.d.ts +302 -0
- package/dist/harness.js +695 -0
- package/dist/harness.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/client.d.ts +55 -0
- package/dist/mcp/client.js +199 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +77 -0
- package/dist/mcp/manager.js +325 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/oauth.d.ts +63 -0
- package/dist/mcp/oauth.js +302 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/protocol.d.ts +130 -0
- package/dist/mcp/protocol.js +14 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/registry.d.ts +52 -0
- package/dist/mcp/registry.js +100 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/registry.json +293 -0
- package/dist/mcp/secrets.d.ts +20 -0
- package/dist/mcp/secrets.js +75 -0
- package/dist/mcp/secrets.js.map +1 -0
- package/dist/mcp/transport.d.ts +51 -0
- package/dist/mcp/transport.js +158 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/paths.d.ts +37 -0
- package/dist/paths.js +63 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugins.d.ts +55 -0
- package/dist/plugins.js +201 -0
- package/dist/plugins.js.map +1 -0
- package/dist/providers/builtin.d.ts +23 -0
- package/dist/providers/builtin.js +141 -0
- package/dist/providers/builtin.js.map +1 -0
- package/dist/providers/catalog.d.ts +129 -0
- package/dist/providers/catalog.js +254 -0
- package/dist/providers/catalog.js.map +1 -0
- package/dist/providers/catalog.json +472 -0
- package/dist/providers/discovery.d.ts +16 -0
- package/dist/providers/discovery.js +161 -0
- package/dist/providers/discovery.js.map +1 -0
- package/dist/providers/http.d.ts +24 -0
- package/dist/providers/http.js +139 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/piai.d.ts +124 -0
- package/dist/providers/piai.js +551 -0
- package/dist/providers/piai.js.map +1 -0
- package/dist/providers/registry.d.ts +65 -0
- package/dist/providers/registry.js +195 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/resilient.d.ts +46 -0
- package/dist/providers/resilient.js +117 -0
- package/dist/providers/resilient.js.map +1 -0
- package/dist/providers/types.d.ts +182 -0
- package/dist/providers/types.js +50 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/schedule/cron.d.ts +35 -0
- package/dist/schedule/cron.js +125 -0
- package/dist/schedule/cron.js.map +1 -0
- package/dist/schedule/scheduler.d.ts +87 -0
- package/dist/schedule/scheduler.js +237 -0
- package/dist/schedule/scheduler.js.map +1 -0
- package/dist/storage/kv.d.ts +56 -0
- package/dist/storage/kv.js +173 -0
- package/dist/storage/kv.js.map +1 -0
- package/dist/storage/migrate.d.ts +11 -0
- package/dist/storage/migrate.js +93 -0
- package/dist/storage/migrate.js.map +1 -0
- package/dist/storage/project.d.ts +9 -0
- package/dist/storage/project.js +57 -0
- package/dist/storage/project.js.map +1 -0
- package/dist/storage/records.d.ts +32 -0
- package/dist/storage/records.js +48 -0
- package/dist/storage/records.js.map +1 -0
- package/dist/sync/client.d.ts +79 -0
- package/dist/sync/client.js +118 -0
- package/dist/sync/client.js.map +1 -0
- package/dist/sync/mirror.d.ts +85 -0
- package/dist/sync/mirror.js +389 -0
- package/dist/sync/mirror.js.map +1 -0
- package/dist/sync/protocol.d.ts +67 -0
- package/dist/sync/protocol.js +16 -0
- package/dist/sync/protocol.js.map +1 -0
- package/dist/sync/settings.d.ts +56 -0
- package/dist/sync/settings.js +60 -0
- package/dist/sync/settings.js.map +1 -0
- package/dist/tools/builtin/artifact.d.ts +16 -0
- package/dist/tools/builtin/artifact.js +134 -0
- package/dist/tools/builtin/artifact.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +3 -0
- package/dist/tools/builtin/bash.js +142 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/fs.d.ts +19 -0
- package/dist/tools/builtin/fs.js +280 -0
- package/dist/tools/builtin/fs.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +22 -0
- package/dist/tools/builtin/index.js +42 -0
- package/dist/tools/builtin/index.js.map +1 -0
- package/dist/tools/builtin/mcp-resource.d.ts +3 -0
- package/dist/tools/builtin/mcp-resource.js +220 -0
- package/dist/tools/builtin/mcp-resource.js.map +1 -0
- package/dist/tools/builtin/memory.d.ts +10 -0
- package/dist/tools/builtin/memory.js +76 -0
- package/dist/tools/builtin/memory.js.map +1 -0
- package/dist/tools/builtin/recall.d.ts +13 -0
- package/dist/tools/builtin/recall.js +163 -0
- package/dist/tools/builtin/recall.js.map +1 -0
- package/dist/tools/builtin/search.d.ts +3 -0
- package/dist/tools/builtin/search.js +240 -0
- package/dist/tools/builtin/search.js.map +1 -0
- package/dist/tools/builtin/skill.d.ts +10 -0
- package/dist/tools/builtin/skill.js +46 -0
- package/dist/tools/builtin/skill.js.map +1 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +68 -0
- package/dist/tools/builtin/task.js.map +1 -0
- package/dist/tools/builtin/todo.d.ts +20 -0
- package/dist/tools/builtin/todo.js +63 -0
- package/dist/tools/builtin/todo.js.map +1 -0
- package/dist/tools/define.d.ts +19 -0
- package/dist/tools/define.js +39 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +39 -0
- package/dist/tools/registry.js +101 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +112 -0
- package/dist/tools/types.js +22 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/util/id.d.ts +9 -0
- package/dist/util/id.js +19 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/json.d.ts +25 -0
- package/dist/util/json.js +100 -0
- package/dist/util/json.js.map +1 -0
- package/dist/util/jwt.d.ts +74 -0
- package/dist/util/jwt.js +177 -0
- package/dist/util/jwt.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.js +59 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/queue.d.ts +26 -0
- package/dist/util/queue.js +106 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/schema.d.ts +17 -0
- package/dist/util/schema.js +98 -0
- package/dist/util/schema.js.map +1 -0
- package/dist/util/shell.d.ts +20 -0
- package/dist/util/shell.js +24 -0
- package/dist/util/shell.js.map +1 -0
- package/official.json +4 -0
- package/package.json +38 -0
- package/skills/artifact-authoring/SKILL.md +81 -0
- package/skills/canvas/SKILL.md +85 -0
- package/skills/skill-authoring/SKILL.md +85 -0
package/dist/paths.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Where OHarness keeps everything that outlives a project: credentials,
|
|
5
|
+
* sessions, the model catalog, user-level skills, agents and plugins.
|
|
6
|
+
*
|
|
7
|
+
* One resolver, because nine call sites each computing `homedir()` themselves
|
|
8
|
+
* is how `OHARNESS_HOME` ended up honoured by the session store and ignored
|
|
9
|
+
* by the other eight — an escape hatch that only worked for one kind of data
|
|
10
|
+
* is worse than none, since it reads as if the whole thing is redirected.
|
|
11
|
+
*
|
|
12
|
+
* Set `OHARNESS_HOME` to redirect all of it. Developing on the harness
|
|
13
|
+
* itself should always set it (the repo's npm scripts do), so working on the
|
|
14
|
+
* tool never writes into the home directory of the person working on it.
|
|
15
|
+
*/
|
|
16
|
+
export function userDir() {
|
|
17
|
+
const override = process.env["OHARNESS_HOME"];
|
|
18
|
+
if (override)
|
|
19
|
+
return path.resolve(override);
|
|
20
|
+
// On Linux, respect the XDG layout when the user has opted into it; falling
|
|
21
|
+
// back to a dotfile in $HOME everywhere else, which is what CLI tools of
|
|
22
|
+
// this shape are expected to do.
|
|
23
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
24
|
+
if (xdg && process.platform === "linux")
|
|
25
|
+
return path.join(xdg, "oharness");
|
|
26
|
+
return path.join(homedir(), ".oharness");
|
|
27
|
+
}
|
|
28
|
+
/** A path inside the user directory, e.g. `userPath("sessions")`. */
|
|
29
|
+
export function userPath(...segments) {
|
|
30
|
+
return path.join(userDir(), ...segments);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Project-local storage: `.oharness/` in the working directory.
|
|
34
|
+
*
|
|
35
|
+
* Kept separate from the user directory on purpose. Settings, skills and
|
|
36
|
+
* agents exist at both levels and the merge order between them is meaningful,
|
|
37
|
+
* so collapsing them into one path helper would blur a distinction the
|
|
38
|
+
* permission and skill layers depend on.
|
|
39
|
+
*/
|
|
40
|
+
export function projectPath(cwd, ...segments) {
|
|
41
|
+
return path.join(cwd, ".oharness", ...segments);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Expand a leading `~` the way a shell would, and leave everything else alone.
|
|
45
|
+
*
|
|
46
|
+
* Lives here rather than in the workspace policy because both the policy and
|
|
47
|
+
* the file tools need it and neither may import the other. A path the agent
|
|
48
|
+
* typed as `~/.oharness/skills` has to become the same string the shell would
|
|
49
|
+
* have produced, or a refusal message ends up naming a path that exists
|
|
50
|
+
* nowhere and the reader learns nothing from it.
|
|
51
|
+
*/
|
|
52
|
+
export function expandHome(input) {
|
|
53
|
+
if (input === "~")
|
|
54
|
+
return homedir();
|
|
55
|
+
if (input.startsWith("~/"))
|
|
56
|
+
return path.join(homedir(), input.slice(2));
|
|
57
|
+
return input;
|
|
58
|
+
}
|
|
59
|
+
/** Whether `target` is one of `roots` or sits underneath one. */
|
|
60
|
+
export function within(roots, target) {
|
|
61
|
+
return roots.some((root) => target === root || target.startsWith(root + path.sep));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC9C,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE5C,4EAA4E;IAC5E,yEAAyE;IACzE,iCAAiC;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,GAAG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAE3E,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAC3C,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,QAAQ,CAAC,GAAG,QAAkB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,GAAG,QAAkB;IAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,MAAM,CAAC,KAAwB,EAAE,MAAc;IAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HookConfig } from "./agent/hooks.ts";
|
|
2
|
+
import type { SubagentDefinition } from "./agent/subagent.ts";
|
|
3
|
+
import type { McpServerConfig } from "./mcp/client.ts";
|
|
4
|
+
/**
|
|
5
|
+
* A plugin is a declarative bundle of existing extension points: skills,
|
|
6
|
+
* agents, hooks and MCP server declarations. It contains no code of its own —
|
|
7
|
+
* tools still arrive through the host application — but two of those four
|
|
8
|
+
* name things that run, so where a plugin came from decides how much of it is
|
|
9
|
+
* adopted. See `pluginContributions`.
|
|
10
|
+
*
|
|
11
|
+
* Two manifest layouts are read, this project's and Claude Code's, because
|
|
12
|
+
* they describe the same thing and reading both costs about twenty lines.
|
|
13
|
+
*/
|
|
14
|
+
export interface PluginManifest {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
hooks?: HookConfig;
|
|
19
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
20
|
+
subagents?: SubagentDefinition[];
|
|
21
|
+
}
|
|
22
|
+
export interface PluginDefinition extends PluginManifest {
|
|
23
|
+
directory: string;
|
|
24
|
+
source: "user" | "project" | "config" | "claude";
|
|
25
|
+
skillDir: string;
|
|
26
|
+
agentDir: string;
|
|
27
|
+
}
|
|
28
|
+
export interface PluginConfig {
|
|
29
|
+
/** Extra directories containing plugin folders, relative to the project. */
|
|
30
|
+
dirs?: string[];
|
|
31
|
+
/** If set, only these plugin names load. */
|
|
32
|
+
enabled?: string[];
|
|
33
|
+
/** Disabled wins over enabled, useful for project-level policy. */
|
|
34
|
+
disabled?: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Read plugins Claude Code has already installed. On by default: they are
|
|
37
|
+
* files on this machine that this user chose to install, and the alternative
|
|
38
|
+
* is an empty list beside a directory full of them. Set false to ignore it.
|
|
39
|
+
*/
|
|
40
|
+
claudeCode?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Where Claude Code keeps what it has installed; overridable for tests. */
|
|
43
|
+
export declare function claudePluginCache(): string;
|
|
44
|
+
/** Discover plugins in a stable override order: Claude → user → project → config. */
|
|
45
|
+
export declare function discoverPlugins(cwd: string, config?: PluginConfig): Promise<PluginDefinition[]>;
|
|
46
|
+
/** Convert installed plugin declarations into ordinary harness configuration. */
|
|
47
|
+
export declare function pluginContributions(plugins: PluginDefinition[]): {
|
|
48
|
+
skillDirs: string[];
|
|
49
|
+
agentDirs: string[];
|
|
50
|
+
hooks: HookConfig;
|
|
51
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
52
|
+
subagents: SubagentDefinition[];
|
|
53
|
+
};
|
|
54
|
+
/** Lightweight validation keeps plugin manifests dependency-free and safe to ignore when malformed. */
|
|
55
|
+
export declare function parsePluginManifest(value: unknown): PluginManifest | undefined;
|
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { userPath } from "./paths.js";
|
|
3
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseJson } from "./util/json.js";
|
|
6
|
+
const MANIFEST = "oharness.plugin.json";
|
|
7
|
+
/**
|
|
8
|
+
* The other manifest this reads.
|
|
9
|
+
*
|
|
10
|
+
* A plugin here is a manifest plus a `skills/` and an `agents/` directory, and
|
|
11
|
+
* so is a Claude Code plugin — the same idea arrived at twice. Reading both is
|
|
12
|
+
* about twenty lines, and it is the difference between a menu with an
|
|
13
|
+
* ecosystem behind it and a menu that is empty until somebody hand-copies a
|
|
14
|
+
* folder into place. Nothing is imported or converted: the directory stays
|
|
15
|
+
* where it is and is read where it lies.
|
|
16
|
+
*/
|
|
17
|
+
const CLAUDE_MANIFEST = path.join(".claude-plugin", "plugin.json");
|
|
18
|
+
/** Its two sidecars, which hold what our manifest keeps inline. */
|
|
19
|
+
const CLAUDE_MCP = ".mcp.json";
|
|
20
|
+
const CLAUDE_HOOKS = path.join("hooks", "hooks.json");
|
|
21
|
+
/** `<marketplace>/<plugin>/<version>/` — three levels down from the cache. */
|
|
22
|
+
const CLAUDE_CACHE_DEPTH = 3;
|
|
23
|
+
/** Where Claude Code keeps what it has installed; overridable for tests. */
|
|
24
|
+
export function claudePluginCache() {
|
|
25
|
+
return process.env["OHARNESS_CLAUDE_PLUGINS"] ?? path.join(homedir(), ".claude", "plugins", "cache");
|
|
26
|
+
}
|
|
27
|
+
/** Discover plugins in a stable override order: Claude → user → project → config. */
|
|
28
|
+
export async function discoverPlugins(cwd, config = {}) {
|
|
29
|
+
const roots = [
|
|
30
|
+
// First, so anything of this project's own overrides it by name.
|
|
31
|
+
...(config.claudeCode === false
|
|
32
|
+
? []
|
|
33
|
+
: [
|
|
34
|
+
{
|
|
35
|
+
directory: claudePluginCache(),
|
|
36
|
+
source: "claude",
|
|
37
|
+
depth: CLAUDE_CACHE_DEPTH,
|
|
38
|
+
},
|
|
39
|
+
]),
|
|
40
|
+
{ directory: userPath("plugins"), source: "user" },
|
|
41
|
+
{ directory: path.join(cwd, ".oharness", "plugins"), source: "project" },
|
|
42
|
+
...((config.dirs ?? []).map((dir) => ({
|
|
43
|
+
directory: path.resolve(cwd, dir),
|
|
44
|
+
source: "config",
|
|
45
|
+
}))),
|
|
46
|
+
];
|
|
47
|
+
// A later layer with the same name deliberately replaces the earlier one.
|
|
48
|
+
const byName = new Map();
|
|
49
|
+
for (const root of roots) {
|
|
50
|
+
for (const plugin of await scanPluginRoot(root.directory, root.source, root.depth ?? 1)) {
|
|
51
|
+
// Within one root the same plugin can be cached at several versions;
|
|
52
|
+
// the newest is the one that was installed, the rest are history.
|
|
53
|
+
const seen = byName.get(plugin.name);
|
|
54
|
+
if (seen && seen.source === plugin.source && newer(seen, plugin))
|
|
55
|
+
continue;
|
|
56
|
+
byName.set(plugin.name, plugin);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const enabled = config.enabled ? new Set(config.enabled) : undefined;
|
|
60
|
+
const disabled = new Set(config.disabled ?? []);
|
|
61
|
+
return [...byName.values()].filter((plugin) => (!enabled || enabled.has(plugin.name)) && !disabled.has(plugin.name));
|
|
62
|
+
}
|
|
63
|
+
/** Convert installed plugin declarations into ordinary harness configuration. */
|
|
64
|
+
export function pluginContributions(plugins) {
|
|
65
|
+
const out = {
|
|
66
|
+
skillDirs: [],
|
|
67
|
+
agentDirs: [],
|
|
68
|
+
hooks: {},
|
|
69
|
+
mcpServers: {},
|
|
70
|
+
subagents: [],
|
|
71
|
+
};
|
|
72
|
+
for (const plugin of plugins) {
|
|
73
|
+
out.skillDirs.push(plugin.skillDir);
|
|
74
|
+
out.agentDirs.push(plugin.agentDir);
|
|
75
|
+
out.subagents.push(...(plugin.subagents ?? []));
|
|
76
|
+
// Skills and agents are text the agent reads when it decides to. Hooks and
|
|
77
|
+
// MCP servers are not: a hook is a command line that runs on its own
|
|
78
|
+
// schedule, and a server is a process started at launch. Adopting those
|
|
79
|
+
// from a directory the user filled in for a different program would mean
|
|
80
|
+
// this harness executing things nobody pointed at it. They are still
|
|
81
|
+
// reported, so a plugin's servers can be added the ordinary way, with the
|
|
82
|
+
// confirmation and the login that path already has.
|
|
83
|
+
if (plugin.source === "claude")
|
|
84
|
+
continue;
|
|
85
|
+
Object.assign(out.mcpServers, plugin.mcpServers ?? {});
|
|
86
|
+
for (const [event, hooks] of Object.entries(plugin.hooks ?? {})) {
|
|
87
|
+
out.hooks[event] = [...(out.hooks[event] ?? []), ...(hooks ?? [])];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Every plugin under a root, to a bounded depth.
|
|
94
|
+
*
|
|
95
|
+
* Depth exists because the two layouts nest differently: ours is one folder
|
|
96
|
+
* per plugin, and Claude Code's cache is marketplace, then plugin, then
|
|
97
|
+
* version. A directory that is itself a plugin is not descended into, so the
|
|
98
|
+
* walk stops at the first thing it recognises rather than picking up whatever
|
|
99
|
+
* a plugin happens to keep in a subfolder.
|
|
100
|
+
*/
|
|
101
|
+
async function scanPluginRoot(root, source, depth) {
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
const plugins = [];
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
112
|
+
continue;
|
|
113
|
+
// Dot-directories at a scan level are bookkeeping, never plugins.
|
|
114
|
+
if (entry.name.startsWith("."))
|
|
115
|
+
continue;
|
|
116
|
+
const directory = path.join(root, entry.name);
|
|
117
|
+
const plugin = await readPlugin(directory, source);
|
|
118
|
+
if (plugin)
|
|
119
|
+
plugins.push(plugin);
|
|
120
|
+
else if (depth > 1)
|
|
121
|
+
plugins.push(...(await scanPluginRoot(directory, source, depth - 1)));
|
|
122
|
+
}
|
|
123
|
+
return plugins;
|
|
124
|
+
}
|
|
125
|
+
/** One directory, in either layout, or nothing if it is neither. */
|
|
126
|
+
async function readPlugin(directory, source) {
|
|
127
|
+
const own = await readManifest(path.join(directory, MANIFEST));
|
|
128
|
+
if (own) {
|
|
129
|
+
return {
|
|
130
|
+
...own,
|
|
131
|
+
directory,
|
|
132
|
+
source,
|
|
133
|
+
skillDir: path.join(directory, "skills"),
|
|
134
|
+
agentDir: path.join(directory, "agents"),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const claude = await readManifest(path.join(directory, CLAUDE_MANIFEST));
|
|
138
|
+
if (!claude)
|
|
139
|
+
return undefined;
|
|
140
|
+
// The same declarations, kept in sidecar files rather than inline.
|
|
141
|
+
const mcp = await readJsonFile(path.join(directory, CLAUDE_MCP));
|
|
142
|
+
const hooks = await readJsonFile(path.join(directory, CLAUDE_HOOKS));
|
|
143
|
+
return {
|
|
144
|
+
...claude,
|
|
145
|
+
...(isRecord(mcp?.mcpServers) ? { mcpServers: mcp.mcpServers } : {}),
|
|
146
|
+
...(isRecord(hooks?.hooks) ? { hooks: hooks.hooks } : {}),
|
|
147
|
+
directory,
|
|
148
|
+
source,
|
|
149
|
+
skillDir: path.join(directory, "skills"),
|
|
150
|
+
agentDir: path.join(directory, "agents"),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function readManifest(file) {
|
|
154
|
+
const value = await readJsonFile(file);
|
|
155
|
+
return value === undefined ? undefined : parsePluginManifest(value);
|
|
156
|
+
}
|
|
157
|
+
async function readJsonFile(file) {
|
|
158
|
+
try {
|
|
159
|
+
const info = await stat(file);
|
|
160
|
+
if (!info.isFile())
|
|
161
|
+
return undefined;
|
|
162
|
+
const parsed = parseJson(await readFile(file, "utf8"));
|
|
163
|
+
return parsed.ok ? parsed.value : undefined;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Whether `a` is at least as new as `b`, comparing dotted versions. */
|
|
170
|
+
function newer(a, b) {
|
|
171
|
+
const parts = (v) => (v ?? "0").split(/[.-]/).map((n) => Number.parseInt(n, 10) || 0);
|
|
172
|
+
const left = parts(a.version);
|
|
173
|
+
const right = parts(b.version);
|
|
174
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
175
|
+
const one = left[i] ?? 0;
|
|
176
|
+
const two = right[i] ?? 0;
|
|
177
|
+
if (one !== two)
|
|
178
|
+
return one > two;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
/** Lightweight validation keeps plugin manifests dependency-free and safe to ignore when malformed. */
|
|
183
|
+
export function parsePluginManifest(value) {
|
|
184
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
185
|
+
return undefined;
|
|
186
|
+
const raw = value;
|
|
187
|
+
if (typeof raw.name !== "string" || raw.name.trim() === "")
|
|
188
|
+
return undefined;
|
|
189
|
+
return {
|
|
190
|
+
name: raw.name.trim(),
|
|
191
|
+
...(typeof raw.description === "string" ? { description: raw.description } : {}),
|
|
192
|
+
...(typeof raw.version === "string" ? { version: raw.version } : {}),
|
|
193
|
+
...(isRecord(raw.hooks) ? { hooks: raw.hooks } : {}),
|
|
194
|
+
...(isRecord(raw.mcpServers) ? { mcpServers: raw.mcpServers } : {}),
|
|
195
|
+
...(Array.isArray(raw.subagents) ? { subagents: raw.subagents } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function isRecord(value) {
|
|
199
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=plugins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Api, Model as PiModel } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { CatalogProvider } from "./catalog.ts";
|
|
3
|
+
import type { ModelSpec } from "./types.ts";
|
|
4
|
+
export interface SeedOptions {
|
|
5
|
+
/** Entries already stated by a catalog file; these are never replaced. */
|
|
6
|
+
existing?: ModelSpec[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Model specs for every catalog provider pi-ai has built-in entries for.
|
|
10
|
+
*
|
|
11
|
+
* A failure here is never fatal: the seed is a convenience, and a catalog that
|
|
12
|
+
* loads with fewer models is a far better outcome than a harness that will not
|
|
13
|
+
* start because a dependency reorganised its data.
|
|
14
|
+
*/
|
|
15
|
+
export declare function builtinModels(providers: Record<string, CatalogProvider>, opts?: SeedOptions): Promise<ModelSpec[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Translate one pi-ai model into a harness spec.
|
|
18
|
+
*
|
|
19
|
+
* The endpoint deliberately does not come across: pi-ai carries a `baseUrl`
|
|
20
|
+
* per model, but which host a provider talks to is the catalog entry's to
|
|
21
|
+
* decide — that is what makes pointing Kimi at its mainland endpoint work.
|
|
22
|
+
*/
|
|
23
|
+
export declare function specFromPiModel(entry: CatalogProvider, model: PiModel<Api>): ModelSpec;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { PI_APIS } from "./piai.js";
|
|
2
|
+
/**
|
|
3
|
+
* Levels pi-ai only offers when the model names them.
|
|
4
|
+
*
|
|
5
|
+
* `thinkingLevelMap` is a patch, not a listing: an absent key means "provider
|
|
6
|
+
* default", and null means unsupported. That reads in opposite directions for
|
|
7
|
+
* the two halves of the scale, since `xhigh` and `max` are opt-in upstream.
|
|
8
|
+
*/
|
|
9
|
+
const OPT_IN_EFFORTS = ["xhigh", "max"];
|
|
10
|
+
const DEFAULT_EFFORTS = ["low", "medium", "high"];
|
|
11
|
+
/**
|
|
12
|
+
* Model specs for every catalog provider pi-ai has built-in entries for.
|
|
13
|
+
*
|
|
14
|
+
* A failure here is never fatal: the seed is a convenience, and a catalog that
|
|
15
|
+
* loads with fewer models is a far better outcome than a harness that will not
|
|
16
|
+
* start because a dependency reorganised its data.
|
|
17
|
+
*/
|
|
18
|
+
export async function builtinModels(providers, opts = {}) {
|
|
19
|
+
let catalog;
|
|
20
|
+
try {
|
|
21
|
+
// Dynamic, so the ~600KB of bundled model data is read when a catalog is
|
|
22
|
+
// built and not merely because something imported this module.
|
|
23
|
+
catalog = (await import("@earendil-works/pi-ai/providers/all"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
let known;
|
|
29
|
+
try {
|
|
30
|
+
known = new Set(catalog.getBuiltinProviders());
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// Which catalog entry claims each pi-ai provider. Ids mostly match; where
|
|
36
|
+
// they do not, the catalog says so with `piProvider`.
|
|
37
|
+
const claims = new Map();
|
|
38
|
+
for (const entry of Object.values(providers)) {
|
|
39
|
+
const piId = entry.piProvider ?? entry.id;
|
|
40
|
+
if (!known.has(piId))
|
|
41
|
+
continue;
|
|
42
|
+
// An entry naming the provider outright beats one that only shares its id
|
|
43
|
+
// by accident, so a mapping is never stolen by declaration order.
|
|
44
|
+
const held = claims.get(piId);
|
|
45
|
+
if (held && (held.piProvider === piId || held.id === piId))
|
|
46
|
+
continue;
|
|
47
|
+
claims.set(piId, entry);
|
|
48
|
+
}
|
|
49
|
+
const taken = new Set();
|
|
50
|
+
for (const spec of opts.existing ?? []) {
|
|
51
|
+
taken.add(spec.id);
|
|
52
|
+
taken.add(`${spec.provider} ${spec.model}`);
|
|
53
|
+
}
|
|
54
|
+
const seeded = [];
|
|
55
|
+
for (const [piId, entry] of claims) {
|
|
56
|
+
let models;
|
|
57
|
+
try {
|
|
58
|
+
models = catalog.getBuiltinModels(piId);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
for (const model of models) {
|
|
64
|
+
// A model pi-ai serves over the Responses API cannot be driven over Chat
|
|
65
|
+
// Completions just because the catalog entry points at the same vendor;
|
|
66
|
+
// the request shape differs. Seed only what this entry can actually call.
|
|
67
|
+
if (model.api !== PI_APIS[entry.protocol])
|
|
68
|
+
continue;
|
|
69
|
+
const spec = specFromPiModel(entry, model);
|
|
70
|
+
if (taken.has(spec.id) || taken.has(`${spec.provider} ${spec.model}`))
|
|
71
|
+
continue;
|
|
72
|
+
taken.add(spec.id);
|
|
73
|
+
taken.add(`${spec.provider} ${spec.model}`);
|
|
74
|
+
seeded.push(spec);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return seeded;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Translate one pi-ai model into a harness spec.
|
|
81
|
+
*
|
|
82
|
+
* The endpoint deliberately does not come across: pi-ai carries a `baseUrl`
|
|
83
|
+
* per model, but which host a provider talks to is the catalog entry's to
|
|
84
|
+
* decide — that is what makes pointing Kimi at its mainland endpoint work.
|
|
85
|
+
*/
|
|
86
|
+
export function specFromPiModel(entry, model) {
|
|
87
|
+
const compat = (model.compat ?? {});
|
|
88
|
+
const levels = model.thinkingLevelMap;
|
|
89
|
+
return {
|
|
90
|
+
id: `${entry.id}/${model.id}`,
|
|
91
|
+
provider: entry.id,
|
|
92
|
+
model: model.id,
|
|
93
|
+
displayName: model.name || model.id,
|
|
94
|
+
contextWindow: model.contextWindow,
|
|
95
|
+
maxOutputTokens: model.maxTokens,
|
|
96
|
+
// pi-ai publishes no tool flag, and a chat model that cannot call tools is
|
|
97
|
+
// useless here anyway — the harness's own default assumes the same.
|
|
98
|
+
supportsTools: true,
|
|
99
|
+
supportsImages: model.input.includes("image"),
|
|
100
|
+
supportsParallelToolCalls: true,
|
|
101
|
+
// A quoted cache-read price is the only evidence in this data that the
|
|
102
|
+
// vendor caches at all.
|
|
103
|
+
supportsPromptCache: (model.cost?.cacheRead ?? 0) > 0,
|
|
104
|
+
thinkingMode: thinkingMode(model, compat),
|
|
105
|
+
effortLevels: effortLevels(model, levels),
|
|
106
|
+
supportsSampling: compat["supportsTemperature"] !== false,
|
|
107
|
+
...price("costPer1MInput", model.cost?.input),
|
|
108
|
+
...price("costPer1MOutput", model.cost?.output),
|
|
109
|
+
...price("costPer1MCacheRead", model.cost?.cacheRead),
|
|
110
|
+
...price("costPer1MCacheWrite", model.cost?.cacheWrite),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* pi-ai models are `reasoning: true/false` plus a per-protocol rendering, so
|
|
115
|
+
* "effort" is the honest translation of every reasoning model here. The one
|
|
116
|
+
* distinction worth keeping is the Anthropic quirk the wire layer round-trips:
|
|
117
|
+
* a model that only accepts an adaptive config rejects a budget outright.
|
|
118
|
+
*/
|
|
119
|
+
function thinkingMode(model, compat) {
|
|
120
|
+
if (!model.reasoning)
|
|
121
|
+
return "none";
|
|
122
|
+
return compat["forceAdaptiveThinking"] === true ? "adaptive" : "effort";
|
|
123
|
+
}
|
|
124
|
+
function effortLevels(model, levels) {
|
|
125
|
+
if (!model.reasoning)
|
|
126
|
+
return [];
|
|
127
|
+
if (!levels)
|
|
128
|
+
return [...DEFAULT_EFFORTS];
|
|
129
|
+
return [
|
|
130
|
+
...DEFAULT_EFFORTS.filter((level) => levels[level] !== null),
|
|
131
|
+
...OPT_IN_EFFORTS.filter((level) => typeof levels[level] === "string"),
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Zero means "not quoted" in this data, and a spec carrying a zero price reads
|
|
136
|
+
* as free everywhere downstream — a worse answer than saying nothing.
|
|
137
|
+
*/
|
|
138
|
+
function price(field, value) {
|
|
139
|
+
return value && value > 0 ? { [field]: value } : {};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=builtin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin.js","sourceRoot":"","sources":["../../src/providers/builtin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,KAAK,CAAU,CAAC;AACjD,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAO3D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAA0C,EAC1C,OAAoB,EAAE;IAEtB,IAAI,OAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,yEAAyE;QACzE,+DAA+D;QAC/D,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,qCAAqC,CAAC,CAA8B,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,KAAkB,CAAC;IACvB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,0EAA0E;IAC1E,sDAAsD;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/B,0EAA0E;QAC1E,kEAAkE;QAClE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;YAAE,SAAS;QACrE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,IAAI,MAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,yEAAyE;YACzE,wEAAwE;YACxE,0EAA0E;YAC1E,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACpD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC3C,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAAE,SAAS;YAChF,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAsB,EAAE,KAAmB;IACzE,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAA4B,CAAC;IAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,gBAA6D,CAAC;IAEnF,OAAO;QACL,EAAE,EAAE,GAAG,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,EAAE;QAC7B,QAAQ,EAAE,KAAK,CAAC,EAAE;QAClB,KAAK,EAAE,KAAK,CAAC,EAAE;QACf,WAAW,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;QACnC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,eAAe,EAAE,KAAK,CAAC,SAAS;QAChC,2EAA2E;QAC3E,oEAAoE;QACpE,aAAa,EAAE,IAAI;QACnB,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC7C,yBAAyB,EAAE,IAAI;QAC/B,uEAAuE;QACvE,wBAAwB;QACxB,mBAAmB,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC;QACrD,YAAY,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC;QACzC,YAAY,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC;QACzC,gBAAgB,EAAE,MAAM,CAAC,qBAAqB,CAAC,KAAK,KAAK;QACzD,GAAG,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;QAC7C,GAAG,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC;QAC/C,GAAG,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;QACrD,GAAG,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAAmB,EAAE,MAA+B;IACxE,IAAI,CAAC,KAAK,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC;IACpC,OAAO,MAAM,CAAC,uBAAuB,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1E,CAAC;AAED,SAAS,YAAY,CACnB,KAAmB,EACnB,MAAiD;IAEjD,IAAI,CAAC,KAAK,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAChC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,eAAe,CAAC,CAAC;IACzC,OAAO;QACL,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;QAC5D,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC;KACvE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,KAAK,CAAC,KAAa,EAAE,KAAyB;IACrD,OAAO,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ModelSpec } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The model catalog is data, not code.
|
|
4
|
+
*
|
|
5
|
+
* Vendor model ids turn over faster than releases of this package, so baking
|
|
6
|
+
* them into TypeScript guarantees the list is wrong within weeks. Provider
|
|
7
|
+
* facts (endpoint, env var, protocol) are stable and worth shipping; model ids
|
|
8
|
+
* mostly come from `/models refresh`, which asks the vendor directly.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Wire formats a catalog entry may declare.
|
|
12
|
+
*
|
|
13
|
+
* The first three are the historical names and stay as they are — renaming
|
|
14
|
+
* them would invalidate every user catalog in existence. The rest were opened
|
|
15
|
+
* up by moving the wire layer onto pi-ai, which implements them already:
|
|
16
|
+
* `openai-responses` for vendors that have moved off Chat Completions, and the
|
|
17
|
+
* two cloud-signed endpoints that were previously out of reach.
|
|
18
|
+
*/
|
|
19
|
+
export type ProtocolId = "anthropic-messages" | "openai-chat" | "gemini" | "openai-responses" | "bedrock" | "vertex";
|
|
20
|
+
export type DiscoveryFormat = "openai" | "openrouter";
|
|
21
|
+
export interface CatalogProvider {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
/** Which wire format this endpoint speaks. */
|
|
25
|
+
protocol: ProtocolId;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
/** Env vars consulted in order for an API key. */
|
|
28
|
+
envVars: string[];
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
/** How to read `/models`; absent means the endpoint is not worth calling. */
|
|
31
|
+
discovery?: DiscoveryFormat;
|
|
32
|
+
/**
|
|
33
|
+
* The id this vendor goes by in pi-ai's built-in model catalog, when it is
|
|
34
|
+
* not this entry's own id. Only naming exists to be reconciled here — the
|
|
35
|
+
* endpoint and credentials stay this entry's.
|
|
36
|
+
*/
|
|
37
|
+
piProvider?: string;
|
|
38
|
+
/** Some servers reject `stream_options: {include_usage: true}`. */
|
|
39
|
+
includeUsage?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Send an `Idempotency-Key` header, one value per request.
|
|
42
|
+
*
|
|
43
|
+
* Not a nicety on the endpoints that ask for it: a metering gateway reserves
|
|
44
|
+
* the worst-case cost before it forwards, and the key is what lets a retry
|
|
45
|
+
* resume that reservation instead of opening a second one. OttoPort rejects
|
|
46
|
+
* a completion without it outright.
|
|
47
|
+
*
|
|
48
|
+
* It cannot be an entry in `headers` — that map is one static value for the
|
|
49
|
+
* life of the process, and a key reused across two different requests is
|
|
50
|
+
* precisely what the header exists to prevent.
|
|
51
|
+
*/
|
|
52
|
+
idempotencyKey?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* A ceiling on `max_tokens`, when the endpoint imposes one below what its
|
|
55
|
+
* models advertise.
|
|
56
|
+
*
|
|
57
|
+
* Gateways cap output to bound what a single request can cost, and the cap
|
|
58
|
+
* is the gateway's rather than the model's — so `/models` does not mention
|
|
59
|
+
* it and discovery lands on its own default, which is higher. The result is
|
|
60
|
+
* a 400 on every request rather than a shorter answer, so the number is
|
|
61
|
+
* stated here where the endpoint is described.
|
|
62
|
+
*/
|
|
63
|
+
maxOutputTokens?: number;
|
|
64
|
+
docsUrl?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Surfaced without being searched for.
|
|
67
|
+
*
|
|
68
|
+
* Which vendors are worth showing first is a fact about the market, not
|
|
69
|
+
* about this code, so it lives in the catalog data with everything else that
|
|
70
|
+
* changes on its own schedule.
|
|
71
|
+
*/
|
|
72
|
+
popular?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The one this project stands behind. At most one entry should carry it —
|
|
75
|
+
* a badge that half the list wears recommends nothing.
|
|
76
|
+
*/
|
|
77
|
+
recommended?: boolean;
|
|
78
|
+
/** One line shown under the name, so a chooser is not a list of bare names. */
|
|
79
|
+
description?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface Catalog {
|
|
82
|
+
/** When the bundled model entries were last checked. */
|
|
83
|
+
verified?: string;
|
|
84
|
+
providers: Record<string, CatalogProvider>;
|
|
85
|
+
models: ModelSpec[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A catalog as read from one file. Fields are optional because a later file
|
|
89
|
+
* is a *patch*: a user catalog that only changes `baseUrl` must not blank out
|
|
90
|
+
* the label and env vars it did not mention.
|
|
91
|
+
*/
|
|
92
|
+
export interface CatalogPatch {
|
|
93
|
+
verified?: string;
|
|
94
|
+
providers: Record<string, Partial<CatalogProvider>>;
|
|
95
|
+
models: ModelSpec[];
|
|
96
|
+
}
|
|
97
|
+
/** Where a user's own catalog lives; overrides the bundled one. */
|
|
98
|
+
export declare function userCatalogPath(): string;
|
|
99
|
+
/**
|
|
100
|
+
* Load the catalog: bundled file, then the user's, then any explicit paths.
|
|
101
|
+
* Later sources override earlier ones per provider id and per model id.
|
|
102
|
+
*
|
|
103
|
+
* Under all of them sits pi-ai's built-in model catalog, which is why a
|
|
104
|
+
* provider nobody has written an entry for still has models to offer. It is
|
|
105
|
+
* seeded last but ranked lowest: appended after the stated entries so the
|
|
106
|
+
* order callers see — and the default model, which is the first of them —
|
|
107
|
+
* still comes from what this package verified.
|
|
108
|
+
*/
|
|
109
|
+
export declare function loadCatalog(extraPaths?: string[]): Promise<Catalog>;
|
|
110
|
+
/** Parse and finalize a single complete catalog. */
|
|
111
|
+
export declare function parseCatalog(raw: unknown, source?: string): Catalog;
|
|
112
|
+
/**
|
|
113
|
+
* Parse one catalog file into a patch, keeping absent fields absent so a later
|
|
114
|
+
* merge can tell "not mentioned" from "set to empty".
|
|
115
|
+
*/
|
|
116
|
+
export declare function parsePatch(raw: unknown, source?: string): CatalogPatch;
|
|
117
|
+
export declare function mergeCatalog(base: Catalog, override: Catalog): Catalog;
|
|
118
|
+
export declare function mergePatch(base: CatalogPatch, override: CatalogPatch): CatalogPatch;
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a merged patch into a usable catalog, dropping providers that are
|
|
121
|
+
* still incomplete after every layer has been applied.
|
|
122
|
+
*/
|
|
123
|
+
export declare function finalize(patch: CatalogPatch): Catalog;
|
|
124
|
+
/**
|
|
125
|
+
* Fill in a partial model entry. Defaults are deliberately conservative:
|
|
126
|
+
* claiming a capability the model lacks is a 400 at request time, whereas
|
|
127
|
+
* under-claiming only costs a feature.
|
|
128
|
+
*/
|
|
129
|
+
export declare function normalizeModel(entry: unknown): ModelSpec | undefined;
|