@sigrun/provider-ollama 0.0.0-stage → 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/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
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
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-10-18
11
+ ### Changed
12
+ - Renamed to `@sigrun/provider-ollama` for the 0.1.0 release (GitHub
13
+ repo `sigrun-ai/sigrun-provider-ollama`): published package metadata
14
+ (`publishConfig` access/provenance), host modules
15
+ (`@earendil-works/pi-ai`, `@earendil-works/pi-durable`) moved to
16
+ `peerDependencies` mirrored in `devDependencies` at exact `1.3.0`,
17
+ CHANGELOG.md joined the files whitelist, and a manifest hygiene check
18
+ wired into `npm test`. The contributor identity follows the package
19
+ slug: the factory result's `name` becomes `provider-ollama` and the
20
+ scoped settings file `settings/provider-ollama.json` (the loader
21
+ pre-keys the two-phase host from the package name; a stale declared
22
+ name would split the factory-time settings scope).
23
+
24
+ ### Added
25
+ - Deployment-configurable base URL: the (now async) factory reads
26
+ `baseUrl` from the plugin's scoped settings
27
+ (`settings/provider-ollama.json`) with the constant as fallback; a
28
+ malformed override fails the factory loudly instead of silently
29
+ falling back (mirrors sigrun-pareto; restart-required).
30
+ - Reference sigrun provider plugin: Ollama Cloud (`https://ollama.com/v1`)
31
+ as one dynamic OpenAI-compatible provider (`provider: "ollama"` stamped on
32
+ every catalog entry so persisted catalogs survive cold-boot restore).
33
+ - Provider-only contributor factory: `{ name: "provider-ollama", providers:
34
+ [{ provider, readiness }], health }` — no pi-durable extension surface.
35
+ - Credential resolution through sigrun's auth.json bridge with
36
+ `OLLAMA_API_KEY` as the ambient fallback (bridge credential always wins).
37
+ - Three-state readiness tracker (`not_configured` / `degraded` / `ready`)
38
+ seeded from credential resolution and updated per refresh outcome;
39
+ `console.warn` on state transitions only. `health()` maps `not_configured`
40
+ to `"ok"` and configured-and-failing to `"degraded"`.
41
+ - `fetchModels` throws on unconfigured and on failure — never returns `[]`
42
+ (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,75 @@
1
- # Temporary Holding Version
1
+ # @sigrun/provider-ollama
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
+ The reference [sigrun](https://github.com/sigrun-ai/sigrun) provider
4
+ plugin: [Ollama Cloud](https://ollama.com) (`https://ollama.com/v1`,
5
+ OpenAI-compatible) 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-ollama", 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** `ollama` — dynamic catalog fetched from `<base>/models`
15
+ (OpenAI `[OI]` list shape) over `openai-completions`; boots with an
16
+ empty baseline and publishes the fetched catalog.
17
+ - **Readiness** (`not_configured` / `degraded` / `ready`, surfaced on
18
+ sigrun's `/v1/auth/check`) — seeded from credential resolution and
19
+ updated per refresh outcome; state transitions log via `console.warn`
20
+ (transitions only, never per failure). On the first configured boot
21
+ the provider announces `startup → ready` at credential *resolution* —
22
+ before any refresh has run (`ready` there means "credential resolved,
23
+ refresh not yet attempted", not "catalog verified").
24
+ - **Health** — `degraded` only when the provider is configured and its
25
+ last refresh failed; an unconfigured optional plugin reports `ok`.
26
+
27
+ ## Credentials
28
+
29
+ Resolved per request through sigrun's auth.json bridge, falling back to
30
+ the ambient environment:
31
+
32
+ 1. the bridged auth.json credential (`"default"` entry, then the
33
+ `"ollama"` entry), then
34
+ 2. `OLLAMA_API_KEY`.
35
+
36
+ The plugin never reads auth.json or `process.env` itself. Rotating the
37
+ key applies on the next resolution — no restart needed (there are no
38
+ plugin settings; the endpoint is fixed).
39
+
40
+ ## Failure semantics
41
+
42
+ `fetchModels` **throws** when unconfigured or when the catalog fetch
43
+ fails — it never returns an empty array, which pi-ai would publish and
44
+ persist, wiping the catalog. A failed refresh retains the previous
45
+ catalog and records a per-provider error.
46
+
47
+ ## Install
48
+
49
+ ```jsonc
50
+ // settings.json (sigrun config dir) — the UNIFIED extensions list:
51
+ // install + load in one place; npm and git specifiers are the same list
52
+ // npm ("@sigrun/provider-ollama@^0.1.0"), git
53
+ // ("git:github.com/sigrun-ai/sigrun-provider-ollama", or any
54
+ // https git URL with an optional @ref), or the file: form below.
55
+ {
56
+ "extensions": ["file:/path/to/sigrun-ollama"]
57
+ }
58
+ ```
59
+
60
+ The committed `dist/` is the load target for git installs; pi-ai and
61
+ pi-durable are peerDependencies (sigrun supplies them at runtime);
62
+ they are mirrored in devDependencies for this repo's own build and tests.
63
+
64
+ ## Development
65
+
66
+ ```sh
67
+ npm install # also builds dist/ via prepare
68
+ npm test # vitest — hermetic fixture HTTP server, no network
69
+ npm run typecheck
70
+ npm run build
71
+ ```
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @sigrun/provider-ollama — the reference sigrun provider plugin (provider plugins
3
+ * Task 7): Ollama Cloud (`https://ollama.com/v1`) as one dynamic
4
+ * OpenAI-compatible provider.
5
+ *
6
+ * The default export is the sigrun extension-package factory returning a
7
+ * PROVIDER-ONLY contributor — `{ name, providers, health }` with no
8
+ * `extension` field — consumed structurally by sigrun's loader (Task 1
9
+ * pathway). Credentials resolve through sigrun's auth.json bridge (the
10
+ * stored credential arrives as `credential` on the auth resolver, ambient
11
+ * env as `ctx.env`) with `OLLAMA_API_KEY` as the fallback; the plugin
12
+ * never reads auth.json or `process.env` itself.
13
+ *
14
+ * Runtime imports from pi-ai are deliberate (`createProvider` + the
15
+ * OpenAI-completions api); the package still declares pi-ai/pi-durable as
16
+ * devDependencies only, because sigrun (the host) ships them and
17
+ * `syncPackages` installs with `--omit=dev` — the provider-plugins spec's
18
+ * dependency posture.
19
+ */
20
+ import { type Provider } from "@earendil-works/pi-ai/models";
21
+ import type { Model } from "@earendil-works/pi-ai";
22
+ /** The provider id models are registered (and stamped) under. */
23
+ export declare const OLLAMA_PROVIDER_ID = "ollama";
24
+ /**
25
+ * Ollama Cloud's OpenAI-compatible endpoint. The operator can override
26
+ * per deployment via the plugin's scoped settings (`baseUrl`) — see
27
+ * {@link resolveOllamaBaseUrl}.
28
+ */
29
+ export declare const OLLAMA_BASE_URL = "https://ollama.com/v1";
30
+ /** Ambient fallback when auth.json holds no credential. */
31
+ export declare const OLLAMA_API_KEY_ENV = "OLLAMA_API_KEY";
32
+ /**
33
+ * Structural slice of sigrun's seam host (src/harness/seam-host.ts) a
34
+ * provider plugin may consume. Declared here — structurally, like the
35
+ * bridge's pi-ai re-declarations — because the plugin cannot import
36
+ * sigrun core. The scoped settings store is async-only, which is why the
37
+ * loader awaits factory results.
38
+ */
39
+ export interface SigrunPluginHost {
40
+ readonly stores: {
41
+ /** This plugin's own `settings/<slug>.json`; absent file → `{}`. */
42
+ readonly settings: {
43
+ get(): Promise<Record<string, unknown>>;
44
+ set(value: Record<string, unknown>): Promise<void>;
45
+ };
46
+ };
47
+ }
48
+ /** Sigrun's three-state per-provider readiness vocabulary (Task 6). */
49
+ export type ProviderReadinessStatus = "not_configured" | "degraded" | "ready";
50
+ /** One tracker report: the status plus optional operator-facing detail. */
51
+ export interface ProviderReadiness {
52
+ readonly status: ProviderReadinessStatus;
53
+ readonly detail?: string;
54
+ }
55
+ /** Sigrun's §3 health vocabulary. */
56
+ export type HealthStatus = "ok" | "degraded";
57
+ /**
58
+ * The provider-plugin factory result (the loader's Task 1 contract):
59
+ * a provider-only contributor — `{ name, providers, health? }`, no
60
+ * `extension`. Structural, like the host slice above.
61
+ */
62
+ export interface ProviderPluginResult {
63
+ readonly name: string;
64
+ readonly providers: readonly {
65
+ readonly provider: Provider;
66
+ readonly readiness?: () => ProviderReadiness;
67
+ }[];
68
+ readonly health?: () => HealthStatus;
69
+ }
70
+ /**
71
+ * Parse an OpenAI-compatible `[OI]` `/models` body into pi-ai chat models.
72
+ * Every entry is stamped `provider: "ollama"` — pi-ai's restore path drops
73
+ * stored models whose `provider` field differs from the registered id, so
74
+ * an unstamped catalog silently vanishes on cold boot. Malformed bodies
75
+ * throw (a refresh failure retains the previous catalog; garbage must
76
+ * never publish).
77
+ */
78
+ export declare function parseOllamaCatalog(body: unknown, baseUrl: string): Model<"openai-completions">[];
79
+ /** One built plugin: the contribution's parts, sharing the tracker closure. */
80
+ export interface OllamaPlugin {
81
+ readonly provider: Provider;
82
+ /** Synchronous read over plugin-owned state (never network/storage). */
83
+ readonly readiness: () => ProviderReadiness;
84
+ readonly health: () => HealthStatus;
85
+ }
86
+ /**
87
+ * Build the Ollama Cloud provider plus its readiness/health trackers.
88
+ * `baseUrl` is overridable so hermetic tests drive a fixture server; the
89
+ * default is the real cloud endpoint.
90
+ */
91
+ export declare function createOllamaPlugin(baseUrl?: string): OllamaPlugin;
92
+ /**
93
+ * Resolve the plugin's base URL from its scoped settings: `baseUrl`
94
+ * (string) overrides the {@link OLLAMA_BASE_URL} constant. Absent → the
95
+ * constant; present-but-not-a-non-empty-string → throw (a malformed
96
+ * override must quarantine loudly, not silently fall back to a possibly
97
+ * wrong constant).
98
+ */
99
+ export declare function resolveOllamaBaseUrl(settings: Record<string, unknown>): string;
100
+ /**
101
+ * The sigrun extension-package factory (provider-only contributor).
102
+ * Arity 1 declares the host-taking signature — sigrun's loader passes the
103
+ * seam host only to factories that declare one (`factory.length > 0`) —
104
+ * and the factory is ASYNC because the scoped settings store is
105
+ * async-only; sigrun's loader awaits factory results. The base URL is
106
+ * deployment-configurable through the scoped settings (`baseUrl` in
107
+ * `settings/provider-ollama.json`, constant fallback): deployments behind a
108
+ * proxy or mirror — and hermetic test fixtures — redirect the endpoint
109
+ * that way (restart-required, exactly like sigrun-pareto/sigrun-bifrost).
110
+ */
111
+ export default function sigrunOllama(host: SigrunPluginHost): Promise<ProviderPluginResult>;
package/dist/index.js ADDED
@@ -0,0 +1,243 @@
1
+ /**
2
+ * @sigrun/provider-ollama — the reference sigrun provider plugin (provider plugins
3
+ * Task 7): Ollama Cloud (`https://ollama.com/v1`) as one dynamic
4
+ * OpenAI-compatible provider.
5
+ *
6
+ * The default export is the sigrun extension-package factory returning a
7
+ * PROVIDER-ONLY contributor — `{ name, providers, health }` with no
8
+ * `extension` field — consumed structurally by sigrun's loader (Task 1
9
+ * pathway). Credentials resolve through sigrun's auth.json bridge (the
10
+ * stored credential arrives as `credential` on the auth resolver, ambient
11
+ * env as `ctx.env`) with `OLLAMA_API_KEY` as the fallback; the plugin
12
+ * never reads auth.json or `process.env` itself.
13
+ *
14
+ * Runtime imports from pi-ai are deliberate (`createProvider` + the
15
+ * OpenAI-completions api); the package still declares pi-ai/pi-durable as
16
+ * devDependencies only, because sigrun (the host) ships them and
17
+ * `syncPackages` installs with `--omit=dev` — the provider-plugins spec's
18
+ * dependency posture.
19
+ */
20
+ import { createProvider } from "@earendil-works/pi-ai/models";
21
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
22
+ /** The provider id models are registered (and stamped) under. */
23
+ export const OLLAMA_PROVIDER_ID = "ollama";
24
+ /**
25
+ * Ollama Cloud's OpenAI-compatible endpoint. The operator can override
26
+ * per deployment via the plugin's scoped settings (`baseUrl`) — see
27
+ * {@link resolveOllamaBaseUrl}.
28
+ */
29
+ export const OLLAMA_BASE_URL = "https://ollama.com/v1";
30
+ /** Ambient fallback when auth.json holds no credential. */
31
+ export const OLLAMA_API_KEY_ENV = "OLLAMA_API_KEY";
32
+ /**
33
+ * Conservative placeholders for catalog entries Ollama's `/models` shape
34
+ * gives no metadata for (no window, no limits, no rates). Same values as
35
+ * sigrun's degenerate env provider (`DEFAULT_OPENAI_*`), keeping context
36
+ * management deterministic against unknown windows.
37
+ */
38
+ const DEFAULT_CONTEXT_WINDOW = 128_000;
39
+ const DEFAULT_MAX_TOKENS = 16_384;
40
+ /**
41
+ * Parse an OpenAI-compatible `[OI]` `/models` body into pi-ai chat models.
42
+ * Every entry is stamped `provider: "ollama"` — pi-ai's restore path drops
43
+ * stored models whose `provider` field differs from the registered id, so
44
+ * an unstamped catalog silently vanishes on cold boot. Malformed bodies
45
+ * throw (a refresh failure retains the previous catalog; garbage must
46
+ * never publish).
47
+ */
48
+ export function parseOllamaCatalog(body, baseUrl) {
49
+ if (typeof body !== "object" || body === null) {
50
+ throw new Error("ollama /models body is not a JSON object");
51
+ }
52
+ const data = body.data;
53
+ if (!Array.isArray(data)) {
54
+ throw new Error("ollama /models body has no data array");
55
+ }
56
+ return data.map((entry) => {
57
+ const id = entry?.id;
58
+ if (typeof id !== "string" || id.length === 0) {
59
+ throw new Error("ollama /models entry without a string id");
60
+ }
61
+ const model = {
62
+ id,
63
+ name: id,
64
+ api: "openai-completions",
65
+ // THE stamp — see the doc comment above.
66
+ provider: OLLAMA_PROVIDER_ID,
67
+ baseUrl,
68
+ // Ollama's /models carries no modality metadata; text is the
69
+ // safe floor (claiming image without evidence would ship image
70
+ // parts to models that cannot read them).
71
+ input: ["text"],
72
+ // Unknown rates: zeroed, usage tracking stays structural.
73
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
74
+ reasoning: false,
75
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
76
+ maxTokens: DEFAULT_MAX_TOKENS,
77
+ };
78
+ return model;
79
+ });
80
+ }
81
+ /**
82
+ * Build the Ollama Cloud provider plus its readiness/health trackers.
83
+ * `baseUrl` is overridable so hermetic tests drive a fixture server; the
84
+ * default is the real cloud endpoint.
85
+ */
86
+ export function createOllamaPlugin(baseUrl = OLLAMA_BASE_URL) {
87
+ // Tracker state: seeded from credential resolution (pi-ai never
88
+ // refreshes an unconfigured provider, so refresh outcomes alone cannot
89
+ // express not_configured), updated per refresh outcome. Readiness and
90
+ // health are synchronous derivations over this state.
91
+ let credentialResolved = false;
92
+ let lastRefreshFailed = false;
93
+ let lastRefreshDetail;
94
+ let lastAnnounced;
95
+ function status() {
96
+ if (!credentialResolved)
97
+ return "not_configured";
98
+ return lastRefreshFailed ? "degraded" : "ready";
99
+ }
100
+ /** Apply a state change; `console.warn` on transitions only. */
101
+ function announce() {
102
+ const next = status();
103
+ if (next === lastAnnounced)
104
+ return;
105
+ console.warn(`provider-ollama: ollama ${lastAnnounced ?? "startup"} → ${next}` +
106
+ (lastRefreshDetail !== undefined && next === "degraded"
107
+ ? ` (${lastRefreshDetail})`
108
+ : ""));
109
+ lastAnnounced = next;
110
+ }
111
+ function noteCredential(resolved) {
112
+ credentialResolved = resolved;
113
+ announce();
114
+ }
115
+ function noteRefresh(ok, detail) {
116
+ lastRefreshFailed = !ok;
117
+ lastRefreshDetail = ok ? undefined : detail;
118
+ announce();
119
+ }
120
+ const provider = createProvider({
121
+ id: OLLAMA_PROVIDER_ID,
122
+ name: "Ollama Cloud",
123
+ baseUrl,
124
+ // REQUIRED baseline: createProvider iterates it (omitting throws
125
+ // "baselineModels is not iterable"); empty = purely dynamic.
126
+ models: [],
127
+ auth: {
128
+ apiKey: {
129
+ name: "Ollama Cloud API key (auth.json or OLLAMA_API_KEY)",
130
+ resolve: async ({ credential, ctx }) => {
131
+ // Bridged auth.json credential (sigrun's bridge: the
132
+ // "default" entry first, then the provider's own) wins;
133
+ // the env var is the ambient fallback.
134
+ const stored = credential?.key?.trim() || undefined;
135
+ if (stored !== undefined) {
136
+ noteCredential(true);
137
+ return { auth: { apiKey: stored }, source: "auth.json" };
138
+ }
139
+ const envKey = (await ctx.env(OLLAMA_API_KEY_ENV))?.trim() || undefined;
140
+ if (envKey !== undefined) {
141
+ noteCredential(true);
142
+ return { auth: { apiKey: envKey }, source: OLLAMA_API_KEY_ENV };
143
+ }
144
+ // Unconfigured: undefined makes pi-ai skip the refresh
145
+ // phase entirely and requests fail loud.
146
+ noteCredential(false);
147
+ return undefined;
148
+ },
149
+ },
150
+ },
151
+ fetchModels: async (context) => {
152
+ // The context credential is whatever auth resolution produced —
153
+ // for this plugin always the merged api key; narrow rather than
154
+ // trust the union.
155
+ const key = context.credential?.type === "api_key"
156
+ ? context.credential.key?.trim() || undefined
157
+ : undefined;
158
+ if (key === undefined) {
159
+ noteCredential(false);
160
+ // NEVER return [] — the createProvider wrapper publishes +
161
+ // persists whatever fetchModels returns; an empty array
162
+ // would wipe the catalog.
163
+ throw new Error(`provider-ollama: Ollama Cloud is not configured ` +
164
+ `(no auth.json credential and no ${OLLAMA_API_KEY_ENV}); ` +
165
+ "refusing to publish an empty catalog");
166
+ }
167
+ const effectiveBase = context.baseUrl ?? baseUrl;
168
+ // URL-join needs the trailing slash ("models" against a
169
+ // slash-less base would resolve to the host root).
170
+ const root = effectiveBase.endsWith("/") ? effectiveBase : `${effectiveBase}/`;
171
+ try {
172
+ const response = await fetch(new URL("models", root), {
173
+ headers: { Authorization: `Bearer ${key}` },
174
+ signal: context.signal,
175
+ });
176
+ if (!response.ok) {
177
+ throw new Error(`HTTP ${response.status}`);
178
+ }
179
+ const models = parseOllamaCatalog(await response.json(), effectiveBase);
180
+ noteRefresh(true);
181
+ return models;
182
+ }
183
+ catch (cause) {
184
+ // An aborted refresh is not a failed one: pi-ai discards
185
+ // the outcome anyway, so the tracker must not move.
186
+ if (context.signal.aborted)
187
+ throw cause;
188
+ const message = cause instanceof Error ? cause.message : String(cause);
189
+ noteRefresh(false, message);
190
+ throw new Error(`provider-ollama: Ollama Cloud catalog refresh failed: ${message}`);
191
+ }
192
+ },
193
+ api: openAICompletionsApi(),
194
+ });
195
+ return {
196
+ provider,
197
+ readiness: () => ({
198
+ status: status(),
199
+ ...(lastRefreshDetail !== undefined && status() === "degraded"
200
+ ? { detail: lastRefreshDetail }
201
+ : {}),
202
+ }),
203
+ // not_configured → ok (an unconfigured optional plugin is not a
204
+ // degraded process); configured-and-failing → degraded; else ok.
205
+ health: () => (status() === "degraded" ? "degraded" : "ok"),
206
+ };
207
+ }
208
+ /**
209
+ * Resolve the plugin's base URL from its scoped settings: `baseUrl`
210
+ * (string) overrides the {@link OLLAMA_BASE_URL} constant. Absent → the
211
+ * constant; present-but-not-a-non-empty-string → throw (a malformed
212
+ * override must quarantine loudly, not silently fall back to a possibly
213
+ * wrong constant).
214
+ */
215
+ export function resolveOllamaBaseUrl(settings) {
216
+ if (!("baseUrl" in settings))
217
+ return OLLAMA_BASE_URL;
218
+ const value = settings.baseUrl;
219
+ if (typeof value !== "string" || value.trim().length === 0) {
220
+ throw new Error("provider-ollama: settings/provider-ollama.json baseUrl must be a non-empty string when present");
221
+ }
222
+ return value.trim();
223
+ }
224
+ /**
225
+ * The sigrun extension-package factory (provider-only contributor).
226
+ * Arity 1 declares the host-taking signature — sigrun's loader passes the
227
+ * seam host only to factories that declare one (`factory.length > 0`) —
228
+ * and the factory is ASYNC because the scoped settings store is
229
+ * async-only; sigrun's loader awaits factory results. The base URL is
230
+ * deployment-configurable through the scoped settings (`baseUrl` in
231
+ * `settings/provider-ollama.json`, constant fallback): deployments behind a
232
+ * proxy or mirror — and hermetic test fixtures — redirect the endpoint
233
+ * that way (restart-required, exactly like sigrun-pareto/sigrun-bifrost).
234
+ */
235
+ export default async function sigrunOllama(host) {
236
+ const settings = await host.stores.settings.get();
237
+ const plugin = createOllamaPlugin(resolveOllamaBaseUrl(settings));
238
+ return {
239
+ name: "provider-ollama",
240
+ providers: [{ provider: plugin.provider, readiness: plugin.readiness }],
241
+ health: plugin.health,
242
+ };
243
+ }
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
- "name": "@sigrun/provider-ollama",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@sigrun/provider-ollama",
3
+ "version": "0.1.0",
4
+ "description": "Reference sigrun provider plugin: Ollama Cloud (OpenAI-compatible /v1) 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-ollama && 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-ollama.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/sigrun-ai/sigrun-provider-ollama/issues"
41
+ },
42
+ "homepage": "https://github.com/sigrun-ai/sigrun-provider-ollama#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
+ }