@lihuu/dsh-ollama-cloud 0.1.2 → 0.2.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/README.md +56 -8
- package/dist/index.js +254 -37
- package/dist/index.js.map +7 -0
- package/lib/discovery.d.ts +36 -0
- package/lib/index.d.ts +61 -17
- package/package.json +6 -2
- package/src/adapter.ts +3 -1
- package/src/discovery.ts +252 -0
- package/src/index.ts +200 -62
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin tha
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
7
|
- Registers the `ollama-cloud-direct` provider route on the LLM seam
|
|
8
|
+
- Declares the route in the configurable-provider directory as a **dormant entry**: until configured, it is offered by the Models page's **添加提供方** select (like a pi-ai route), and configuring it turns it into a removable **ollama-cloud** row
|
|
9
|
+
- Installs the `llm-ollama-cloud` user-settings section, so base URL, model catalog, and defaults are editable on the page and take effect without a restart
|
|
10
|
+
- Answers the Models page's **fetch available models** action from the resolved catalog, or interrogates a drafted endpoint at `GET {baseURL}/models`
|
|
8
11
|
- Ships with a default model catalog: DeepSeek-V4-Flash (cloud), DeepSeek-V4-Pro (cloud), GLM-5.2 (cloud)
|
|
9
12
|
- Reads the API key through the harness credential seam, per request — a changed key takes effect without a restart
|
|
10
13
|
- Model ids without a `:cloud` suffix are sent as `id:cloud` (e.g. `deepseek-v4-flash` → `deepseek-v4-flash:cloud`)
|
|
@@ -28,13 +31,35 @@ dsh plugin --profile web add @lihuu/dsh-ollama-cloud
|
|
|
28
31
|
|
|
29
32
|
Restart the web service once after installing (bundle layers are composed at boot).
|
|
30
33
|
|
|
34
|
+
### Upgrading from 0.1.x
|
|
35
|
+
|
|
36
|
+
0.2.0 moves the configuration from section-level fields to a per-route profile (`providers.ollama-cloud-direct`), which is what makes the route appear in the Models page's **添加提供方** select. If you pinned config in `~/.dsh/cordis.patch.yml`, move it under the profile:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# 0.1.x # 0.2.0
|
|
40
|
+
- id: llm-ollama-cloud - id: llm-ollama-cloud
|
|
41
|
+
config: config:
|
|
42
|
+
apiKeyEnv: ... providers:
|
|
43
|
+
baseURL: ... ollama-cloud-direct:
|
|
44
|
+
models: ... apiKeyEnv: ...
|
|
45
|
+
baseURL: ...
|
|
46
|
+
models: ...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Easiest path: drop the `config:` block entirely (keep the bare row) and configure on the Models page after restarting.
|
|
50
|
+
|
|
31
51
|
## Configuration
|
|
32
52
|
|
|
33
53
|
### 1. Set the API key
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
After a restart, open **Settings → Models**, click **添加提供方**, and pick **ollama-cloud** from the select:
|
|
56
|
+
|
|
57
|
+
- Type the key and **保存** — it is stored write-only under the `OLLAMA_CLOUD_DIRECT_API_KEY` credential reference, and the profile is created. The row appears with a green key dot.
|
|
58
|
+
- Or save **without typing a key** — the profile resolves the conventional `OLLAMA_CLOUD_API_KEY` reference instead, so a key already set in the environment or the credentials file keeps working.
|
|
59
|
+
|
|
60
|
+
The key can also be set outside the page:
|
|
36
61
|
|
|
37
|
-
**
|
|
62
|
+
**Credentials file.** Add one line to `~/.dsh/.credentials.yaml`:
|
|
38
63
|
|
|
39
64
|
```yaml
|
|
40
65
|
version: 1
|
|
@@ -42,29 +67,52 @@ refs:
|
|
|
42
67
|
OLLAMA_CLOUD_API_KEY: ollama-xxxxxxxxxxxxxxxx
|
|
43
68
|
```
|
|
44
69
|
|
|
45
|
-
**
|
|
70
|
+
**Environment variable.** Set `OLLAMA_CLOUD_API_KEY` in the environment the harness process runs under (your shell profile, a launchd unit, a process manager, etc.):
|
|
46
71
|
|
|
47
72
|
```sh
|
|
48
73
|
export OLLAMA_CLOUD_API_KEY="ollama-xxxxxxxxxxxxxxxx"
|
|
49
74
|
```
|
|
50
75
|
|
|
76
|
+
While the route is dormant (no profile stored), the adapter already serves the default catalog through that reference — page-stored credentials win over the environment once a profile names one; both are read per request, so changing either never needs a restart.
|
|
77
|
+
|
|
51
78
|
### 2. Select the provider
|
|
52
79
|
|
|
53
80
|
Choose the `ollama-cloud-direct` provider for the model (for example in the model settings or the agent's provider configuration).
|
|
54
81
|
|
|
82
|
+
### Settings section
|
|
83
|
+
|
|
84
|
+
The section shape is one profile per route:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
llm-ollama-cloud:
|
|
88
|
+
providers:
|
|
89
|
+
ollama-cloud-direct:
|
|
90
|
+
apiKeyEnv: OLLAMA_CLOUD_API_KEY
|
|
91
|
+
baseURL: https://ollama.com/v1
|
|
92
|
+
models:
|
|
93
|
+
- id: deepseek-v4-flash
|
|
94
|
+
contextWindow: 800000
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The plugin's `cordis.yml` mount entry is the section's base layer: pinning the profile there presents the route as an already-configured row; leaving it out keeps it dormant in the add-provider select. Everything the profile allows (`apiKeyEnv`, `baseURL`, `thinking`, `reasoningEffort`, `maxTokens`, `defaultContextWindow`, `models`, `streamIdleTimeoutMs`, `retryPolicy`) is editable on the page or in `settings.yaml` — the user layer wins, and a change takes effect on the next request without a restart. Fields the curated editor does not show (retry policy, timeouts, thinking defaults) stay owned by the mount config and `settings.yaml`.
|
|
98
|
+
|
|
55
99
|
## Custom model catalog
|
|
56
100
|
|
|
57
|
-
The
|
|
101
|
+
The plugin resolves a default catalog when the profile names no `models` array. To pin one for the deployment, add it to the profile in `~/.dsh/cordis.patch.yml`:
|
|
58
102
|
|
|
59
103
|
```yaml
|
|
60
104
|
- id: llm-ollama-cloud
|
|
61
105
|
config:
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
106
|
+
providers:
|
|
107
|
+
ollama-cloud-direct:
|
|
108
|
+
models:
|
|
109
|
+
- id: deepseek-v4-flash
|
|
110
|
+
name: DeepSeek-V4-Flash
|
|
111
|
+
contextWindow: 800000
|
|
66
112
|
```
|
|
67
113
|
|
|
114
|
+
(Note: pinning the profile this way also presents the route as configured.) The Models page can override the catalog per deployment (自定义设置 → models); **reset** there hands the catalog back to the layer beneath.
|
|
115
|
+
|
|
68
116
|
## License
|
|
69
117
|
|
|
70
118
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { assertUsableApiKey, LlmError as LlmError6, resolveRetryPolicy, RetryPolicySchema } from "@deepseek-ai/dsh-llm";
|
|
2
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
3
|
-
import {
|
|
5
|
+
import { deepEqualJson, installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
6
|
|
|
5
7
|
// src/adapter.ts
|
|
6
8
|
import {
|
|
@@ -574,7 +576,7 @@ var OllamaAdapter = class extends LlmAdapter {
|
|
|
574
576
|
}
|
|
575
577
|
config;
|
|
576
578
|
providerInfo(provider) {
|
|
577
|
-
return { id: provider, name: "
|
|
579
|
+
return { id: provider, name: "ollama-cloud" };
|
|
578
580
|
}
|
|
579
581
|
providerRetryPolicy(_provider) {
|
|
580
582
|
return this.config.options().retryPolicy;
|
|
@@ -713,9 +715,152 @@ var OllamaAdapter = class extends LlmAdapter {
|
|
|
713
715
|
}
|
|
714
716
|
};
|
|
715
717
|
|
|
718
|
+
// src/discovery.ts
|
|
719
|
+
import { INVALID_CREDENTIAL_CODE, LlmError as LlmError5, normalizeApiKey } from "@deepseek-ai/dsh-llm";
|
|
720
|
+
import { attributionHeaders as attributionHeaders2 } from "@deepseek-ai/dsh-llm";
|
|
721
|
+
var MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
722
|
+
function capacity(...candidates) {
|
|
723
|
+
for (const candidate of candidates) {
|
|
724
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate > 0) return candidate;
|
|
725
|
+
}
|
|
726
|
+
return void 0;
|
|
727
|
+
}
|
|
728
|
+
function label(...candidates) {
|
|
729
|
+
for (const candidate of candidates) {
|
|
730
|
+
if (typeof candidate === "string" && candidate.length > 0) return candidate;
|
|
731
|
+
}
|
|
732
|
+
return void 0;
|
|
733
|
+
}
|
|
734
|
+
function listingUrl(baseURL) {
|
|
735
|
+
return `${baseURL.replace(/\/+$/, "")}/models`;
|
|
736
|
+
}
|
|
737
|
+
function usableProbeKey(raw) {
|
|
738
|
+
const checked = normalizeApiKey(raw);
|
|
739
|
+
if (checked.ok) return checked.value;
|
|
740
|
+
throw new LlmError5(
|
|
741
|
+
checked.reason === "empty" ? "this provider's API key is blank; enter it on the Models page, or clear it to probe unauthenticated" : "this provider's API key contains characters no HTTP header can carry; paste the raw key only",
|
|
742
|
+
INVALID_CREDENTIAL_CODE
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
async function readBounded(response, url) {
|
|
746
|
+
const oversized = () => new LlmError5(`${url} answered with more than ${MAX_RESPONSE_BYTES} bytes`, "DISCOVERY_FAILED");
|
|
747
|
+
const declared = Number(response.headers.get("content-length") ?? Number.NaN);
|
|
748
|
+
if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES) {
|
|
749
|
+
await response.body?.cancel();
|
|
750
|
+
throw oversized();
|
|
751
|
+
}
|
|
752
|
+
if (response.body === null) return "";
|
|
753
|
+
const reader = response.body.getReader();
|
|
754
|
+
const chunks = [];
|
|
755
|
+
let total = 0;
|
|
756
|
+
try {
|
|
757
|
+
for (; ; ) {
|
|
758
|
+
const { done, value } = await reader.read();
|
|
759
|
+
if (done) break;
|
|
760
|
+
total += value.byteLength;
|
|
761
|
+
if (total > MAX_RESPONSE_BYTES) throw oversized();
|
|
762
|
+
chunks.push(value);
|
|
763
|
+
}
|
|
764
|
+
} finally {
|
|
765
|
+
await reader.cancel().catch(() => {
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const body = new Uint8Array(total);
|
|
769
|
+
let offset = 0;
|
|
770
|
+
for (const chunk of chunks) {
|
|
771
|
+
body.set(chunk, offset);
|
|
772
|
+
offset += chunk.byteLength;
|
|
773
|
+
}
|
|
774
|
+
return new TextDecoder().decode(body);
|
|
775
|
+
}
|
|
776
|
+
function readListing(body) {
|
|
777
|
+
const data = body?.data;
|
|
778
|
+
if (!Array.isArray(data)) {
|
|
779
|
+
throw new LlmError5(
|
|
780
|
+
`the endpoint's model listing has no "data" array; enter this provider's models by hand`,
|
|
781
|
+
"DISCOVERY_FAILED"
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
const models = [];
|
|
785
|
+
for (const raw of data) {
|
|
786
|
+
const entry = raw;
|
|
787
|
+
const id = label(entry?.id);
|
|
788
|
+
if (id === void 0) continue;
|
|
789
|
+
const name2 = label(entry?.name, entry?.display_name);
|
|
790
|
+
const contextWindow = capacity(entry?.context_window, entry?.context_length);
|
|
791
|
+
const maxTokens = capacity(entry?.max_output_tokens, entry?.max_tokens);
|
|
792
|
+
models.push({
|
|
793
|
+
id,
|
|
794
|
+
...name2 === void 0 ? {} : { name: name2 },
|
|
795
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
796
|
+
...maxTokens === void 0 ? {} : { maxTokens }
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
return models;
|
|
800
|
+
}
|
|
801
|
+
async function discoverModels(request, installed, storedApiKey) {
|
|
802
|
+
if (request.provider !== void 0 && installed.length > 0) {
|
|
803
|
+
return installed.map((model) => ({
|
|
804
|
+
id: model.id,
|
|
805
|
+
name: model.name ?? model.id,
|
|
806
|
+
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
807
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
808
|
+
}));
|
|
809
|
+
}
|
|
810
|
+
if (request.baseURL === void 0 || request.baseURL.length === 0) {
|
|
811
|
+
throw new LlmError5(
|
|
812
|
+
"model discovery needs a baseURL to interrogate; set one, or enter this provider's models by hand",
|
|
813
|
+
"DISCOVERY_FAILED"
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
const url = listingUrl(request.baseURL);
|
|
817
|
+
const supplied = request.apiKey ?? await storedApiKey?.();
|
|
818
|
+
const apiKey = supplied === void 0 ? void 0 : usableProbeKey(supplied);
|
|
819
|
+
let response;
|
|
820
|
+
try {
|
|
821
|
+
response = await fetch(url, {
|
|
822
|
+
method: "GET",
|
|
823
|
+
headers: {
|
|
824
|
+
accept: "application/json",
|
|
825
|
+
...apiKey === void 0 ? {} : { authorization: `Bearer ${apiKey}` },
|
|
826
|
+
...attributionHeaders2()
|
|
827
|
+
},
|
|
828
|
+
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
829
|
+
});
|
|
830
|
+
} catch (error) {
|
|
831
|
+
if (request.signal?.aborted) {
|
|
832
|
+
throw new LlmError5("model discovery aborted by caller", "ABORTED", { cause: error });
|
|
833
|
+
}
|
|
834
|
+
throw new LlmError5(`could not reach ${url}`, "DISCOVERY_FAILED", { cause: error });
|
|
835
|
+
}
|
|
836
|
+
if (!response.ok) {
|
|
837
|
+
throw new LlmError5(
|
|
838
|
+
`${url} answered ${response.status}${response.status === 401 || response.status === 403 ? "; check the API key" : ""}`,
|
|
839
|
+
"DISCOVERY_FAILED"
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
let text;
|
|
843
|
+
try {
|
|
844
|
+
text = await readBounded(response, url);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
if (request.signal?.aborted) {
|
|
847
|
+
throw new LlmError5("model discovery aborted by caller", "ABORTED", { cause: error });
|
|
848
|
+
}
|
|
849
|
+
throw error;
|
|
850
|
+
}
|
|
851
|
+
let body;
|
|
852
|
+
try {
|
|
853
|
+
body = JSON.parse(text);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
throw new LlmError5(`${url} did not answer with JSON`, "DISCOVERY_FAILED", { cause: error });
|
|
856
|
+
}
|
|
857
|
+
return readListing(body);
|
|
858
|
+
}
|
|
859
|
+
|
|
716
860
|
// src/index.ts
|
|
717
861
|
var name = "llm-ollama-cloud";
|
|
718
862
|
var inject = ["llm"];
|
|
863
|
+
var NS = settingsNamespace("llm-ollama-cloud");
|
|
719
864
|
var DEFAULT_API_KEY_ENV = "OLLAMA_CLOUD_API_KEY";
|
|
720
865
|
var PROVIDER = "ollama-cloud-direct";
|
|
721
866
|
var DEFAULT_MODELS = [
|
|
@@ -724,6 +869,28 @@ var DEFAULT_MODELS = [
|
|
|
724
869
|
{ id: "glm-5.2:cloud", name: "GLM-5.2 (cloud)", contextWindow: DEFAULT_CONTEXT_WINDOW }
|
|
725
870
|
];
|
|
726
871
|
var MODEL_MODALITIES = ["text", "image"];
|
|
872
|
+
var catalogModel = z.object({
|
|
873
|
+
id: z.string().required(),
|
|
874
|
+
name: z.string(),
|
|
875
|
+
description: z.string(),
|
|
876
|
+
contextWindow: z.number().step(1).min(1),
|
|
877
|
+
maxTokens: z.number().step(1).min(1),
|
|
878
|
+
inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default(["text"])
|
|
879
|
+
});
|
|
880
|
+
var profileSchema = z.object({
|
|
881
|
+
apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
|
|
882
|
+
baseURL: z.string(),
|
|
883
|
+
thinking: z.union(["enabled", "disabled"]),
|
|
884
|
+
reasoningEffort: z.union(["off", "low", "high", "max"]),
|
|
885
|
+
maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_TOKENS),
|
|
886
|
+
defaultContextWindow: z.number().step(1).min(1).default(DEFAULT_CONTEXT_WINDOW),
|
|
887
|
+
models: z.array(catalogModel).default(DEFAULT_MODELS),
|
|
888
|
+
streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
889
|
+
retryPolicy: RetryPolicySchema
|
|
890
|
+
});
|
|
891
|
+
var Config = z.object({
|
|
892
|
+
providers: z.dict(profileSchema).default({})
|
|
893
|
+
});
|
|
727
894
|
var PUBLIC_BASE_URL = "https://ollama.com/v1";
|
|
728
895
|
function resolveModels(models) {
|
|
729
896
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -768,73 +935,123 @@ function resolveModels(models) {
|
|
|
768
935
|
});
|
|
769
936
|
}
|
|
770
937
|
function resolveAdapterOptions(config) {
|
|
771
|
-
|
|
938
|
+
return resolveProfileOptions(config.providers?.[PROVIDER]);
|
|
939
|
+
}
|
|
940
|
+
function resolveProfileOptions(profile) {
|
|
941
|
+
if (profile?.thinking === "disabled" && profile.reasoningEffort !== void 0 && profile.reasoningEffort !== "off") {
|
|
772
942
|
throw new Error('llm-ollama-cloud: only reasoningEffort "off" can be configured when thinking is disabled');
|
|
773
943
|
}
|
|
774
|
-
if (
|
|
944
|
+
if (profile?.defaultContextWindow !== void 0 && (!Number.isInteger(profile.defaultContextWindow) || profile.defaultContextWindow <= 0)) {
|
|
775
945
|
throw new Error("llm-ollama-cloud: defaultContextWindow must be a positive integer");
|
|
776
946
|
}
|
|
777
|
-
if (
|
|
947
|
+
if (profile?.maxTokens !== void 0 && (!Number.isSafeInteger(profile.maxTokens) || profile.maxTokens <= 0)) {
|
|
778
948
|
throw new Error("llm-ollama-cloud: maxTokens must be a positive safe integer");
|
|
779
949
|
}
|
|
780
|
-
const streamIdleTimeoutMs =
|
|
950
|
+
const streamIdleTimeoutMs = profile?.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
781
951
|
if (!Number.isFinite(streamIdleTimeoutMs) || streamIdleTimeoutMs <= 0 || streamIdleTimeoutMs > MAX_TIMER_DELAY_MS) {
|
|
782
952
|
throw new Error(
|
|
783
953
|
`llm-ollama-cloud: streamIdleTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`
|
|
784
954
|
);
|
|
785
955
|
}
|
|
786
956
|
return {
|
|
787
|
-
apiKeyEnv:
|
|
788
|
-
baseURL:
|
|
957
|
+
apiKeyEnv: credentialRef(profile?.apiKeyEnv ?? DEFAULT_API_KEY_ENV),
|
|
958
|
+
baseURL: profile?.baseURL ?? PUBLIC_BASE_URL,
|
|
789
959
|
defaults: {
|
|
790
|
-
thinking:
|
|
791
|
-
reasoningEffort:
|
|
960
|
+
thinking: profile?.thinking,
|
|
961
|
+
reasoningEffort: profile?.reasoningEffort
|
|
792
962
|
},
|
|
793
|
-
maxTokens:
|
|
794
|
-
defaultContextWindow:
|
|
795
|
-
models: resolveModels(
|
|
963
|
+
maxTokens: profile?.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
964
|
+
defaultContextWindow: profile?.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
965
|
+
models: resolveModels(profile?.models),
|
|
796
966
|
streamIdleTimeoutMs,
|
|
797
|
-
retryPolicy: resolveRetryPolicy(
|
|
967
|
+
retryPolicy: resolveRetryPolicy(profile?.retryPolicy, "llm-ollama-cloud: retryPolicy")
|
|
798
968
|
};
|
|
799
969
|
}
|
|
800
970
|
function apply(ctx, config = {}) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
971
|
+
let current = () => config;
|
|
972
|
+
let lastRaw;
|
|
973
|
+
let lastGood;
|
|
974
|
+
const options = () => {
|
|
975
|
+
const raw = current();
|
|
976
|
+
if (raw === lastRaw && lastGood !== void 0) return lastGood;
|
|
977
|
+
try {
|
|
978
|
+
const next = resolveAdapterOptions(raw);
|
|
979
|
+
lastRaw = raw;
|
|
980
|
+
lastGood = next;
|
|
981
|
+
return next;
|
|
982
|
+
} catch (error) {
|
|
983
|
+
if (lastGood === void 0) throw error;
|
|
984
|
+
lastRaw = raw;
|
|
985
|
+
ctx.logger.error("llm-ollama-cloud: keeping the last good configuration after an invalid settings section");
|
|
986
|
+
ctx.logger.error(error);
|
|
987
|
+
return lastGood;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
options();
|
|
991
|
+
const resolveApiKey = async (connection) => {
|
|
992
|
+
const ref = connection.apiKeyEnv;
|
|
993
|
+
const credentials = ctx.get("credentials");
|
|
994
|
+
if (credentials !== void 0) {
|
|
995
|
+
const hit = await credentials.resolve(ref);
|
|
996
|
+
if (hit !== void 0 && hit.value.length > 0) {
|
|
997
|
+
return assertUsableApiKey(hit.value, "llm-ollama-cloud", ref);
|
|
818
998
|
}
|
|
819
|
-
throw new LlmError5(
|
|
820
|
-
`llm-ollama-cloud: no API key for provider route "${PROVIDER}"; store ${ref} in the credentials file or export it in the launching environment`,
|
|
821
|
-
"MISSING_CREDENTIAL"
|
|
822
|
-
);
|
|
823
999
|
}
|
|
1000
|
+
const ambient = process.env[ref];
|
|
1001
|
+
if (ambient !== void 0 && ambient.length > 0) {
|
|
1002
|
+
return assertUsableApiKey(ambient, "llm-ollama-cloud", ref);
|
|
1003
|
+
}
|
|
1004
|
+
throw new LlmError6(
|
|
1005
|
+
`llm-ollama-cloud: no API key for provider route "${PROVIDER}"; store ${ref} through the credentials service (the web Models page writes it), or export ${ref} in the launching environment`,
|
|
1006
|
+
"MISSING_CREDENTIAL"
|
|
1007
|
+
);
|
|
1008
|
+
};
|
|
1009
|
+
const storedApiKey = async () => {
|
|
1010
|
+
const ref = options().apiKeyEnv;
|
|
1011
|
+
const credentials = ctx.get("credentials");
|
|
1012
|
+
const hit = credentials !== void 0 ? (await credentials.resolve(ref))?.value : void 0;
|
|
1013
|
+
const value = hit !== void 0 && hit.length > 0 ? hit : process.env[ref];
|
|
1014
|
+
return value !== void 0 && value.length > 0 ? value : void 0;
|
|
1015
|
+
};
|
|
1016
|
+
const adapter = new OllamaAdapter({ options, resolveApiKey });
|
|
1017
|
+
ctx.llm.registerConfigurableProviders([
|
|
1018
|
+
{ provider: PROVIDER, displayName: "ollama-cloud", settingsNs: NS, settingsPath: ["providers", PROVIDER] }
|
|
1019
|
+
]);
|
|
1020
|
+
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
1021
|
+
let registeredPolicy = options().retryPolicy;
|
|
1022
|
+
const ensureRegistrationFacts = () => {
|
|
1023
|
+
const policy = options().retryPolicy;
|
|
1024
|
+
if (deepEqualJson(policy, registeredPolicy)) return;
|
|
1025
|
+
registration.replace([PROVIDER]);
|
|
1026
|
+
registeredPolicy = policy;
|
|
1027
|
+
};
|
|
1028
|
+
ctx.llm.registerModelDiscovery(NS, (request, signal) => discoverModels(
|
|
1029
|
+
{ ...request, ...signal === void 0 ? {} : { signal } },
|
|
1030
|
+
options().models,
|
|
1031
|
+
storedApiKey
|
|
1032
|
+
));
|
|
1033
|
+
installSettingsSection(ctx, NS, Config, config, {
|
|
1034
|
+
setSource: (source) => {
|
|
1035
|
+
current = source;
|
|
1036
|
+
},
|
|
1037
|
+
onChange: ensureRegistrationFacts
|
|
824
1038
|
});
|
|
825
|
-
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
826
1039
|
}
|
|
827
1040
|
export {
|
|
1041
|
+
Config,
|
|
828
1042
|
DEFAULT_CONTEXT_WINDOW,
|
|
829
1043
|
DEFAULT_MAX_TOKENS,
|
|
830
1044
|
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
831
1045
|
MAX_TIMER_DELAY_MS,
|
|
832
1046
|
OllamaAdapter,
|
|
1047
|
+
PROVIDER,
|
|
833
1048
|
PUBLIC_BASE_URL,
|
|
834
1049
|
apply,
|
|
1050
|
+
discoverModels,
|
|
835
1051
|
inject,
|
|
836
1052
|
name,
|
|
837
1053
|
normalizeCloud,
|
|
838
|
-
resolveAdapterOptions
|
|
1054
|
+
resolveAdapterOptions,
|
|
1055
|
+
resolveProfileOptions
|
|
839
1056
|
};
|
|
840
1057
|
//# sourceMappingURL=index.js.map
|