@sigrun/provider-pareto 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.1] - 2026-10-27
9
+ ### Changed
10
+ - Re-released through CI trusted publishing; this version carries SLSA
11
+ provenance. No functional change from 0.1.0.
12
+
13
+ ## [Unreleased]
14
+
15
+ ## [0.1.0] - 2026-10-18
16
+ ### Changed
17
+ - Renamed to `@sigrun/provider-pareto` for the 0.1.0 release (GitHub
18
+ repo `sigrun-ai/sigrun-provider-pareto`): published package metadata
19
+ (`publishConfig` access/provenance), host modules
20
+ (`@earendil-works/pi-ai`, `@earendil-works/pi-durable`) moved to
21
+ `peerDependencies` mirrored in `devDependencies` at exact `1.3.0`,
22
+ CHANGELOG.md joined the files whitelist, and a manifest hygiene check
23
+ wired into `npm test`.
24
+ ### Added
25
+ - Sigrun provider plugin: Pareto paygo
26
+ (`https://api.paretoinference.com/v1`, live-verified 2026-10-18) as
27
+ one dynamic OpenAI-compatible provider (`provider: "pareto"` stamped
28
+ on every catalog entry so persisted catalogs survive cold-boot
29
+ restore).
30
+ - Provider-only contributor factory: `{ name: "provider-pareto",
31
+ providers: [{ provider, readiness }], health }` — no pi-durable
32
+ extension surface.
33
+ - Deployment-configurable base URL: the async factory reads `baseUrl`
34
+ from the plugin's scoped settings with the constant as fallback; a
35
+ malformed override fails the factory loudly instead of silently
36
+ falling back.
37
+ - Credential resolution through sigrun's auth.json bridge with
38
+ `PARETO_API_KEY` as the ambient fallback (bridge credential always
39
+ wins).
40
+ - Three-state readiness tracker (`not_configured` / `degraded` /
41
+ `ready`) seeded from credential resolution and updated per refresh
42
+ outcome; `console.warn` on state transitions only. `health()` maps
43
+ `not_configured` to `"ok"` and configured-and-failing to
44
+ `"degraded"`.
45
+ - `fetchModels` throws on unconfigured and on failure — never returns
46
+ `[]` (an empty array would publish and wipe the persisted catalog).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael MacLeod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # @sigrun/provider-pareto
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [sigrun](https://github.com/sigrun-ai) provider plugin:
4
+ [Pareto](https://paretoinference.com) paygo (OpenAI-compatible
5
+ inference) as one dynamic chat provider.
6
+
7
+ ## What it contributes
8
+
9
+ A **provider-only contributor** — the default export is a factory
10
+ returning `{ name: "provider-pareto", providers: [{ provider, readiness }],
11
+ health }` with no pi-durable extension surface. Sigrun's loader merges
12
+ the provider into its `Models` store at boot.
13
+
14
+ - **Provider** `pareto` — dynamic catalog fetched from `<base>/models`
15
+ (OpenAI `[OI]` list shape; entries also carry a per-token dollar-string
16
+ `pricing` object this plugin does not map — cost stays zeroed and
17
+ structural) over `openai-completions`; boots with an empty baseline
18
+ and publishes the fetched catalog.
19
+ - **Readiness** (`not_configured` / `degraded` / `ready`, surfaced on
20
+ sigrun's `/v1/auth/check`) — seeded from credential resolution and
21
+ updated per refresh outcome; state transitions log via `console.warn`
22
+ (transitions only, never per failure). On the first configured boot
23
+ the provider announces `startup → ready` at credential *resolution* —
24
+ before any refresh has run (`ready` there means "credential resolved,
25
+ refresh not yet attempted", not "catalog verified").
26
+ - **Health** — `degraded` only when the provider is configured and its
27
+ last refresh failed; an unconfigured optional plugin reports `ok`.
28
+
29
+ ## Base URL
30
+
31
+ The default endpoint constant is `https://api.paretoinference.com/v1`
32
+ (live-verified 2026-10-18). Pareto's documented base URL is a
33
+ deployment-specific value, so the factory reads the plugin's scoped
34
+ settings first — `baseUrl` (string) in `settings/provider-pareto.json`
35
+ overrides the constant:
36
+
37
+ ```jsonc
38
+ // settings/provider-pareto.json (in the sigrun config dir)
39
+ { "baseUrl": "https://your-pareto-endpoint.example/v1" }
40
+ ```
41
+
42
+ An absent `baseUrl` uses the constant; a present-but-malformed one
43
+ (empty or not a string) fails the factory loudly (the extension
44
+ quarantines with the reason) rather than silently falling back.
45
+
46
+ ## Credentials
47
+
48
+ Resolved per request through sigrun's auth.json bridge, falling back to
49
+ the ambient environment:
50
+
51
+ 1. the bridged auth.json credential (`"default"` entry, then the
52
+ `"pareto"` entry), then
53
+ 2. `PARETO_API_KEY`.
54
+
55
+ The plugin never reads auth.json or `process.env` itself. Rotating the
56
+ key applies on the next resolution — no restart needed.
57
+
58
+ ## Failure semantics
59
+
60
+ `fetchModels` **throws** when unconfigured or when the catalog fetch
61
+ fails — it never returns an empty array, which pi-ai would publish and
62
+ persist, wiping the catalog. A failed refresh retains the previous
63
+ catalog and records a per-provider error.
64
+
65
+ ## Install
66
+
67
+ ```jsonc
68
+ // settings.json (sigrun config dir) — the UNIFIED extensions list:
69
+ // install + load in one place; npm and git specifiers are the same list
70
+ // npm ("@sigrun/provider-pareto@^0.1.0"), git
71
+ // ("git:github.com/sigrun-ai/sigrun-provider-pareto", or any
72
+ // https git URL with an optional @ref), or the file: form below.
73
+ {
74
+ "extensions": ["file:/path/to/sigrun-pareto"]
75
+ }
76
+ ```
77
+
78
+ The committed `dist/` is the load target for git installs; pi-ai and
79
+ pi-durable are peerDependencies (sigrun supplies them at runtime);
80
+ they are mirrored in devDependencies for this repo's own build and tests.
81
+
82
+ ## Development
83
+
84
+ ```sh
85
+ npm install # also builds dist/ via prepare
86
+ npm test # vitest — hermetic fixture HTTP server, no network
87
+ npm run typecheck
88
+ npm run build
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @sigrun/provider-pareto — a sigrun provider plugin (provider plugins Task 9):
3
+ * [Pareto](https://paretoinference.com) paygo (OpenAI-compatible
4
+ * inference) as one dynamic provider.
5
+ *
6
+ * Structurally identical to sigrun-ollama (the Task 7 reference plugin):
7
+ * the default export is the sigrun extension-package factory returning a
8
+ * PROVIDER-ONLY contributor — `{ name, providers, health }` with no
9
+ * `extension` field — consumed structurally by sigrun's loader. The
10
+ * deltas from the reference are the provider id, the base URL, the
11
+ * ambient env key, and ONE addition: the base URL is
12
+ * deployment-configurable through the plugin's scoped settings
13
+ * (`baseUrl` in `settings/provider-pareto.json`, constant fallback),
14
+ * because Pareto's documented endpoint is a deployment-specific value.
15
+ * Credentials resolve through sigrun's auth.json bridge (the stored
16
+ * credential arrives as `credential` on the auth resolver, ambient env
17
+ * as `ctx.env`) with `PARETO_API_KEY` as the fallback; the plugin never
18
+ * reads auth.json or `process.env` itself.
19
+ *
20
+ * Runtime imports from pi-ai are deliberate (`createProvider` + the
21
+ * OpenAI-completions api); the package still declares pi-ai/pi-durable as
22
+ * devDependencies only, because sigrun (the host) ships them and
23
+ * `syncPackages` installs with `--omit=dev` — the provider-plugins spec's
24
+ * dependency posture.
25
+ */
26
+ import { type Provider } from "@earendil-works/pi-ai/models";
27
+ import type { Model } from "@earendil-works/pi-ai";
28
+ /** The provider id models are registered (and stamped) under. */
29
+ export declare const PARETO_PROVIDER_ID = "pareto";
30
+ /**
31
+ * Pareto's OpenAI-compatible endpoint. Live-verified 2026-10-18; the
32
+ * operator can override per deployment via the plugin's scoped settings
33
+ * (`baseUrl`) — see {@link resolveParetoBaseUrl}.
34
+ */
35
+ export declare const PARETO_BASE_URL = "https://api.paretoinference.com/v1";
36
+ /** Ambient fallback when auth.json holds no credential. */
37
+ export declare const PARETO_API_KEY_ENV = "PARETO_API_KEY";
38
+ /**
39
+ * Structural slice of sigrun's seam host (src/harness/seam-host.ts) a
40
+ * provider plugin may consume. Declared here — structurally, like the
41
+ * bridge's pi-ai re-declarations — because the plugin cannot import
42
+ * sigrun core. The scoped settings store is async-only, which is why the
43
+ * loader awaits factory results (and why this plugin's factory is async).
44
+ */
45
+ export interface SigrunPluginHost {
46
+ readonly stores: {
47
+ /** This plugin's own `settings/<slug>.json`; absent file → `{}`. */
48
+ readonly settings: {
49
+ get(): Promise<Record<string, unknown>>;
50
+ set(value: Record<string, unknown>): Promise<void>;
51
+ };
52
+ };
53
+ }
54
+ /** Sigrun's three-state per-provider readiness vocabulary (Task 6). */
55
+ export type ProviderReadinessStatus = "not_configured" | "degraded" | "ready";
56
+ /** One tracker report: the status plus optional operator-facing detail. */
57
+ export interface ProviderReadiness {
58
+ readonly status: ProviderReadinessStatus;
59
+ readonly detail?: string;
60
+ }
61
+ /** Sigrun's §3 health vocabulary. */
62
+ export type HealthStatus = "ok" | "degraded";
63
+ /**
64
+ * The provider-plugin factory result (the loader's Task 1 contract):
65
+ * a provider-only contributor — `{ name, providers, health? }`, no
66
+ * `extension`. Structural, like the host slice above.
67
+ */
68
+ export interface ProviderPluginResult {
69
+ readonly name: string;
70
+ readonly providers: readonly {
71
+ readonly provider: Provider;
72
+ readonly readiness?: () => ProviderReadiness;
73
+ }[];
74
+ readonly health?: () => HealthStatus;
75
+ }
76
+ /**
77
+ * Resolve the plugin's base URL from its scoped settings: `baseUrl`
78
+ * (string) overrides the {@link PARETO_BASE_URL} constant. Absent → the
79
+ * constant; present-but-not-a-non-empty-string → throw (a malformed
80
+ * override must quarantine loudly, not silently fall back to a possibly
81
+ * wrong constant).
82
+ */
83
+ export declare function resolveParetoBaseUrl(settings: Record<string, unknown>): string;
84
+ /**
85
+ * Parse an OpenAI-compatible `[OI]` `/models` body into pi-ai chat
86
+ * models. Every entry is stamped `provider: "pareto"` — pi-ai's restore
87
+ * path drops stored models whose `provider` field differs from the
88
+ * registered id, so an unstamped catalog silently vanishes on cold boot.
89
+ * Malformed bodies throw (a refresh failure retains the previous
90
+ * catalog; garbage must never publish). Extra entry fields (the live
91
+ * endpoint also serves `owned_by` and a `pricing` object) are ignored.
92
+ */
93
+ export declare function parseParetoCatalog(body: unknown, baseUrl: string): Model<"openai-completions">[];
94
+ /** One built plugin: the contribution's parts, sharing the tracker closure. */
95
+ export interface ParetoPlugin {
96
+ readonly provider: Provider;
97
+ /** Synchronous read over plugin-owned state (never network/storage). */
98
+ readonly readiness: () => ProviderReadiness;
99
+ readonly health: () => HealthStatus;
100
+ }
101
+ /**
102
+ * Build the Pareto provider plus its readiness/health trackers.
103
+ * `baseUrl` is overridable (scoped settings in the default factory,
104
+ * fixture server in tests); the default is the live-verified endpoint.
105
+ */
106
+ export declare function createParetoPlugin(baseUrl?: string): ParetoPlugin;
107
+ /**
108
+ * The sigrun extension-package factory (provider-only contributor).
109
+ * Arity 1 declares the host-taking signature — sigrun's loader passes the
110
+ * seam host only to factories that declare one (`factory.length > 0`) —
111
+ * and the factory is ASYNC because the scoped settings store is
112
+ * async-only; sigrun's loader awaits factory results.
113
+ */
114
+ export default function sigrunPareto(host: SigrunPluginHost): Promise<ProviderPluginResult>;
package/dist/index.js ADDED
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @sigrun/provider-pareto — a sigrun provider plugin (provider plugins Task 9):
3
+ * [Pareto](https://paretoinference.com) paygo (OpenAI-compatible
4
+ * inference) as one dynamic provider.
5
+ *
6
+ * Structurally identical to sigrun-ollama (the Task 7 reference plugin):
7
+ * the default export is the sigrun extension-package factory returning a
8
+ * PROVIDER-ONLY contributor — `{ name, providers, health }` with no
9
+ * `extension` field — consumed structurally by sigrun's loader. The
10
+ * deltas from the reference are the provider id, the base URL, the
11
+ * ambient env key, and ONE addition: the base URL is
12
+ * deployment-configurable through the plugin's scoped settings
13
+ * (`baseUrl` in `settings/provider-pareto.json`, constant fallback),
14
+ * because Pareto's documented endpoint is a deployment-specific value.
15
+ * Credentials resolve through sigrun's auth.json bridge (the stored
16
+ * credential arrives as `credential` on the auth resolver, ambient env
17
+ * as `ctx.env`) with `PARETO_API_KEY` as the fallback; the plugin never
18
+ * reads auth.json or `process.env` itself.
19
+ *
20
+ * Runtime imports from pi-ai are deliberate (`createProvider` + the
21
+ * OpenAI-completions api); the package still declares pi-ai/pi-durable as
22
+ * devDependencies only, because sigrun (the host) ships them and
23
+ * `syncPackages` installs with `--omit=dev` — the provider-plugins spec's
24
+ * dependency posture.
25
+ */
26
+ import { createProvider } from "@earendil-works/pi-ai/models";
27
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
28
+ /** The provider id models are registered (and stamped) under. */
29
+ export const PARETO_PROVIDER_ID = "pareto";
30
+ /**
31
+ * Pareto's OpenAI-compatible endpoint. Live-verified 2026-10-18; the
32
+ * operator can override per deployment via the plugin's scoped settings
33
+ * (`baseUrl`) — see {@link resolveParetoBaseUrl}.
34
+ */
35
+ export const PARETO_BASE_URL = "https://api.paretoinference.com/v1";
36
+ /** Ambient fallback when auth.json holds no credential. */
37
+ export const PARETO_API_KEY_ENV = "PARETO_API_KEY";
38
+ /**
39
+ * Resolve the plugin's base URL from its scoped settings: `baseUrl`
40
+ * (string) overrides the {@link PARETO_BASE_URL} constant. Absent → the
41
+ * constant; present-but-not-a-non-empty-string → throw (a malformed
42
+ * override must quarantine loudly, not silently fall back to a possibly
43
+ * wrong constant).
44
+ */
45
+ export function resolveParetoBaseUrl(settings) {
46
+ if (!("baseUrl" in settings))
47
+ return PARETO_BASE_URL;
48
+ const value = settings.baseUrl;
49
+ if (typeof value !== "string" || value.trim().length === 0) {
50
+ throw new Error("provider-pareto: settings/provider-pareto.json baseUrl must be a non-empty string when present");
51
+ }
52
+ return value.trim();
53
+ }
54
+ /**
55
+ * Conservative placeholders for catalog entries Pareto's `/models` shape
56
+ * gives no usable metadata for (entries carry a per-token `pricing`
57
+ * object in dollar STRINGS, which this plugin does not map — cost stays
58
+ * zeroed and structural until sigrun defines a rate source of truth).
59
+ * Same values as the reference plugin and sigrun's degenerate env
60
+ * provider, keeping context management deterministic against unknown
61
+ * windows.
62
+ */
63
+ const DEFAULT_CONTEXT_WINDOW = 128_000;
64
+ const DEFAULT_MAX_TOKENS = 16_384;
65
+ /**
66
+ * Parse an OpenAI-compatible `[OI]` `/models` body into pi-ai chat
67
+ * models. Every entry is stamped `provider: "pareto"` — pi-ai's restore
68
+ * path drops stored models whose `provider` field differs from the
69
+ * registered id, so an unstamped catalog silently vanishes on cold boot.
70
+ * Malformed bodies throw (a refresh failure retains the previous
71
+ * catalog; garbage must never publish). Extra entry fields (the live
72
+ * endpoint also serves `owned_by` and a `pricing` object) are ignored.
73
+ */
74
+ export function parseParetoCatalog(body, baseUrl) {
75
+ if (typeof body !== "object" || body === null) {
76
+ throw new Error("pareto /models body is not a JSON object");
77
+ }
78
+ const data = body.data;
79
+ if (!Array.isArray(data)) {
80
+ throw new Error("pareto /models body has no data array");
81
+ }
82
+ return data.map((entry) => {
83
+ const id = entry?.id;
84
+ if (typeof id !== "string" || id.length === 0) {
85
+ throw new Error("pareto /models entry without a string id");
86
+ }
87
+ const model = {
88
+ id,
89
+ name: id,
90
+ api: "openai-completions",
91
+ // THE stamp — see the doc comment above.
92
+ provider: PARETO_PROVIDER_ID,
93
+ baseUrl,
94
+ // Pareto's /models carries no modality metadata; text is the
95
+ // safe floor (claiming image without evidence would ship image
96
+ // parts to models that cannot read them).
97
+ input: ["text"],
98
+ // Rates exist on the wire but are not mapped (see the
99
+ // placeholders comment); zeroed, usage tracking stays
100
+ // structural.
101
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
102
+ reasoning: false,
103
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
104
+ maxTokens: DEFAULT_MAX_TOKENS,
105
+ };
106
+ return model;
107
+ });
108
+ }
109
+ /**
110
+ * Build the Pareto provider plus its readiness/health trackers.
111
+ * `baseUrl` is overridable (scoped settings in the default factory,
112
+ * fixture server in tests); the default is the live-verified endpoint.
113
+ */
114
+ export function createParetoPlugin(baseUrl = PARETO_BASE_URL) {
115
+ // Tracker state: seeded from credential resolution (pi-ai never
116
+ // refreshes an unconfigured provider, so refresh outcomes alone cannot
117
+ // express not_configured), updated per refresh outcome. Readiness and
118
+ // health are synchronous derivations over this state.
119
+ let credentialResolved = false;
120
+ let lastRefreshFailed = false;
121
+ let lastRefreshDetail;
122
+ let lastAnnounced;
123
+ function status() {
124
+ if (!credentialResolved)
125
+ return "not_configured";
126
+ return lastRefreshFailed ? "degraded" : "ready";
127
+ }
128
+ /** Apply a state change; `console.warn` on transitions only. */
129
+ function announce() {
130
+ const next = status();
131
+ if (next === lastAnnounced)
132
+ return;
133
+ console.warn(`provider-pareto: pareto ${lastAnnounced ?? "startup"} → ${next}` +
134
+ (lastRefreshDetail !== undefined && next === "degraded"
135
+ ? ` (${lastRefreshDetail})`
136
+ : ""));
137
+ lastAnnounced = next;
138
+ }
139
+ function noteCredential(resolved) {
140
+ credentialResolved = resolved;
141
+ announce();
142
+ }
143
+ function noteRefresh(ok, detail) {
144
+ lastRefreshFailed = !ok;
145
+ lastRefreshDetail = ok ? undefined : detail;
146
+ announce();
147
+ }
148
+ const provider = createProvider({
149
+ id: PARETO_PROVIDER_ID,
150
+ name: "Pareto",
151
+ baseUrl,
152
+ // REQUIRED baseline: createProvider iterates it (omitting throws
153
+ // "baselineModels is not iterable"); empty = purely dynamic.
154
+ models: [],
155
+ auth: {
156
+ apiKey: {
157
+ name: "Pareto API key (auth.json or PARETO_API_KEY)",
158
+ resolve: async ({ credential, ctx }) => {
159
+ // Bridged auth.json credential (sigrun's bridge: the
160
+ // "default" entry first, then the provider's own) wins;
161
+ // the env var is the ambient fallback.
162
+ const stored = credential?.key?.trim() || undefined;
163
+ if (stored !== undefined) {
164
+ noteCredential(true);
165
+ return { auth: { apiKey: stored }, source: "auth.json" };
166
+ }
167
+ const envKey = (await ctx.env(PARETO_API_KEY_ENV))?.trim() || undefined;
168
+ if (envKey !== undefined) {
169
+ noteCredential(true);
170
+ return { auth: { apiKey: envKey }, source: PARETO_API_KEY_ENV };
171
+ }
172
+ // Unconfigured: undefined makes pi-ai skip the refresh
173
+ // phase entirely and requests fail loud.
174
+ noteCredential(false);
175
+ return undefined;
176
+ },
177
+ },
178
+ },
179
+ fetchModels: async (context) => {
180
+ // The context credential is whatever auth resolution produced —
181
+ // for this plugin always the merged api key; narrow rather than
182
+ // trust the union.
183
+ const key = context.credential?.type === "api_key"
184
+ ? context.credential.key?.trim() || undefined
185
+ : undefined;
186
+ if (key === undefined) {
187
+ noteCredential(false);
188
+ // NEVER return [] — the createProvider wrapper publishes +
189
+ // persists whatever fetchModels returns; an empty array
190
+ // would wipe the catalog.
191
+ throw new Error(`provider-pareto: Pareto is not configured ` +
192
+ `(no auth.json credential and no ${PARETO_API_KEY_ENV}); ` +
193
+ "refusing to publish an empty catalog");
194
+ }
195
+ const effectiveBase = context.baseUrl ?? baseUrl;
196
+ // URL-join needs the trailing slash ("models" against a
197
+ // slash-less base would resolve to the host root).
198
+ const root = effectiveBase.endsWith("/") ? effectiveBase : `${effectiveBase}/`;
199
+ try {
200
+ const response = await fetch(new URL("models", root), {
201
+ headers: { Authorization: `Bearer ${key}` },
202
+ signal: context.signal,
203
+ });
204
+ if (!response.ok) {
205
+ throw new Error(`HTTP ${response.status}`);
206
+ }
207
+ const models = parseParetoCatalog(await response.json(), effectiveBase);
208
+ noteRefresh(true);
209
+ return models;
210
+ }
211
+ catch (cause) {
212
+ // An aborted refresh is not a failed one: pi-ai discards
213
+ // the outcome anyway, so the tracker must not move.
214
+ if (context.signal.aborted)
215
+ throw cause;
216
+ const message = cause instanceof Error ? cause.message : String(cause);
217
+ noteRefresh(false, message);
218
+ throw new Error(`provider-pareto: Pareto catalog refresh failed: ${message}`);
219
+ }
220
+ },
221
+ api: openAICompletionsApi(),
222
+ });
223
+ return {
224
+ provider,
225
+ readiness: () => ({
226
+ status: status(),
227
+ ...(lastRefreshDetail !== undefined && status() === "degraded"
228
+ ? { detail: lastRefreshDetail }
229
+ : {}),
230
+ }),
231
+ // not_configured → ok (an unconfigured optional plugin is not a
232
+ // degraded process); configured-and-failing → degraded; else ok.
233
+ health: () => (status() === "degraded" ? "degraded" : "ok"),
234
+ };
235
+ }
236
+ /**
237
+ * The sigrun extension-package factory (provider-only contributor).
238
+ * Arity 1 declares the host-taking signature — sigrun's loader passes the
239
+ * seam host only to factories that declare one (`factory.length > 0`) —
240
+ * and the factory is ASYNC because the scoped settings store is
241
+ * async-only; sigrun's loader awaits factory results.
242
+ */
243
+ export default async function sigrunPareto(host) {
244
+ const settings = await host.stores.settings.get();
245
+ const plugin = createParetoPlugin(resolveParetoBaseUrl(settings));
246
+ return {
247
+ name: "provider-pareto",
248
+ providers: [{ provider: plugin.provider, readiness: plugin.readiness }],
249
+ health: plugin.health,
250
+ };
251
+ }
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
- "name": "@sigrun/provider-pareto",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@sigrun/provider-pareto",
3
+ "version": "0.1.1",
4
+ "description": "Sigrun provider plugin: Pareto paygo (OpenAI-compatible inference) as a dynamic provider",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.19.0"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "scripts": {
22
+ "test": "node scripts/check-manifest.mjs --expect-name @sigrun/provider-pareto && vitest run",
23
+ "typecheck": "tsc --noEmit",
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "prepare": "npm run build"
26
+ },
27
+ "devDependencies": {
28
+ "@earendil-works/pi-ai": "1.3.0",
29
+ "@earendil-works/pi-durable": "1.3.0",
30
+ "@types/node": "^26.6.4",
31
+ "typescript": "^7.0.2",
32
+ "vitest": "^5.0.3"
33
+ },
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "github",
37
+ "url": "git+https://github.com/sigrun-ai/sigrun-provider-pareto.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/sigrun-ai/sigrun-provider-pareto/issues"
41
+ },
42
+ "homepage": "https://github.com/sigrun-ai/sigrun-provider-pareto#readme",
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "provenance": true
46
+ },
47
+ "peerDependencies": {
48
+ "@earendil-works/pi-ai": "1.3.0",
49
+ "@earendil-works/pi-durable": "1.3.0"
50
+ }
51
+ }