@fideliosai/adapter-ollama-local 0.0.40

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/format-event.d.ts +8 -0
  3. package/dist/cli/format-event.d.ts.map +1 -0
  4. package/dist/cli/format-event.js +22 -0
  5. package/dist/cli/format-event.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +2 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +68 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/server/config.d.ts +32 -0
  15. package/dist/server/config.d.ts.map +1 -0
  16. package/dist/server/config.js +110 -0
  17. package/dist/server/config.js.map +1 -0
  18. package/dist/server/config.test.d.ts +2 -0
  19. package/dist/server/config.test.d.ts.map +1 -0
  20. package/dist/server/config.test.js +85 -0
  21. package/dist/server/config.test.js.map +1 -0
  22. package/dist/server/execute.d.ts +3 -0
  23. package/dist/server/execute.d.ts.map +1 -0
  24. package/dist/server/execute.js +267 -0
  25. package/dist/server/execute.js.map +1 -0
  26. package/dist/server/index.d.ts +8 -0
  27. package/dist/server/index.d.ts.map +1 -0
  28. package/dist/server/index.js +6 -0
  29. package/dist/server/index.js.map +1 -0
  30. package/dist/server/models.d.ts +19 -0
  31. package/dist/server/models.d.ts.map +1 -0
  32. package/dist/server/models.js +107 -0
  33. package/dist/server/models.js.map +1 -0
  34. package/dist/server/models.test.d.ts +2 -0
  35. package/dist/server/models.test.d.ts.map +1 -0
  36. package/dist/server/models.test.js +177 -0
  37. package/dist/server/models.test.js.map +1 -0
  38. package/dist/server/session-codec.d.ts +21 -0
  39. package/dist/server/session-codec.d.ts.map +1 -0
  40. package/dist/server/session-codec.js +72 -0
  41. package/dist/server/session-codec.js.map +1 -0
  42. package/dist/server/session-codec.test.d.ts +2 -0
  43. package/dist/server/session-codec.test.d.ts.map +1 -0
  44. package/dist/server/session-codec.test.js +86 -0
  45. package/dist/server/session-codec.test.js.map +1 -0
  46. package/dist/server/test.d.ts +13 -0
  47. package/dist/server/test.d.ts.map +1 -0
  48. package/dist/server/test.js +243 -0
  49. package/dist/server/test.js.map +1 -0
  50. package/dist/server/test.test.d.ts +2 -0
  51. package/dist/server/test.test.d.ts.map +1 -0
  52. package/dist/server/test.test.js +158 -0
  53. package/dist/server/test.test.js.map +1 -0
  54. package/dist/ui/build-config.d.ts +14 -0
  55. package/dist/ui/build-config.d.ts.map +1 -0
  56. package/dist/ui/build-config.js +102 -0
  57. package/dist/ui/build-config.js.map +1 -0
  58. package/dist/ui/index.d.ts +3 -0
  59. package/dist/ui/index.d.ts.map +1 -0
  60. package/dist/ui/index.js +3 -0
  61. package/dist/ui/index.js.map +1 -0
  62. package/dist/ui/parse-stdout.d.ts +9 -0
  63. package/dist/ui/parse-stdout.d.ts.map +1 -0
  64. package/dist/ui/parse-stdout.js +23 -0
  65. package/dist/ui/parse-stdout.js.map +1 -0
  66. package/package.json +57 -0
@@ -0,0 +1,21 @@
1
+ import type { AdapterSessionCodec } from "@fideliosai/adapter-utils";
2
+ /**
3
+ * One chat turn as the Ollama SDK expects.
4
+ * Kept loose on purpose so we can serialize even when the model returned
5
+ * unexpected fields (tool_calls, thinking, etc.) — the SDK will ignore
6
+ * unknown keys.
7
+ */
8
+ export interface OllamaChatMessage {
9
+ role: "system" | "user" | "assistant" | "tool";
10
+ content: string;
11
+ [key: string]: unknown;
12
+ }
13
+ export interface OllamaSessionParams {
14
+ /** Stable id assigned by the adapter on first execute(); used as displayId. */
15
+ conversationId: string;
16
+ /** Full prior chat history. The next execute() prepends this before the new user turn. */
17
+ messages: OllamaChatMessage[];
18
+ }
19
+ export declare const sessionCodec: AdapterSessionCodec;
20
+ export declare function newConversationId(): string;
21
+ //# sourceMappingURL=session-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-codec.d.ts","sourceRoot":"","sources":["../../src/server/session-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAEhB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA+BD,eAAO,MAAM,YAAY,EAAE,mBAkC1B,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C"}
@@ -0,0 +1,72 @@
1
+ function isMessageLike(value) {
2
+ if (typeof value !== "object" || value === null || Array.isArray(value))
3
+ return false;
4
+ const rec = value;
5
+ if (typeof rec.role !== "string")
6
+ return false;
7
+ if (rec.role !== "system" && rec.role !== "user" && rec.role !== "assistant" && rec.role !== "tool") {
8
+ return false;
9
+ }
10
+ // content may be string or omitted (e.g., tool_calls only) — be permissive.
11
+ if (typeof rec.content !== "string" && rec.content !== undefined)
12
+ return false;
13
+ return true;
14
+ }
15
+ function readNonEmptyString(value) {
16
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
17
+ }
18
+ function normalizeMessages(raw) {
19
+ if (!Array.isArray(raw))
20
+ return [];
21
+ const out = [];
22
+ for (const entry of raw) {
23
+ if (!isMessageLike(entry))
24
+ continue;
25
+ out.push({
26
+ ...entry,
27
+ content: typeof entry.content === "string" ? entry.content : "",
28
+ });
29
+ }
30
+ return out;
31
+ }
32
+ export const sessionCodec = {
33
+ deserialize(raw) {
34
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
35
+ return null;
36
+ const rec = raw;
37
+ const conversationId = readNonEmptyString(rec.conversationId) ??
38
+ readNonEmptyString(rec.sessionId) ??
39
+ readNonEmptyString(rec.session_id);
40
+ if (!conversationId)
41
+ return null;
42
+ return {
43
+ conversationId,
44
+ messages: normalizeMessages(rec.messages),
45
+ };
46
+ },
47
+ serialize(params) {
48
+ if (!params)
49
+ return null;
50
+ const conversationId = readNonEmptyString(params.conversationId) ??
51
+ readNonEmptyString(params.sessionId) ??
52
+ readNonEmptyString(params.session_id);
53
+ if (!conversationId)
54
+ return null;
55
+ return {
56
+ conversationId,
57
+ messages: normalizeMessages(params.messages),
58
+ };
59
+ },
60
+ getDisplayId(params) {
61
+ if (!params)
62
+ return null;
63
+ return (readNonEmptyString(params.conversationId) ??
64
+ readNonEmptyString(params.sessionId) ??
65
+ readNonEmptyString(params.session_id));
66
+ },
67
+ };
68
+ export function newConversationId() {
69
+ // Lightweight non-cryptographic id; collision-free enough for log labels.
70
+ return `ollama-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
71
+ }
72
+ //# sourceMappingURL=session-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-codec.js","sourceRoot":"","sources":["../../src/server/session-codec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-codec.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-codec.test.d.ts","sourceRoot":"","sources":["../../src/server/session-codec.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { newConversationId, sessionCodec } from "./session-codec.js";
3
+ describe("ollama_local sessionCodec", () => {
4
+ it("returns null for null/non-object inputs", () => {
5
+ expect(sessionCodec.deserialize(null)).toBeNull();
6
+ expect(sessionCodec.deserialize("string")).toBeNull();
7
+ expect(sessionCodec.deserialize(["a"])).toBeNull();
8
+ expect(sessionCodec.serialize(null)).toBeNull();
9
+ });
10
+ it("requires a non-empty conversationId", () => {
11
+ expect(sessionCodec.deserialize({ messages: [] })).toBeNull();
12
+ expect(sessionCodec.deserialize({ conversationId: "" })).toBeNull();
13
+ });
14
+ it("accepts legacy sessionId / session_id field names", () => {
15
+ expect(sessionCodec.deserialize({ sessionId: "abc" })).toEqual({
16
+ conversationId: "abc",
17
+ messages: [],
18
+ });
19
+ expect(sessionCodec.deserialize({ session_id: "abc" })).toEqual({
20
+ conversationId: "abc",
21
+ messages: [],
22
+ });
23
+ });
24
+ it("round-trips a conversation with mixed roles", () => {
25
+ const params = {
26
+ conversationId: "ollama-deadbeef",
27
+ messages: [
28
+ { role: "system", content: "you are X" },
29
+ { role: "user", content: "hi" },
30
+ { role: "assistant", content: "hello" },
31
+ ],
32
+ };
33
+ const decoded = sessionCodec.deserialize(params);
34
+ expect(decoded).toEqual(params);
35
+ const encoded = sessionCodec.serialize(decoded);
36
+ expect(encoded).toEqual(params);
37
+ });
38
+ it("drops malformed message entries", () => {
39
+ const decoded = sessionCodec.deserialize({
40
+ conversationId: "x",
41
+ messages: [
42
+ { role: "user", content: "kept" },
43
+ { role: "alien", content: "dropped" },
44
+ "not-an-object",
45
+ null,
46
+ { role: "assistant" }, // content optional → kept, content normalized to ""
47
+ ],
48
+ });
49
+ expect(decoded?.messages).toEqual([
50
+ { role: "user", content: "kept" },
51
+ { role: "assistant", content: "" },
52
+ ]);
53
+ });
54
+ it("preserves auxiliary keys like tool_calls when round-tripping", () => {
55
+ const tooled = {
56
+ conversationId: "x",
57
+ messages: [
58
+ {
59
+ role: "assistant",
60
+ content: "",
61
+ tool_calls: [{ function: { name: "f", arguments: "{}" } }],
62
+ },
63
+ ],
64
+ };
65
+ const decoded = sessionCodec.deserialize(tooled);
66
+ const messages = decoded?.messages;
67
+ expect(messages[0]?.tool_calls).toEqual([
68
+ { function: { name: "f", arguments: "{}" } },
69
+ ]);
70
+ });
71
+ it("getDisplayId returns the conversationId", () => {
72
+ expect(sessionCodec.getDisplayId?.({ conversationId: "abc", messages: [] })).toBe("abc");
73
+ expect(sessionCodec.getDisplayId?.({ sessionId: "legacy", messages: [] })).toBe("legacy");
74
+ expect(sessionCodec.getDisplayId?.(null)).toBeNull();
75
+ });
76
+ });
77
+ describe("newConversationId", () => {
78
+ it("returns a unique id with the ollama- prefix", () => {
79
+ const a = newConversationId();
80
+ const b = newConversationId();
81
+ expect(a).toMatch(/^ollama-/);
82
+ expect(b).toMatch(/^ollama-/);
83
+ expect(a).not.toBe(b);
84
+ });
85
+ });
86
+ //# sourceMappingURL=session-codec.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-codec.test.js","sourceRoot":"","sources":["../../src/server/session-codec.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { AdapterEnvironmentTestContext, AdapterEnvironmentTestResult } from "@fideliosai/adapter-utils";
2
+ import { Ollama } from "ollama";
3
+ /**
4
+ * Pattern that flags responses indicating an auth/license/quota gate
5
+ * rather than a daemon outage. Mirrors PR #44 hermes-local parity.
6
+ */
7
+ export declare const isOllamaAuthRequiredText: (text: string) => boolean;
8
+ interface TestEnvironmentDeps {
9
+ ollamaCtor?: typeof Ollama;
10
+ }
11
+ export declare function testEnvironment(ctx: AdapterEnvironmentTestContext, deps?: TestEnvironmentDeps): Promise<AdapterEnvironmentTestResult>;
12
+ export {};
13
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/server/test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,6BAA6B,EAC7B,4BAA4B,EAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAShC;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,OAGrD,CAAC;AAoBJ,UAAU,mBAAmB;IAC3B,UAAU,CAAC,EAAE,OAAO,MAAM,CAAC;CAC5B;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,6BAA6B,EAClC,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,4BAA4B,CAAC,CAuNvC"}
@@ -0,0 +1,243 @@
1
+ import { Ollama } from "ollama";
2
+ import { buildOllamaHeaders, isCloudHost, parseOllamaConfig, } from "./config.js";
3
+ import { discoverOllamaModelsCached } from "./models.js";
4
+ /**
5
+ * Pattern that flags responses indicating an auth/license/quota gate
6
+ * rather than a daemon outage. Mirrors PR #44 hermes-local parity.
7
+ */
8
+ export const isOllamaAuthRequiredText = (text) => /(?:unauthor(?:ized|ised)|forbidden|invalid\s*api\s*key|api\s*key|missing\s*token|please\s+sign\s+in|payment\s*required|insufficient\s*credit|quota\s*exceeded|rate\s*limit)/i.test(text);
9
+ function summarizeStatus(checks) {
10
+ if (checks.some((c) => c.level === "error"))
11
+ return "fail";
12
+ if (checks.some((c) => c.level === "warn"))
13
+ return "warn";
14
+ return "pass";
15
+ }
16
+ function truncate(text, max = 240) {
17
+ const clean = text.replace(/\s+/g, " ").trim();
18
+ return clean.length > max ? `${clean.slice(0, max - 1)}...` : clean;
19
+ }
20
+ function buildClient(cfg, ctor = Ollama) {
21
+ const headers = buildOllamaHeaders(cfg.apiKey);
22
+ return new ctor({ host: cfg.host, ...(headers ? { headers } : {}) });
23
+ }
24
+ export async function testEnvironment(ctx, deps = {}) {
25
+ const checks = [];
26
+ let cfg;
27
+ try {
28
+ cfg = parseOllamaConfig(ctx.config);
29
+ }
30
+ catch (err) {
31
+ checks.push({
32
+ code: "ollama_config_invalid",
33
+ level: "error",
34
+ message: err instanceof Error ? err.message : "Invalid ollama_local config.",
35
+ hint: "Set adapterConfig.model to a model id from `/api/tags` (e.g. llama3.1).",
36
+ });
37
+ return {
38
+ adapterType: ctx.adapterType,
39
+ status: summarizeStatus(checks),
40
+ checks,
41
+ testedAt: new Date().toISOString(),
42
+ };
43
+ }
44
+ const Ctor = deps.ollamaCtor ?? Ollama;
45
+ const client = buildClient(cfg, Ctor);
46
+ // Surface tier verbatim (no public Ollama API to verify it).
47
+ if (cfg.ollamaTier) {
48
+ checks.push({
49
+ code: "ollama_tier_configured",
50
+ level: "info",
51
+ message: `Configured Ollama tier: ${cfg.ollamaTier}`,
52
+ hint: "There is no public Ollama API to verify a tier — this value is documentation only.",
53
+ });
54
+ }
55
+ // 1) /api/version
56
+ let versionOk = false;
57
+ try {
58
+ const v = (await client.version());
59
+ if (v?.version) {
60
+ versionOk = true;
61
+ checks.push({
62
+ code: "ollama_version_ok",
63
+ level: "info",
64
+ message: `Ollama daemon reachable at ${cfg.host} (version ${v.version}).`,
65
+ });
66
+ }
67
+ else {
68
+ checks.push({
69
+ code: "ollama_version_unexpected",
70
+ level: "warn",
71
+ message: `Ollama /api/version at ${cfg.host} returned no version string.`,
72
+ });
73
+ }
74
+ }
75
+ catch (err) {
76
+ const msg = err instanceof Error ? err.message : String(err);
77
+ if (isOllamaAuthRequiredText(msg)) {
78
+ checks.push({
79
+ code: "ollama_version_auth_required",
80
+ level: "warn",
81
+ message: `Ollama at ${cfg.host} requires authentication.`,
82
+ detail: truncate(msg),
83
+ hint: "Set OLLAMA_API_KEY in adapterConfig.env or the agent secret store.",
84
+ });
85
+ }
86
+ else {
87
+ checks.push({
88
+ code: "ollama_version_unreachable",
89
+ level: "error",
90
+ message: `Ollama daemon at ${cfg.host} is unreachable.`,
91
+ detail: truncate(msg),
92
+ hint: isCloudHost(cfg.host)
93
+ ? "Check OLLAMA_API_KEY and your network access to ollama.com."
94
+ : "Start the daemon with `ollama serve` or update `host` to point at a running instance.",
95
+ });
96
+ }
97
+ }
98
+ // 2) /api/tags
99
+ let discovered = [];
100
+ if (versionOk) {
101
+ try {
102
+ discovered = await discoverOllamaModelsCached({
103
+ host: cfg.host,
104
+ apiKey: cfg.apiKey,
105
+ ollamaCtor: Ctor,
106
+ });
107
+ if (discovered.length === 0) {
108
+ checks.push({
109
+ code: "ollama_models_empty",
110
+ level: "warn",
111
+ message: "Ollama returned no models.",
112
+ hint: isCloudHost(cfg.host)
113
+ ? "Verify your OLLAMA_API_KEY can access cloud models."
114
+ : "Pull a model with `ollama pull <model>` and retry.",
115
+ });
116
+ }
117
+ else {
118
+ checks.push({
119
+ code: "ollama_models_discovered",
120
+ level: "info",
121
+ message: `Discovered ${discovered.length} model(s) from ${cfg.host}.`,
122
+ });
123
+ }
124
+ }
125
+ catch (err) {
126
+ const msg = err instanceof Error ? err.message : String(err);
127
+ if (isOllamaAuthRequiredText(msg)) {
128
+ checks.push({
129
+ code: "ollama_tags_auth_required",
130
+ level: "warn",
131
+ message: "Ollama /api/tags requires authentication.",
132
+ detail: truncate(msg),
133
+ hint: "Set OLLAMA_API_KEY in adapterConfig.env.",
134
+ });
135
+ }
136
+ else {
137
+ checks.push({
138
+ code: "ollama_tags_failed",
139
+ level: "warn",
140
+ message: "Ollama /api/tags discovery failed.",
141
+ detail: truncate(msg),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ // Verify configured model is available (when we have a list).
147
+ if (discovered.length > 0) {
148
+ if (discovered.some((m) => m.id === cfg.model)) {
149
+ checks.push({
150
+ code: "ollama_model_configured",
151
+ level: "info",
152
+ message: `Configured model is available: ${cfg.model}`,
153
+ });
154
+ }
155
+ else {
156
+ const sample = discovered.slice(0, 8).map((m) => m.id).join(", ");
157
+ checks.push({
158
+ code: "ollama_model_not_found",
159
+ level: "warn",
160
+ message: `Configured model "${cfg.model}" is not in /api/tags.`,
161
+ detail: `Available: ${sample}${discovered.length > 8 ? ", ..." : ""}`,
162
+ hint: "Pull the model or pick one from /api/tags.",
163
+ });
164
+ }
165
+ }
166
+ // 3) Hello probe — only when daemon is reachable. Mirrors PR #44 parity.
167
+ if (versionOk) {
168
+ try {
169
+ const stream = (await client.chat({
170
+ model: cfg.model,
171
+ messages: [{ role: "user", content: "Respond with the single word: hello" }],
172
+ stream: true,
173
+ ...(cfg.keepAlive !== null ? { keep_alive: cfg.keepAlive } : {}),
174
+ }));
175
+ let text = "";
176
+ for await (const part of stream) {
177
+ if (part.message?.content)
178
+ text += part.message.content;
179
+ if (text.length > 256)
180
+ break;
181
+ }
182
+ if (/\bhello\b/i.test(text)) {
183
+ checks.push({
184
+ code: "ollama_hello_probe_passed",
185
+ level: "info",
186
+ message: "Ollama hello probe succeeded.",
187
+ detail: truncate(text),
188
+ });
189
+ }
190
+ else if (text.trim().length > 0) {
191
+ checks.push({
192
+ code: "ollama_hello_probe_unexpected_output",
193
+ level: "warn",
194
+ message: "Ollama probe ran but did not return `hello` as expected.",
195
+ detail: truncate(text),
196
+ });
197
+ }
198
+ else {
199
+ checks.push({
200
+ code: "ollama_hello_probe_empty",
201
+ level: "warn",
202
+ message: "Ollama probe returned no content.",
203
+ hint: "Run `ollama run <model>` manually to confirm the model is loaded.",
204
+ });
205
+ }
206
+ }
207
+ catch (err) {
208
+ const msg = err instanceof Error ? err.message : String(err);
209
+ if (isOllamaAuthRequiredText(msg)) {
210
+ checks.push({
211
+ code: "ollama_hello_probe_auth_required",
212
+ level: "warn",
213
+ message: "Ollama daemon is reachable, but provider authentication is not ready.",
214
+ detail: truncate(msg),
215
+ hint: "Set OLLAMA_API_KEY for cloud models or verify the local daemon's access policy.",
216
+ });
217
+ }
218
+ else {
219
+ checks.push({
220
+ code: "ollama_hello_probe_failed",
221
+ level: "warn",
222
+ message: "Ollama hello probe failed.",
223
+ detail: truncate(msg),
224
+ hint: `Run \`ollama run ${cfg.model}\` manually against ${cfg.host} to debug.`,
225
+ });
226
+ }
227
+ }
228
+ }
229
+ else {
230
+ checks.push({
231
+ code: "ollama_hello_probe_skipped",
232
+ level: "warn",
233
+ message: "Skipped Ollama hello probe because /api/version did not respond.",
234
+ });
235
+ }
236
+ return {
237
+ adapterType: ctx.adapterType,
238
+ status: summarizeStatus(checks),
239
+ checks,
240
+ testedAt: new Date().toISOString(),
241
+ };
242
+ }
243
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/server/test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.test.d.ts","sourceRoot":"","sources":["../../src/server/test.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,158 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { isOllamaAuthRequiredText, testEnvironment } from "./test.js";
3
+ import { resetOllamaModelsCacheForTests } from "./models.js";
4
+ class FakeOllama {
5
+ static behaviors = new Map();
6
+ host;
7
+ headers;
8
+ constructor(opts) {
9
+ this.host = opts.host;
10
+ this.headers = opts.headers;
11
+ }
12
+ async version() {
13
+ const b = FakeOllama.behaviors.get(this.host);
14
+ if (!b?.version)
15
+ return { version: "0.0.0" };
16
+ return b.version();
17
+ }
18
+ async list() {
19
+ const b = FakeOllama.behaviors.get(this.host);
20
+ if (!b?.list)
21
+ return { models: [] };
22
+ return b.list();
23
+ }
24
+ async chat() {
25
+ const b = FakeOllama.behaviors.get(this.host);
26
+ if (!b?.chat) {
27
+ async function* empty() { }
28
+ return empty();
29
+ }
30
+ return b.chat();
31
+ }
32
+ }
33
+ afterEach(() => {
34
+ FakeOllama.behaviors.clear();
35
+ resetOllamaModelsCacheForTests();
36
+ });
37
+ async function* helloStream() {
38
+ yield { message: { content: "hello" } };
39
+ }
40
+ const ctor = FakeOllama;
41
+ describe("isOllamaAuthRequiredText", () => {
42
+ it.each([
43
+ "401 Unauthorized",
44
+ "Forbidden",
45
+ "Invalid api key",
46
+ "missing token",
47
+ "Please sign in to continue",
48
+ "rate limit exceeded",
49
+ "Quota exceeded",
50
+ "payment required",
51
+ ])("flags %s as auth/quota gate", (text) => {
52
+ expect(isOllamaAuthRequiredText(text)).toBe(true);
53
+ });
54
+ it.each(["ECONNREFUSED", "Connection reset", "model not found"])("ignores generic transport errors like %s", (text) => {
55
+ expect(isOllamaAuthRequiredText(text)).toBe(false);
56
+ });
57
+ });
58
+ describe("testEnvironment", () => {
59
+ it("fails fast when config is invalid", async () => {
60
+ const result = await testEnvironment({ adapterType: "ollama_local", companyId: "c", config: {} }, { ollamaCtor: ctor });
61
+ expect(result.status).toBe("fail");
62
+ const code = result.checks[0]?.code;
63
+ expect(code).toBe("ollama_config_invalid");
64
+ });
65
+ it("returns pass when version, tags, and hello probe all succeed", async () => {
66
+ FakeOllama.behaviors.set("http://localhost:11434", {
67
+ version: () => ({ version: "0.5.0" }),
68
+ list: () => ({ models: [{ name: "llama3.1" }] }),
69
+ chat: () => helloStream(),
70
+ });
71
+ const result = await testEnvironment({
72
+ adapterType: "ollama_local",
73
+ companyId: "c",
74
+ config: { model: "llama3.1" },
75
+ }, { ollamaCtor: ctor });
76
+ expect(result.status).toBe("pass");
77
+ const codes = result.checks.map((c) => c.code);
78
+ expect(codes).toContain("ollama_version_ok");
79
+ expect(codes).toContain("ollama_models_discovered");
80
+ expect(codes).toContain("ollama_model_configured");
81
+ expect(codes).toContain("ollama_hello_probe_passed");
82
+ });
83
+ it("flags configured tier as informational", async () => {
84
+ FakeOllama.behaviors.set("http://localhost:11434", {
85
+ version: () => ({ version: "0.5.0" }),
86
+ list: () => ({ models: [{ name: "llama3.1" }] }),
87
+ chat: () => helloStream(),
88
+ });
89
+ const result = await testEnvironment({
90
+ adapterType: "ollama_local",
91
+ companyId: "c",
92
+ config: { model: "llama3.1", ollamaTier: "pro" },
93
+ }, { ollamaCtor: ctor });
94
+ const tierCheck = result.checks.find((c) => c.code === "ollama_tier_configured");
95
+ expect(tierCheck?.message).toMatch(/pro/);
96
+ expect(tierCheck?.hint).toMatch(/no public Ollama API/i);
97
+ });
98
+ it("warns when configured model is missing from /api/tags", async () => {
99
+ FakeOllama.behaviors.set("http://localhost:11434", {
100
+ version: () => ({ version: "0.5.0" }),
101
+ list: () => ({ models: [{ name: "qwen" }] }),
102
+ chat: () => helloStream(),
103
+ });
104
+ const result = await testEnvironment({
105
+ adapterType: "ollama_local",
106
+ companyId: "c",
107
+ config: { model: "missing-model" },
108
+ }, { ollamaCtor: ctor });
109
+ expect(result.status).toBe("warn");
110
+ expect(result.checks.find((c) => c.code === "ollama_model_not_found")).toBeTruthy();
111
+ });
112
+ it("flags auth-required errors instead of marking the daemon unreachable", async () => {
113
+ FakeOllama.behaviors.set("https://ollama.com", {
114
+ version: () => {
115
+ throw new Error("401 Unauthorized — invalid api key");
116
+ },
117
+ });
118
+ const result = await testEnvironment({
119
+ adapterType: "ollama_local",
120
+ companyId: "c",
121
+ config: { model: "kimi-k2.6:cloud", host: "https://ollama.com" },
122
+ }, { ollamaCtor: ctor });
123
+ expect(result.checks.find((c) => c.code === "ollama_version_auth_required")).toBeTruthy();
124
+ });
125
+ it("returns fail when the daemon is unreachable", async () => {
126
+ FakeOllama.behaviors.set("http://localhost:11434", {
127
+ version: () => {
128
+ throw new Error("ECONNREFUSED");
129
+ },
130
+ });
131
+ const result = await testEnvironment({
132
+ adapterType: "ollama_local",
133
+ companyId: "c",
134
+ config: { model: "llama3.1" },
135
+ }, { ollamaCtor: ctor });
136
+ expect(result.status).toBe("fail");
137
+ const versionCheck = result.checks.find((c) => c.code === "ollama_version_unreachable");
138
+ expect(versionCheck).toBeTruthy();
139
+ // hello probe must be skipped, never run, when version failed
140
+ expect(result.checks.find((c) => c.code === "ollama_hello_probe_skipped")).toBeTruthy();
141
+ });
142
+ it("classifies hello-probe auth failure as auth_required", async () => {
143
+ FakeOllama.behaviors.set("http://localhost:11434", {
144
+ version: () => ({ version: "0.5.0" }),
145
+ list: () => ({ models: [{ name: "llama3.1" }] }),
146
+ chat: () => {
147
+ throw new Error("Invalid api key for this model");
148
+ },
149
+ });
150
+ const result = await testEnvironment({
151
+ adapterType: "ollama_local",
152
+ companyId: "c",
153
+ config: { model: "llama3.1" },
154
+ }, { ollamaCtor: ctor });
155
+ expect(result.checks.find((c) => c.code === "ollama_hello_probe_auth_required")).toBeTruthy();
156
+ });
157
+ });
158
+ //# sourceMappingURL=test.test.js.map