@socketsecurity/lib 6.0.8 → 6.0.9
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/CHANGELOG.md +22 -0
- package/dist/ai/route.d.mts +21 -8
- package/dist/ai/route.js +21 -6
- package/dist/ai/spawn.d.mts +32 -0
- package/dist/ai/spawn.js +91 -2
- package/dist/ai/types.d.mts +10 -0
- package/dist/bin/prim.cjs +18797 -13627
- package/dist/bin/resolve.js +1 -1
- package/dist/cache/ttl/store.js +1 -1
- package/dist/compression/brotli.js +1 -1
- package/dist/compression/gzip.js +1 -1
- package/dist/config/layers.d.ts +53 -0
- package/dist/config/layers.js +83 -0
- package/dist/constants/socket.js +1 -1
- package/dist/debug/caller-info.js +1 -1
- package/dist/dlx/binary-cache.js +1 -1
- package/dist/dlx/binary-resolution.js +1 -1
- package/dist/dlx/firewall.js +1 -1
- package/dist/dlx/lockfile.js +1 -1
- package/dist/eco/npm/npm/parse-lockfile.js +1 -1
- package/dist/eco/npm/yarnpkg/yarn/parse-lockfile.js +1 -1
- package/dist/env/rewire.d.ts +5 -3
- package/dist/env/rewire.js +6 -7
- package/dist/env/xdg.d.ts +17 -0
- package/dist/env/xdg.js +21 -1
- package/dist/errors/stack.js +1 -2
- package/dist/external/@npmcli/package-json.js +3455 -4881
- package/dist/external/@yarnpkg/extensions.js +1 -1
- package/dist/external/external-pack.js +1 -1
- package/dist/external/npm-pack.js +29164 -31799
- package/dist/external/tar-fs.js +63 -16
- package/dist/external-tools/jre/detect-platform-arch.js +1 -1
- package/dist/external-tools/manifest.js +1 -1
- package/dist/external-tools/python/asset-names.js +1 -1
- package/dist/external-tools/python/uv-install.d.ts +89 -0
- package/dist/external-tools/python/uv-install.js +165 -0
- package/dist/external-tools/skillspector/from-uv.d.ts +30 -0
- package/dist/external-tools/skillspector/from-uv.js +56 -0
- package/dist/external-tools/skillspector/resolve.d.ts +18 -3
- package/dist/external-tools/skillspector/resolve.js +21 -8
- package/dist/external-tools/skillspector/types.d.ts +3 -1
- package/dist/external-tools/uv/asset-names.d.ts +36 -0
- package/dist/external-tools/uv/asset-names.js +73 -0
- package/dist/external-tools/uv/from-download.d.ts +17 -0
- package/dist/external-tools/uv/from-download.js +50 -0
- package/dist/external-tools/uv/from-path.d.ts +5 -0
- package/dist/external-tools/uv/from-path.js +22 -0
- package/dist/external-tools/uv/from-vfs.d.ts +7 -0
- package/dist/external-tools/uv/from-vfs.js +26 -0
- package/dist/external-tools/uv/resolve.d.ts +25 -0
- package/dist/external-tools/uv/resolve.js +61 -0
- package/dist/external-tools/uv/types.d.ts +24 -0
- package/dist/external-tools/uv/types.js +2 -0
- package/dist/fleet/repo-config.d.ts +53 -0
- package/dist/fleet/repo-config.js +79 -0
- package/dist/fs/allowed-dirs-cache.js +3 -5
- package/dist/fs/copy.d.ts +88 -0
- package/dist/fs/copy.js +89 -0
- package/dist/fs/safe.js +1 -1
- package/dist/git/_internal.js +2 -2
- package/dist/github/refs.js +10 -15
- package/dist/globs/_internal.js +1 -1
- package/dist/http-request/browser.js +1 -1
- package/dist/http-request/checksum-file.js +1 -1
- package/dist/http-request/headers.js +1 -1
- package/dist/integrity.d.ts +125 -78
- package/dist/integrity.js +168 -84
- package/dist/logger/colors.d.ts +1 -2
- package/dist/logger/colors.js +2 -2
- package/dist/logger/symbols-builder.js +7 -8
- package/dist/logger/symbols.js +8 -8
- package/dist/node/module.d.ts +73 -2
- package/dist/node/module.js +97 -12
- package/dist/objects/predicates.js +1 -1
- package/dist/packages/exports.js +2 -2
- package/dist/packages/isolation.js +1 -1
- package/dist/packages/manifest.d.ts +29 -0
- package/dist/packages/manifest.js +38 -2
- package/dist/packages/normalize.js +1 -1
- package/dist/packages/provenance.js +2 -2
- package/dist/packages/specs.js +1 -1
- package/dist/paths/_internal.d.ts +1 -7
- package/dist/paths/_internal.js +9 -15
- package/dist/paths/conversion.js +1 -1
- package/dist/paths/normalize.js +1 -1
- package/dist/paths/predicates.js +2 -1
- package/dist/paths/socket.d.ts +74 -23
- package/dist/paths/socket.js +97 -23
- package/dist/perf/metrics.js +1 -1
- package/dist/perf/report.js +1 -1
- package/dist/regexps/spec.js +1 -1
- package/dist/schema/validate.js +1 -1
- package/dist/secrets/broker.d.ts +35 -0
- package/dist/secrets/broker.js +72 -0
- package/dist/secrets/find.d.ts +6 -4
- package/dist/secrets/find.js +15 -1
- package/dist/secrets/rc.js +1 -1
- package/dist/smol/detect.js +1 -1
- package/dist/smol/http.js +1 -1
- package/dist/smol/https.js +1 -1
- package/dist/smol/manifest.js +1 -1
- package/dist/smol/path.js +1 -1
- package/dist/smol/primordial.js +1 -1
- package/dist/smol/purl.js +1 -1
- package/dist/smol/versions.js +1 -1
- package/dist/smol/vfs.js +1 -1
- package/dist/sorts/_internal.d.ts +1 -2
- package/dist/sorts/_internal.js +2 -6
- package/dist/sorts/semver.js +2 -2
- package/dist/spinner/create-spinner-class.js +1 -1
- package/dist/stdio/progress.js +1 -1
- package/dist/stdio/prompts.d.ts +0 -8
- package/dist/stdio/prompts.js +10 -23
- package/dist/strings/format.js +1 -1
- package/dist/strings/search.js +1 -1
- package/dist/themes/context.d.ts +5 -3
- package/dist/themes/context.js +5 -6
- package/llms.txt +750 -0
- package/package.json +79 -13
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [6.0.9](https://github.com/SocketDev/socket-lib/releases/tag/v6.0.9) - 2026-06-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`external-tools/uv` — uv resolver.** Resolves Astral's `uv` Python package manager across three tiers (embedded VFS, then system PATH, then a downloaded GitHub release), matching the shape of the other `external-tools/*` tool resolvers. Exports `resolveUv`, `ResolvedUv`, `UvSource`, plus the per-platform asset map and download helpers.
|
|
13
|
+
- **`external-tools/python/uv-install` — reproducible uv-project install helpers.** `uvSyncProject` installs a uv project at its exact pinned versions and refuses to proceed when those have drifted, so every machine gets the same result. `uvExportMaterialize` installs the same pinned versions into a content-addressed directory with no virtualenv, so the result is relocatable and embeddable in a single-file build. Concurrent callers serialize so two installs can't collide. The Python analog of the npm dlx install model.
|
|
14
|
+
- **`external-tools/skillspector` — pinned-project resolution tier.** `skillspectorFromUv` installs SkillSpector from a fully pinned uv project (every version fixed) and returns its entry point, resolved ahead of the existing git-SHA fallback when a project directory and `uv` binary are supplied. Adds a `'uv'` source to the resolution result.
|
|
15
|
+
- **`config/layers` — generic layered-config reader.** `readConfigLayers(name, { dirs })` reads a named config file from an ordered list of layer directories (lowest precedence first) and returns the layers that exist; absent or unparseable layers are skipped. `mergeConfigArray` concatenates one array-valued key across all layers, for lists that higher layers extend rather than replace. It carries no project-convention knowledge: the caller supplies the directories and the merge policy.
|
|
16
|
+
- **`paths` — `_wheelhouse` tool-layout dirs and the agent-clone dir.** `getSocketRackDir` and `getSocketRackToolDir` locate the racked tool store, `getSocketWheelhouseBinDir` the PATH-handle directory that points into it, and `getSocketRepoClonesDir` the directory where agents clone external repos for reference (kept out of the projects tree so sibling-walking tooling never treats a clone as a fleet member).
|
|
17
|
+
- **`ai` — offline/gated-model detection and fall-over.** `spawnTierWithFallback` walks a tier's cross-engine equivalence chain and runs the first engine that is both installed and authenticated, so a request still completes when the preferred model is down, gated, or unkeyed. `isModelUnavailable` recognizes a down-or-gated model from the engine's actual output rather than a brittle literal-string match. The `ai/route` resolver and `ai/subagent-status` reader are exposed as their own entry points.
|
|
18
|
+
- **`fs/copy` — recursive copy with three destination modes.** `copy(from, to, { mode })` copies a file or directory tree. `CopyMode` chooses how an existing destination is treated: `'overlay'` (the default: overwrite collisions, keep destination-only files), `'pave'` (the destination becomes an exact mirror of the source via an atomic sibling-temp-then-rename swap, so no stale files survive and a partial tree is never observed), or `'fill'` (no-clobber: add only what is missing, never overwrite). Also accepts `filter`, `dereference`, and an abort `signal`.
|
|
19
|
+
- **`node/requireFrom` — relative `require` bound to the caller's directory.** `requireFrom(fromUrl, specifier)` resolves a relative specifier (`./x`, `../y`) from the caller's own directory, passed as `import.meta.url`, so relative loads bind to the right base when modules run unbundled (for example AOT-compiled from source, where each module sits at its own nested path). Builtins and bare packages resolve as before.
|
|
20
|
+
- **`secrets` — proteus broker credential tier.** The credential resolver gains a broker layer in the documented order (explicit, then env, then broker, then keychain): it connects to the broker's runtime socket, requests the value, and self-gates. No socket means no broker, so it returns `undefined` and falls through to the keychain. Async only (absent from `resolveSync`) and skipped under `allowEnvOnly`. Existing `resolveProviderCredential` call sites are unchanged.
|
|
21
|
+
- **`paths` — `getRuntimeSocketPath` and `getXdgRuntimeDir` for daemon sockets.** One resolver a daemon and its clients both call to locate a runtime socket: the XDG runtime dir when present, a `$TMPDIR/<name>-<uid>` fallback, or a named pipe on Windows.
|
|
22
|
+
- **`llms.txt` — discovery index for AI agents.** `pnpm run docs` now also emits a publish-safe `llms.txt` at the package root that links each export subpath to its shipped `.d.mts` declaration, giving an agent one file to read after installing. It ships in the published tarball.
|
|
23
|
+
- **`packages/manifest` — `trimPublishManifest`.** Returns a shallow copy of a `package.json` that omits dev/build-only top-level fields (`devDependencies`, `scripts` by default) so a published tarball and its npm metadata stay lean. `drop` overrides the field set; `keep` retains a field even when dropped (e.g. a runtime `postinstall`). The original object is left intact.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`integrity` — one `Hash` currency (breaking).** Hashes are modeled as a single concept, a `Hash` carrying algorithm, hex, and sri, rather than an integrity-vs-checksum type duality, so the algorithm is explicit on every value. Adds `parseHash` (encoding- and length-aware), `computeHash` (sha512 default), `verifyHash(bytes, expected)` (verifies against the expected hash's declared algorithm), `equalHashes` (encoding-agnostic, and never equal across algorithms), `makeHash`, and `HashMismatchError`. Breaking: `verifyHash` now takes `(bytes, expected)`, not `(expected, computed)`, and `DlxHashMismatchError` is a deprecated alias of `HashMismatchError`. Back-compat shims remain: `checksumToIntegrity`, `integrityToChecksum`, `normalizeHash`, `computeHashes`, `isChecksum`, `isIntegrity`, `parseIntegrity`.
|
|
28
|
+
- **`fleet/repo-config` is now a thin wrapper over `config/layers`.** `resolveRepoConfig` (the fleet default layered under a per-repo override) and `mergeRepoConfigArray` stay at `fleet/repo-config`; the generic, convention-free primitives moved to the new `config/layers` entry. `resolveRepoConfig` callers see no behavior change.
|
|
29
|
+
|
|
8
30
|
## [6.0.8](https://github.com/SocketDev/socket-lib/releases/tag/v6.0.8) - 2026-06-11
|
|
9
31
|
|
|
10
32
|
### Added
|
package/dist/ai/route.d.mts
CHANGED
|
@@ -32,15 +32,16 @@ export interface TierCandidate {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Why the resolver returned what it did. - `preferred` — the tier's
|
|
35
|
-
* first-choice engine was available + keyed. - `
|
|
36
|
-
* engine was missing/unkeyed; an equivalent on another engine was used
|
|
37
|
-
* names the
|
|
35
|
+
* first-choice engine was available + keyed. - `fell-over` — the preferred
|
|
36
|
+
* engine was missing/unkeyed; an equivalent on another engine was used
|
|
37
|
+
* (`requestedTier` names the tier originally asked for). Matches the
|
|
38
|
+
* `fellOver` vocabulary `spawnTierWithFallback` (spawn.mts) reports.
|
|
38
39
|
*/
|
|
39
|
-
export type TierResolveReason = '
|
|
40
|
+
export type TierResolveReason = 'fell-over' | 'preferred';
|
|
40
41
|
export interface TierResolution {
|
|
41
42
|
readonly candidate: TierCandidate;
|
|
42
43
|
readonly reason: TierResolveReason;
|
|
43
|
-
readonly
|
|
44
|
+
readonly requestedTier?: AiTier | undefined;
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* The context a caller probes once and passes in: which engine CLIs exist, and
|
|
@@ -62,8 +63,20 @@ export declare function isCandidateUsable(candidate: TierCandidate, ctx: RouteCo
|
|
|
62
63
|
* Resolve a tier to the best available concrete target. Prefers the tier's
|
|
63
64
|
* first-choice (Claude) candidate; if its engine is missing or unkeyed, walks
|
|
64
65
|
* the cross-engine equivalence ladder and returns the first usable equivalent,
|
|
65
|
-
* tagging the result `
|
|
66
|
-
* `undefined` only when NOTHING in the chain is usable — the caller
|
|
67
|
-
* the work or surfaces a "no AI engine available" message.
|
|
66
|
+
* tagging the result `fell-over` with the original tier in `requestedTier`.
|
|
67
|
+
* Returns `undefined` only when NOTHING in the chain is usable — the caller
|
|
68
|
+
* then skips the work or surfaces a "no AI engine available" message.
|
|
68
69
|
*/
|
|
69
70
|
export declare function resolveTier(tier: AiTier, ctx: RouteContext): TierResolution | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* The ORDERED list of usable candidates for a tier — the runtime-fallback
|
|
73
|
+
* sequence. `resolveTier` returns only the single best pick (good when you
|
|
74
|
+
* trust it will work); this returns every usable candidate in preference order
|
|
75
|
+
* so a caller can advance to the next when one fails AT RUNTIME — e.g. the
|
|
76
|
+
* preferred model is installed + keyed (so it passes `isCandidateUsable`) yet
|
|
77
|
+
* its CLI reports it offline at spawn ("Claude Fable 5 is currently
|
|
78
|
+
* unavailable"). The static check can't predict an outage; only the spawn
|
|
79
|
+
* result can, so the caller walks this list until a spawn returns without the
|
|
80
|
+
* `unavailable` flag. Empty when nothing in the chain is usable.
|
|
81
|
+
*/
|
|
82
|
+
export declare function usableTierCandidates(tier: AiTier, ctx: RouteContext): TierCandidate[];
|
package/dist/ai/route.js
CHANGED
|
@@ -131,9 +131,9 @@ function isCandidateUsable(candidate, ctx) {
|
|
|
131
131
|
* Resolve a tier to the best available concrete target. Prefers the tier's
|
|
132
132
|
* first-choice (Claude) candidate; if its engine is missing or unkeyed, walks
|
|
133
133
|
* the cross-engine equivalence ladder and returns the first usable equivalent,
|
|
134
|
-
* tagging the result `
|
|
135
|
-
* `undefined` only when NOTHING in the chain is usable — the caller
|
|
136
|
-
* the work or surfaces a "no AI engine available" message.
|
|
134
|
+
* tagging the result `fell-over` with the original tier in `requestedTier`.
|
|
135
|
+
* Returns `undefined` only when NOTHING in the chain is usable — the caller
|
|
136
|
+
* then skips the work or surfaces a "no AI engine available" message.
|
|
137
137
|
*/
|
|
138
138
|
function resolveTier(tier, ctx) {
|
|
139
139
|
const chain = TIER_CHAINS[tier] ?? TIER_CHAINS.sonnet;
|
|
@@ -144,13 +144,28 @@ function resolveTier(tier, ctx) {
|
|
|
144
144
|
reason: "preferred"
|
|
145
145
|
} : {
|
|
146
146
|
candidate,
|
|
147
|
-
|
|
148
|
-
reason: "
|
|
147
|
+
requestedTier: tier,
|
|
148
|
+
reason: "fell-over"
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* The ORDERED list of usable candidates for a tier — the runtime-fallback
|
|
154
|
+
* sequence. `resolveTier` returns only the single best pick (good when you
|
|
155
|
+
* trust it will work); this returns every usable candidate in preference order
|
|
156
|
+
* so a caller can advance to the next when one fails AT RUNTIME — e.g. the
|
|
157
|
+
* preferred model is installed + keyed (so it passes `isCandidateUsable`) yet
|
|
158
|
+
* its CLI reports it offline at spawn ("Claude Fable 5 is currently
|
|
159
|
+
* unavailable"). The static check can't predict an outage; only the spawn
|
|
160
|
+
* result can, so the caller walks this list until a spawn returns without the
|
|
161
|
+
* `unavailable` flag. Empty when nothing in the chain is usable.
|
|
162
|
+
*/
|
|
163
|
+
function usableTierCandidates(tier, ctx) {
|
|
164
|
+
return (TIER_CHAINS[tier] ?? TIER_CHAINS.sonnet).filter((c) => isCandidateUsable(c, ctx));
|
|
165
|
+
}
|
|
152
166
|
|
|
153
167
|
//#endregion
|
|
154
168
|
exports.TIER_CHAINS = TIER_CHAINS;
|
|
155
169
|
exports.isCandidateUsable = isCandidateUsable;
|
|
156
|
-
exports.resolveTier = resolveTier;
|
|
170
|
+
exports.resolveTier = resolveTier;
|
|
171
|
+
exports.usableTierCandidates = usableTierCandidates;
|
package/dist/ai/spawn.d.mts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* SDK installs. Retry: 3 attempts on overload (HTTP 529 / "Overloaded"), exp.
|
|
12
12
|
* backoff (5s / 15s / 45s). Each retry is a fresh subprocess.
|
|
13
13
|
*/
|
|
14
|
+
import type { RouteContext, TierCandidate } from './route.mts';
|
|
15
|
+
import type { AiTier } from './tier.mts';
|
|
14
16
|
import type { AgentSpawnResult, AiAgentName, SpawnAiAgentOptions } from './types.mts';
|
|
15
17
|
export declare function backoffFor(attempt: number): number;
|
|
16
18
|
/**
|
|
@@ -31,6 +33,7 @@ export declare function buildArgs(agent: AiAgentName, options: SpawnAiAgentOptio
|
|
|
31
33
|
* shapes (`fable`, `claude-fable-5`, `mythos`, `claude-mythos-5`).
|
|
32
34
|
*/
|
|
33
35
|
export declare function isAdaptiveOnlyModel(model: string): boolean;
|
|
36
|
+
export declare function isModelUnavailable(stdout: string, stderr: string): boolean;
|
|
34
37
|
export declare function isOverloaded(stdout: string, stderr: string): boolean;
|
|
35
38
|
export declare function pickAgent(requested: AiAgentName | undefined, cwd: string): Promise<AiAgentName>;
|
|
36
39
|
/**
|
|
@@ -55,3 +58,32 @@ export declare function pickAgent(requested: AiAgentName | undefined, cwd: strin
|
|
|
55
58
|
* is requested, when none of the known agents are on PATH).
|
|
56
59
|
*/
|
|
57
60
|
export declare function spawnAiAgent(options: SpawnAiAgentOptions): Promise<AgentSpawnResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Result of a tier spawn that may have fallen over one or more offline models.
|
|
63
|
+
* `result` is the spawn that actually ran (the first whose model was not
|
|
64
|
+
* `unavailable`, or the last attempt if every candidate was down). `candidate`
|
|
65
|
+
* is the engine/model that produced it. `fellOver` lists the candidates that
|
|
66
|
+
* reported their model offline before this one — empty on a first-try success.
|
|
67
|
+
*/
|
|
68
|
+
export interface TierSpawnResult {
|
|
69
|
+
readonly candidate: TierCandidate;
|
|
70
|
+
readonly fellOver: readonly TierCandidate[];
|
|
71
|
+
readonly result: AgentSpawnResult;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Spawn a tier's work, automatically FALLING OVER to the next agent when a
|
|
75
|
+
* model is offline. Walks the tier's usable candidates (Claude → Codex →
|
|
76
|
+
* open-weight) in order; if a spawn comes back with `unavailable` (the model is
|
|
77
|
+
* down or gated — e.g. "Claude Fable 5 is currently unavailable"), it advances
|
|
78
|
+
* to the next candidate instead of failing. This is the runtime complement to
|
|
79
|
+
* `route.mts`'s static availability check: that check can't predict an outage,
|
|
80
|
+
* so the live spawn result drives the fallback.
|
|
81
|
+
*
|
|
82
|
+
* Returns the first non-`unavailable` spawn (success OR a genuine work failure
|
|
83
|
+
* on a model that WAS reachable — a real failure shouldn't silently retry on a
|
|
84
|
+
* weaker model). If every candidate is offline, returns the last attempt with
|
|
85
|
+
* its `unavailable` flag set, so the caller can report "all models down".
|
|
86
|
+
* Throws only when the tier has no usable candidate at all (nothing installed +
|
|
87
|
+
* keyed) — same contract as `resolveTier` returning undefined.
|
|
88
|
+
*/
|
|
89
|
+
export declare function spawnTierWithFallback(tier: AiTier, ctx: RouteContext, options: Omit<SpawnAiAgentOptions, 'agent' | 'effort' | 'model'>): Promise<TierSpawnResult>;
|
package/dist/ai/spawn.js
CHANGED
|
@@ -9,6 +9,7 @@ const require_process_spawn_errors = require('../process/spawn/errors.js');
|
|
|
9
9
|
const require_process_spawn_child = require('../process/spawn/child.js');
|
|
10
10
|
const require_ai_discover = require('./discover.js');
|
|
11
11
|
const require_primordials_promise = require('../primordials/promise.js');
|
|
12
|
+
const require_ai_route = require('./route.js');
|
|
12
13
|
|
|
13
14
|
//#region src/ai/spawn.mts
|
|
14
15
|
/**
|
|
@@ -113,6 +114,45 @@ function buildArgs(agent, options) {
|
|
|
113
114
|
function isAdaptiveOnlyModel(model) {
|
|
114
115
|
return /\b(?:fable|mythos)\b/i.test(model) || /claude-(?:fable|mythos)/i.test(model);
|
|
115
116
|
}
|
|
117
|
+
const MODEL_UNAVAILABLE_PHRASES = [
|
|
118
|
+
"access denied",
|
|
119
|
+
"currently unavailable",
|
|
120
|
+
"forbidden",
|
|
121
|
+
"have access",
|
|
122
|
+
"is unavailable",
|
|
123
|
+
"isn’t available",
|
|
124
|
+
"isn't available",
|
|
125
|
+
"may not exist",
|
|
126
|
+
"no access to",
|
|
127
|
+
"no such model",
|
|
128
|
+
"not authorized",
|
|
129
|
+
"not authorised",
|
|
130
|
+
"not available",
|
|
131
|
+
"permission denied",
|
|
132
|
+
"permission_denied",
|
|
133
|
+
"temporarily unavailable",
|
|
134
|
+
"unauthorized",
|
|
135
|
+
"unauthorised"
|
|
136
|
+
];
|
|
137
|
+
const MODEL_EXISTENCE_PHRASES = [
|
|
138
|
+
"does not exist",
|
|
139
|
+
"doesn't exist",
|
|
140
|
+
"no such",
|
|
141
|
+
"not exist",
|
|
142
|
+
"not found",
|
|
143
|
+
"unavailable",
|
|
144
|
+
"unknown",
|
|
145
|
+
"unreachable"
|
|
146
|
+
];
|
|
147
|
+
function isModelUnavailable(stdout, stderr) {
|
|
148
|
+
const text = `${stdout}\n${stderr}`.toLowerCase();
|
|
149
|
+
for (let i = 0, { length } = MODEL_UNAVAILABLE_PHRASES; i < length; i += 1) if (text.includes(MODEL_UNAVAILABLE_PHRASES[i])) return true;
|
|
150
|
+
if (/\bmodel[_-]?not[_-]?found\b/i.test(text) || /\bapi error:\s*(?:403|404)\b/i.test(text)) return true;
|
|
151
|
+
if (text.includes("model")) {
|
|
152
|
+
for (let i = 0, { length } = MODEL_EXISTENCE_PHRASES; i < length; i += 1) if (text.includes(MODEL_EXISTENCE_PHRASES[i])) return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
116
156
|
function isOverloaded(stdout, stderr) {
|
|
117
157
|
const re = /API Error: 529|Overloaded/i;
|
|
118
158
|
return re.test(stdout) || re.test(stderr);
|
|
@@ -200,7 +240,54 @@ async function spawnAiAgent(options) {
|
|
|
200
240
|
exitCode,
|
|
201
241
|
overloaded: isOverloaded(stdout, stderr),
|
|
202
242
|
stderr,
|
|
203
|
-
stdout
|
|
243
|
+
stdout,
|
|
244
|
+
unavailable: isModelUnavailable(stdout, stderr)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Spawn a tier's work, automatically FALLING OVER to the next agent when a
|
|
249
|
+
* model is offline. Walks the tier's usable candidates (Claude → Codex →
|
|
250
|
+
* open-weight) in order; if a spawn comes back with `unavailable` (the model is
|
|
251
|
+
* down or gated — e.g. "Claude Fable 5 is currently unavailable"), it advances
|
|
252
|
+
* to the next candidate instead of failing. This is the runtime complement to
|
|
253
|
+
* `route.mts`'s static availability check: that check can't predict an outage,
|
|
254
|
+
* so the live spawn result drives the fallback.
|
|
255
|
+
*
|
|
256
|
+
* Returns the first non-`unavailable` spawn (success OR a genuine work failure
|
|
257
|
+
* on a model that WAS reachable — a real failure shouldn't silently retry on a
|
|
258
|
+
* weaker model). If every candidate is offline, returns the last attempt with
|
|
259
|
+
* its `unavailable` flag set, so the caller can report "all models down".
|
|
260
|
+
* Throws only when the tier has no usable candidate at all (nothing installed +
|
|
261
|
+
* keyed) — same contract as `resolveTier` returning undefined.
|
|
262
|
+
*/
|
|
263
|
+
async function spawnTierWithFallback(tier, ctx, options) {
|
|
264
|
+
const candidates = require_ai_route.usableTierCandidates(tier, ctx);
|
|
265
|
+
if (candidates.length === 0) throw new require_primordials_error.ErrorCtor(`spawnTierWithFallback: no usable agent for tier "${tier}". No candidate engine is both installed and keyed (checked: ${candidates.length}). Install/authenticate one of the tier's engines, or pick a different tier.`);
|
|
266
|
+
const fellOver = [];
|
|
267
|
+
let last;
|
|
268
|
+
for (let i = 0, { length } = candidates; i < length; i += 1) {
|
|
269
|
+
const candidate = candidates[i];
|
|
270
|
+
const result = await spawnAiAgent({
|
|
271
|
+
...options,
|
|
272
|
+
agent: candidate.engine,
|
|
273
|
+
effort: candidate.effort,
|
|
274
|
+
model: candidate.model
|
|
275
|
+
});
|
|
276
|
+
last = {
|
|
277
|
+
candidate,
|
|
278
|
+
result
|
|
279
|
+
};
|
|
280
|
+
if (!result.unavailable) return {
|
|
281
|
+
candidate,
|
|
282
|
+
fellOver,
|
|
283
|
+
result
|
|
284
|
+
};
|
|
285
|
+
fellOver.push(candidate);
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
candidate: last.candidate,
|
|
289
|
+
fellOver: fellOver.slice(0, -1),
|
|
290
|
+
result: last.result
|
|
204
291
|
};
|
|
205
292
|
}
|
|
206
293
|
|
|
@@ -208,6 +295,8 @@ async function spawnAiAgent(options) {
|
|
|
208
295
|
exports.backoffFor = backoffFor;
|
|
209
296
|
exports.buildArgs = buildArgs;
|
|
210
297
|
exports.isAdaptiveOnlyModel = isAdaptiveOnlyModel;
|
|
298
|
+
exports.isModelUnavailable = isModelUnavailable;
|
|
211
299
|
exports.isOverloaded = isOverloaded;
|
|
212
300
|
exports.pickAgent = pickAgent;
|
|
213
|
-
exports.spawnAiAgent = spawnAiAgent;
|
|
301
|
+
exports.spawnAiAgent = spawnAiAgent;
|
|
302
|
+
exports.spawnTierWithFallback = spawnTierWithFallback;
|
package/dist/ai/types.d.mts
CHANGED
|
@@ -56,6 +56,16 @@ export interface AgentSpawnResult {
|
|
|
56
56
|
readonly overloaded: boolean;
|
|
57
57
|
readonly stderr: string;
|
|
58
58
|
readonly stdout: string;
|
|
59
|
+
/**
|
|
60
|
+
* True when the selected MODEL could not serve the request — it is offline
|
|
61
|
+
* (e.g. "Claude Fable 5 is currently unavailable") or gated/absent for this
|
|
62
|
+
* account ("issue with the selected model … may not exist or you may not have
|
|
63
|
+
* access"). Distinct from `overloaded`: the model is not coming back within a
|
|
64
|
+
* retry window, so the right response is to FALL OVER to the next agent in
|
|
65
|
+
* the tier chain rather than retry the same one. Both surface as a non-zero
|
|
66
|
+
* `exitCode`; this flag is how a caller knows to route elsewhere.
|
|
67
|
+
*/
|
|
68
|
+
readonly unavailable: boolean;
|
|
59
69
|
}
|
|
60
70
|
/**
|
|
61
71
|
* Inputs to a single agent spawn.
|