@warlock.js/ai-ollama 4.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/esm/sdk.d.mts ADDED
@@ -0,0 +1,62 @@
1
+ import { OllamaEmbedderConfig, OllamaModelConfig, OllamaSDKConfig } from "./config.type.mjs";
2
+ import { EmbedderContract, ModelContract, SDKAdapterContract } from "@warlock.js/ai";
3
+
4
+ //#region ../../@warlock.js/ai-ollama/src/sdk.d.ts
5
+ /**
6
+ * Ollama-backed implementation of `SDKAdapterContract`.
7
+ *
8
+ * **Role.** The package entry point for local / self-hosted models
9
+ * served by an Ollama daemon via the official `ollama` client. One
10
+ * `OllamaSDK` holds one live `Ollama` client, shared by every
11
+ * `ModelContract` / `EmbedderContract` it produces.
12
+ *
13
+ * **Responsibility.**
14
+ * - Owns: a long-lived `Ollama` client (host, headers) and its
15
+ * lifetime. Factory for `OllamaModel` / `OllamaEmbedder` instances
16
+ * sharing that client.
17
+ * - Does NOT own: anything per-call — those live in `OllamaModel` /
18
+ * `OllamaEmbedder` and the agent runtime.
19
+ *
20
+ * Modeled as a class (see §4.2 of code-style.md — "long-lived state
21
+ * across many calls"), fronted by FP usage like the other adapters.
22
+ *
23
+ * @example
24
+ * const ollama = new OllamaSDK({}); // local default host
25
+ * const model = ollama.model({ name: "llama3.1", temperature: 0.7 });
26
+ * const embedder = ollama.embedder({ name: "nomic-embed-text" });
27
+ */
28
+ declare class OllamaSDK implements SDKAdapterContract {
29
+ private readonly client;
30
+ private readonly provider;
31
+ private readonly pricing?;
32
+ constructor(config?: OllamaSDKConfig);
33
+ /**
34
+ * Build an `OllamaModel` bound to this SDK's client. Each call
35
+ * returns a fresh instance; all instances share the underlying
36
+ * `Ollama` client. The SDK's `provider` label is forwarded.
37
+ *
38
+ * Pricing resolution: per-model `config.pricing` wins; otherwise the
39
+ * SDK-level registry entry keyed by `config.name`; otherwise
40
+ * `undefined` (local Ollama is free, so usually undefined).
41
+ */
42
+ model(config: OllamaModelConfig): ModelContract;
43
+ /**
44
+ * Rough token-count estimate. Uses the character-heuristic
45
+ * (`approximateTokenCount`) from the core package — good enough for
46
+ * budgeting / context guards, not billing (and Ollama is free
47
+ * anyway). The optional model id is reserved for future per-model
48
+ * tokenizer dispatch; currently ignored.
49
+ */
50
+ count(text: string, _model?: string): Promise<number>;
51
+ /**
52
+ * Build an `OllamaEmbedder` bound to this SDK's client.
53
+ *
54
+ * @example
55
+ * const embedder = ollama.embedder({ name: "nomic-embed-text" });
56
+ * const { vector } = await embedder.embed("Hello world");
57
+ */
58
+ embedder(config: OllamaEmbedderConfig): EmbedderContract;
59
+ }
60
+ //#endregion
61
+ export { OllamaSDK };
62
+ //# sourceMappingURL=sdk.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../@warlock.js/ai-ollama/src/sdk.ts"],"mappings":";;;;;;AAuCA;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,GAAQ,eAAA;EAgCwB;;;;;;AAWY;;;EA1BxD,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;;;;;;;;EAe5B,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;EAW5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;AAAA"}
package/esm/sdk.mjs ADDED
@@ -0,0 +1,78 @@
1
+ import { OllamaEmbedder } from "./embedder.mjs";
2
+ import { OllamaModel } from "./model.mjs";
3
+ import { Ollama } from "ollama";
4
+ import { approximateTokenCount } from "@warlock.js/ai";
5
+
6
+ //#region ../../@warlock.js/ai-ollama/src/sdk.ts
7
+ /**
8
+ * Ollama-backed implementation of `SDKAdapterContract`.
9
+ *
10
+ * **Role.** The package entry point for local / self-hosted models
11
+ * served by an Ollama daemon via the official `ollama` client. One
12
+ * `OllamaSDK` holds one live `Ollama` client, shared by every
13
+ * `ModelContract` / `EmbedderContract` it produces.
14
+ *
15
+ * **Responsibility.**
16
+ * - Owns: a long-lived `Ollama` client (host, headers) and its
17
+ * lifetime. Factory for `OllamaModel` / `OllamaEmbedder` instances
18
+ * sharing that client.
19
+ * - Does NOT own: anything per-call — those live in `OllamaModel` /
20
+ * `OllamaEmbedder` and the agent runtime.
21
+ *
22
+ * Modeled as a class (see §4.2 of code-style.md — "long-lived state
23
+ * across many calls"), fronted by FP usage like the other adapters.
24
+ *
25
+ * @example
26
+ * const ollama = new OllamaSDK({}); // local default host
27
+ * const model = ollama.model({ name: "llama3.1", temperature: 0.7 });
28
+ * const embedder = ollama.embedder({ name: "nomic-embed-text" });
29
+ */
30
+ var OllamaSDK = class {
31
+ constructor(config = {}) {
32
+ const { provider, pricing, ...clientConfig } = config;
33
+ this.client = new Ollama(clientConfig);
34
+ this.provider = provider ?? "ollama";
35
+ this.pricing = pricing;
36
+ }
37
+ /**
38
+ * Build an `OllamaModel` bound to this SDK's client. Each call
39
+ * returns a fresh instance; all instances share the underlying
40
+ * `Ollama` client. The SDK's `provider` label is forwarded.
41
+ *
42
+ * Pricing resolution: per-model `config.pricing` wins; otherwise the
43
+ * SDK-level registry entry keyed by `config.name`; otherwise
44
+ * `undefined` (local Ollama is free, so usually undefined).
45
+ */
46
+ model(config) {
47
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
48
+ const resolvedConfig = resolvedPricing === config.pricing ? config : {
49
+ ...config,
50
+ pricing: resolvedPricing
51
+ };
52
+ return new OllamaModel(this.client, resolvedConfig, this.provider);
53
+ }
54
+ /**
55
+ * Rough token-count estimate. Uses the character-heuristic
56
+ * (`approximateTokenCount`) from the core package — good enough for
57
+ * budgeting / context guards, not billing (and Ollama is free
58
+ * anyway). The optional model id is reserved for future per-model
59
+ * tokenizer dispatch; currently ignored.
60
+ */
61
+ async count(text, _model) {
62
+ return approximateTokenCount(text);
63
+ }
64
+ /**
65
+ * Build an `OllamaEmbedder` bound to this SDK's client.
66
+ *
67
+ * @example
68
+ * const embedder = ollama.embedder({ name: "nomic-embed-text" });
69
+ * const { vector } = await embedder.embed("Hello world");
70
+ */
71
+ embedder(config) {
72
+ return new OllamaEmbedder(this.client, config, this.provider);
73
+ }
74
+ };
75
+
76
+ //#endregion
77
+ export { OllamaSDK };
78
+ //# sourceMappingURL=sdk.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../@warlock.js/ai-ollama/src/sdk.ts"],"sourcesContent":["import { Ollama } from \"ollama\";\nimport type {\n EmbedderContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n OllamaEmbedderConfig,\n OllamaModelConfig,\n OllamaSDKConfig,\n} from \"./config.type\";\nimport { OllamaEmbedder } from \"./embedder\";\nimport { OllamaModel } from \"./model\";\n\n/**\n * Ollama-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for local / self-hosted models\n * served by an Ollama daemon via the official `ollama` client. One\n * `OllamaSDK` holds one live `Ollama` client, shared by every\n * `ModelContract` / `EmbedderContract` it produces.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Ollama` client (host, headers) and its\n * lifetime. Factory for `OllamaModel` / `OllamaEmbedder` instances\n * sharing that client.\n * - Does NOT own: anything per-call — those live in `OllamaModel` /\n * `OllamaEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"), fronted by FP usage like the other adapters.\n *\n * @example\n * const ollama = new OllamaSDK({}); // local default host\n * const model = ollama.model({ name: \"llama3.1\", temperature: 0.7 });\n * const embedder = ollama.embedder({ name: \"nomic-embed-text\" });\n */\nexport class OllamaSDK implements SDKAdapterContract {\n private readonly client: Ollama;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: OllamaSDKConfig = {}) {\n const { provider, pricing, ...clientConfig } = config;\n\n this.client = new Ollama(clientConfig);\n this.provider = provider ?? \"ollama\";\n this.pricing = pricing;\n }\n\n /**\n * Build an `OllamaModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying\n * `Ollama` client. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (local Ollama is free, so usually undefined).\n */\n public model(config: OllamaModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OllamaModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OllamaModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — good enough for\n * budgeting / context guards, not billing (and Ollama is free\n * anyway). The optional model id is reserved for future per-model\n * tokenizer dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build an `OllamaEmbedder` bound to this SDK's client.\n *\n * @example\n * const embedder = ollama.embedder({ name: \"nomic-embed-text\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: OllamaEmbedderConfig): EmbedderContract {\n return new OllamaEmbedder(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,SAA0B,CAAC,GAAG;EAC/C,MAAM,EAAE,UAAU,SAAS,GAAG,iBAAiB;EAE/C,KAAK,SAAS,IAAI,OAAO,YAAY;EACrC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;CAWA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACnE;;;;;;;;CASA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;CASA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CAC9D;AACF"}
@@ -0,0 +1,6 @@
1
+ import { mapDoneReason } from "./map-done-reason.mjs";
2
+ import { toOllamaMessages } from "./to-ollama-messages.mjs";
3
+ import { toOllamaTools } from "./to-ollama-tools.mjs";
4
+ import { wrapOllamaError } from "./wrap-ollama-error.mjs";
5
+
6
+ export { };
@@ -0,0 +1,31 @@
1
+ //#region ../../@warlock.js/ai-ollama/src/utils/map-done-reason.ts
2
+ const doneReasonMap = {
3
+ stop: "stop",
4
+ length: "length"
5
+ };
6
+ /**
7
+ * Map Ollama's `done_reason` to the normalized `FinishReason` union.
8
+ *
9
+ * `stop` is the natural terminal; `length` means the `num_predict`
10
+ * cap was hit. Anything else — `load` (model load only, no
11
+ * generation), an empty string, or any future value — falls through
12
+ * to `"error"`.
13
+ *
14
+ * Note: Ollama has no tool-use done reason — it sets `done_reason:
15
+ * "stop"` and populates `message.tool_calls`. `OllamaModel` derives
16
+ * `"tool_calls"` from tool-call presence; this map stays purely about
17
+ * the raw signal.
18
+ *
19
+ * @example
20
+ * mapDoneReason("stop"); // "stop"
21
+ * mapDoneReason("length"); // "length"
22
+ * mapDoneReason("load"); // "error"
23
+ * mapDoneReason(undefined); // "error"
24
+ */
25
+ function mapDoneReason(raw) {
26
+ return doneReasonMap[raw ?? ""] ?? "error";
27
+ }
28
+
29
+ //#endregion
30
+ export { mapDoneReason };
31
+ //# sourceMappingURL=map-done-reason.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-done-reason.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-ollama/src/utils/map-done-reason.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst doneReasonMap: Record<string, FinishReason> = {\n stop: \"stop\",\n length: \"length\",\n};\n\n/**\n * Map Ollama's `done_reason` to the normalized `FinishReason` union.\n *\n * `stop` is the natural terminal; `length` means the `num_predict`\n * cap was hit. Anything else — `load` (model load only, no\n * generation), an empty string, or any future value — falls through\n * to `\"error\"`.\n *\n * Note: Ollama has no tool-use done reason — it sets `done_reason:\n * \"stop\"` and populates `message.tool_calls`. `OllamaModel` derives\n * `\"tool_calls\"` from tool-call presence; this map stays purely about\n * the raw signal.\n *\n * @example\n * mapDoneReason(\"stop\"); // \"stop\"\n * mapDoneReason(\"length\"); // \"length\"\n * mapDoneReason(\"load\"); // \"error\"\n * mapDoneReason(undefined); // \"error\"\n */\nexport function mapDoneReason(raw: string | null | undefined): FinishReason {\n return doneReasonMap[raw ?? \"\"] ?? \"error\";\n}\n"],"mappings":";AAEA,MAAM,gBAA8C;CAClD,MAAM;CACN,QAAQ;AACV;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,cAAc,KAA8C;CAC1E,OAAO,cAAc,OAAO,OAAO;AACrC"}
@@ -0,0 +1,87 @@
1
+ import { InvalidRequestError } from "@warlock.js/ai";
2
+
3
+ //#region ../../@warlock.js/ai-ollama/src/utils/to-ollama-messages.ts
4
+ /**
5
+ * Convert vendor-neutral `Message[]` into the Ollama chat message
6
+ * shape.
7
+ *
8
+ * Unlike Anthropic / Gemini / Bedrock, Ollama keeps a first-class
9
+ * `system` role inside `messages`, so there is no system-prompt
10
+ * hoisting — roles pass straight through. The Ollama specifics this
11
+ * absorbs:
12
+ *
13
+ * 1. **Tool calls.** An assistant message with `toolCalls` becomes an
14
+ * `assistant` message whose `tool_calls` is the Ollama
15
+ * `{ function: { name, arguments } }` shape (Ollama has no tool-call
16
+ * id — see `OllamaModel`/decisions for the synthesized-id note).
17
+ * 2. **Tool results.** A neutral `tool` message becomes a `tool`
18
+ * message with `tool_name` set from `toolCallId` (Ollama matches a
19
+ * result to its call by tool name).
20
+ * 3. **Images.** Multipart user content collapses to a single
21
+ * `content` string plus an `images` array of base64 strings.
22
+ *
23
+ * @example
24
+ * const messages = toOllamaMessages([
25
+ * { role: "system", content: "Be concise." },
26
+ * { role: "user", content: "Hi" },
27
+ * ]);
28
+ */
29
+ function toOllamaMessages(messages) {
30
+ return messages.map((message) => {
31
+ if (message.role === "tool") return {
32
+ role: "tool",
33
+ content: stringifyContent(message.content),
34
+ tool_name: message.toolCallId ?? ""
35
+ };
36
+ if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) return {
37
+ role: "assistant",
38
+ content: stringifyContent(message.content),
39
+ tool_calls: message.toolCalls.map((toolCall) => ({ function: {
40
+ name: toolCall.name,
41
+ arguments: toolCall.input ?? {}
42
+ } }))
43
+ };
44
+ if (message.role === "user" && Array.isArray(message.content)) return toMultipartMessage(message.content);
45
+ return {
46
+ role: message.role,
47
+ content: stringifyContent(message.content)
48
+ };
49
+ });
50
+ }
51
+ /**
52
+ * Collapse a `ContentPart[]` user message into Ollama's
53
+ * single-string-content + base64-`images` shape. Ollama cannot fetch
54
+ * remote URLs, so a `{ url }` image surfaces a typed
55
+ * `InvalidRequestError` upfront (consistent with the Bedrock/Gemini
56
+ * adapters). The agent has already resolved attachments — nothing is
57
+ * fetched here.
58
+ */
59
+ function toMultipartMessage(parts) {
60
+ const textChunks = [];
61
+ const images = [];
62
+ for (const part of parts) {
63
+ if (part.type === "text") {
64
+ textChunks.push(part.text);
65
+ continue;
66
+ }
67
+ if ("url" in part.source) throw new InvalidRequestError("Ollama does not fetch remote-URL images; supply base64 image bytes instead.");
68
+ images.push(part.source.base64);
69
+ }
70
+ return {
71
+ role: "user",
72
+ content: textChunks.join(""),
73
+ ...images.length > 0 ? { images } : {}
74
+ };
75
+ }
76
+ /**
77
+ * Multipart content on a non-user role collapses to concatenated text;
78
+ * plain strings pass through unchanged.
79
+ */
80
+ function stringifyContent(content) {
81
+ if (typeof content === "string") return content;
82
+ return content.filter((part) => part.type === "text").map((part) => part.text).join("");
83
+ }
84
+
85
+ //#endregion
86
+ export { toOllamaMessages };
87
+ //# sourceMappingURL=to-ollama-messages.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-ollama-messages.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-ollama/src/utils/to-ollama-messages.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type { Message as OllamaMessage } from \"ollama\";\n\n/**\n * Convert vendor-neutral `Message[]` into the Ollama chat message\n * shape.\n *\n * Unlike Anthropic / Gemini / Bedrock, Ollama keeps a first-class\n * `system` role inside `messages`, so there is no system-prompt\n * hoisting — roles pass straight through. The Ollama specifics this\n * absorbs:\n *\n * 1. **Tool calls.** An assistant message with `toolCalls` becomes an\n * `assistant` message whose `tool_calls` is the Ollama\n * `{ function: { name, arguments } }` shape (Ollama has no tool-call\n * id — see `OllamaModel`/decisions for the synthesized-id note).\n * 2. **Tool results.** A neutral `tool` message becomes a `tool`\n * message with `tool_name` set from `toolCallId` (Ollama matches a\n * result to its call by tool name).\n * 3. **Images.** Multipart user content collapses to a single\n * `content` string plus an `images` array of base64 strings.\n *\n * @example\n * const messages = toOllamaMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toOllamaMessages(messages: Message[]): OllamaMessage[] {\n return messages.map((message): OllamaMessage => {\n if (message.role === \"tool\") {\n return {\n role: \"tool\",\n content: stringifyContent(message.content),\n tool_name: message.toolCallId ?? \"\",\n };\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n return {\n role: \"assistant\",\n content: stringifyContent(message.content),\n tool_calls: message.toolCalls.map((toolCall) => ({\n function: {\n name: toolCall.name,\n arguments: (toolCall.input ?? {}) as Record<string, unknown>,\n },\n })),\n };\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n return toMultipartMessage(message.content);\n }\n\n return { role: message.role, content: stringifyContent(message.content) };\n });\n}\n\n/**\n * Collapse a `ContentPart[]` user message into Ollama's\n * single-string-content + base64-`images` shape. Ollama cannot fetch\n * remote URLs, so a `{ url }` image surfaces a typed\n * `InvalidRequestError` upfront (consistent with the Bedrock/Gemini\n * adapters). The agent has already resolved attachments — nothing is\n * fetched here.\n */\nfunction toMultipartMessage(parts: ContentPart[]): OllamaMessage {\n const textChunks: string[] = [];\n const images: string[] = [];\n\n for (const part of parts) {\n if (part.type === \"text\") {\n textChunks.push(part.text);\n\n continue;\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"Ollama does not fetch remote-URL images; supply base64 image bytes instead.\",\n );\n }\n\n images.push(part.source.base64);\n }\n\n return {\n role: \"user\",\n content: textChunks.join(\"\"),\n ...(images.length > 0 ? { images } : {}),\n };\n}\n\n/**\n * Multipart content on a non-user role collapses to concatenated text;\n * plain strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,iBAAiB,UAAsC;CACrE,OAAO,SAAS,KAAK,YAA2B;EAC9C,IAAI,QAAQ,SAAS,QACnB,OAAO;GACL,MAAM;GACN,SAAS,iBAAiB,QAAQ,OAAO;GACzC,WAAW,QAAQ,cAAc;EACnC;EAGF,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAClF,OAAO;GACL,MAAM;GACN,SAAS,iBAAiB,QAAQ,OAAO;GACzC,YAAY,QAAQ,UAAU,KAAK,cAAc,EAC/C,UAAU;IACR,MAAM,SAAS;IACf,WAAY,SAAS,SAAS,CAAC;GACjC,EACF,EAAE;EACJ;EAGF,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAC1D,OAAO,mBAAmB,QAAQ,OAAO;EAG3C,OAAO;GAAE,MAAM,QAAQ;GAAM,SAAS,iBAAiB,QAAQ,OAAO;EAAE;CAC1E,CAAC;AACH;;;;;;;;;AAUA,SAAS,mBAAmB,OAAqC;CAC/D,MAAM,aAAuB,CAAC;CAC9B,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,QAAQ;GACxB,WAAW,KAAK,KAAK,IAAI;GAEzB;EACF;EAEA,IAAI,SAAS,KAAK,QAChB,MAAM,IAAI,oBACR,6EACF;EAGF,OAAO,KAAK,KAAK,OAAO,MAAM;CAChC;CAEA,OAAO;EACL,MAAM;EACN,SAAS,WAAW,KAAK,EAAE;EAC3B,GAAI,OAAO,SAAS,IAAI,EAAE,OAAO,IAAI,CAAC;CACxC;AACF;;;;;AAMA,SAAS,iBAAiB,SAAyC;CACjE,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAiD,KAAK,SAAS,MAAM,EAC7E,KAAK,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AACZ"}
@@ -0,0 +1,41 @@
1
+ import { extractJsonSchema } from "@warlock.js/ai";
2
+
3
+ //#region ../../@warlock.js/ai-ollama/src/utils/to-ollama-tools.ts
4
+ /**
5
+ * Convert vendor-neutral `ToolConfig[]` into Ollama's `tools` array.
6
+ * Each tool becomes a `{ type: "function", function: { name,
7
+ * description, parameters } }` entry. Non-object extractions degrade
8
+ * to a parameterless object so registration never fails.
9
+ *
10
+ * Returns `undefined` when there are no tools so the caller can omit
11
+ * `tools` from the request.
12
+ *
13
+ * @example
14
+ * const tools = toOllamaTools([weatherTool]);
15
+ * await ollama.chat({ model, messages, tools });
16
+ */
17
+ function toOllamaTools(tools) {
18
+ if (!tools || tools.length === 0) return;
19
+ return tools.map((tool) => ({
20
+ type: "function",
21
+ function: {
22
+ name: tool.name,
23
+ description: tool.description,
24
+ parameters: toParameters(tool.input)
25
+ }
26
+ }));
27
+ }
28
+ /**
29
+ * Resolve a tool's input schema to a JSON-Schema object. Ollama wants
30
+ * an object root for function parameters; anything else (or a failed
31
+ * extraction) degrades to a parameterless object.
32
+ */
33
+ function toParameters(input) {
34
+ const schema = extractJsonSchema(input);
35
+ if (schema && schema.type === "object") return schema;
36
+ return { type: "object" };
37
+ }
38
+
39
+ //#endregion
40
+ export { toOllamaTools };
41
+ //# sourceMappingURL=to-ollama-tools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-ollama-tools.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-ollama/src/utils/to-ollama-tools.ts"],"sourcesContent":["import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type { Tool } from \"ollama\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Ollama's `tools` array.\n * Each tool becomes a `{ type: \"function\", function: { name,\n * description, parameters } }` entry. Non-object extractions degrade\n * to a parameterless object so registration never fails.\n *\n * Returns `undefined` when there are no tools so the caller can omit\n * `tools` from the request.\n *\n * @example\n * const tools = toOllamaTools([weatherTool]);\n * await ollama.chat({ model, messages, tools });\n */\nexport function toOllamaTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: toParameters(tool.input),\n },\n }));\n}\n\n/**\n * Resolve a tool's input schema to a JSON-Schema object. Ollama wants\n * an object root for function parameters; anything else (or a failed\n * extraction) degrades to a parameterless object.\n */\nfunction toParameters(input: ToolConfig<unknown, unknown>[\"input\"]): Tool[\"function\"][\"parameters\"] {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema as Tool[\"function\"][\"parameters\"];\n }\n\n return { type: \"object\" } as Tool[\"function\"][\"parameters\"];\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,cACd,OACoB;CACpB,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM;EACN,UAAU;GACR,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,YAAY,aAAa,KAAK,KAAK;EACrC;CACF,EAAE;AACJ;;;;;;AAOA,SAAS,aAAa,OAA8E;CAClG,MAAM,SAAS,kBAAkB,KAAK;CAEtC,IAAI,UAAU,OAAO,SAAS,UAC5B,OAAO;CAGT,OAAO,EAAE,MAAM,SAAS;AAC1B"}
@@ -0,0 +1,104 @@
1
+ import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError } from "@warlock.js/ai";
2
+
3
+ //#region ../../@warlock.js/ai-ollama/src/utils/wrap-ollama-error.ts
4
+ /**
5
+ * Wrap any thrown value caught inside the Ollama adapter into the
6
+ * appropriate `@warlock.js/ai` `AIError` subclass.
7
+ *
8
+ * **Dispatch strategy.** HTTP faults carry `status_code`; the local
9
+ * daemon being down surfaces as a connection error (`ECONNREFUSED` /
10
+ * "fetch failed") — mapped to `ProviderError` since it's an
11
+ * operational "is Ollama running?" condition, not a request defect.
12
+ * `400` with context-length phrasing maps to
13
+ * `ContextLengthExceededError`.
14
+ *
15
+ * `AIError` instances pass through unchanged so `catch/throw wrap(e)`
16
+ * pipelines never double-wrap.
17
+ *
18
+ * @example
19
+ * try {
20
+ * return await this.client.chat({ ... });
21
+ * } catch (thrown) {
22
+ * throw wrapOllamaError(thrown);
23
+ * }
24
+ */
25
+ function wrapOllamaError(thrown) {
26
+ if (thrown instanceof AIError) return thrown;
27
+ const shape = toShape(thrown);
28
+ const context = buildContext(shape);
29
+ const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));
30
+ if (isTimeout(shape)) return new ProviderTimeoutError(message, {
31
+ cause: thrown,
32
+ context
33
+ });
34
+ if (isConnectionRefused(shape, message)) return new ProviderError(message, {
35
+ cause: thrown,
36
+ context
37
+ });
38
+ if (shape.statusCode === 401 || shape.statusCode === 403) return new ProviderAuthError(message, {
39
+ cause: thrown,
40
+ context
41
+ });
42
+ if (shape.statusCode === 429) return new ProviderRateLimitError(message, {
43
+ cause: thrown,
44
+ context
45
+ });
46
+ if (isClientStatus(shape.statusCode)) {
47
+ if (/context length|too long|exceeds|maximum context/i.test(message)) return new ContextLengthExceededError(message, {
48
+ cause: thrown,
49
+ context
50
+ });
51
+ return new InvalidRequestError(message, {
52
+ cause: thrown,
53
+ context
54
+ });
55
+ }
56
+ return new ProviderError(message, {
57
+ cause: thrown,
58
+ context
59
+ });
60
+ }
61
+ /**
62
+ * Read the raw error shape. `ResponseError` exposes `status_code`;
63
+ * fetch-layer errors carry a `cause` whose `code` is the OS-level
64
+ * socket error.
65
+ */
66
+ function toShape(thrown) {
67
+ if (typeof thrown !== "object" || thrown === null) return {};
68
+ const raw = thrown;
69
+ const cause = raw.cause;
70
+ return {
71
+ name: typeof raw.name === "string" ? raw.name : void 0,
72
+ message: typeof raw.message === "string" ? raw.message : void 0,
73
+ statusCode: typeof raw.status_code === "number" ? raw.status_code : void 0,
74
+ code: typeof raw.code === "string" ? raw.code : cause && typeof cause.code === "string" ? cause.code : void 0
75
+ };
76
+ }
77
+ /** Transport-level timeout signals. */
78
+ function isTimeout(shape) {
79
+ if (shape.name === "AbortError" || shape.name === "TimeoutError") return true;
80
+ return shape.code === "ETIMEDOUT" || shape.code === "ECONNABORTED";
81
+ }
82
+ /**
83
+ * The Ollama daemon not being reachable (most common local failure):
84
+ * connection refused at the socket layer, or the `fetch failed`
85
+ * TypeError the client surfaces when the host is down.
86
+ */
87
+ function isConnectionRefused(shape, message) {
88
+ return shape.code === "ECONNREFUSED" || /fetch failed|econnrefused/i.test(message);
89
+ }
90
+ /** True for HTTP 4xx — a client-side request problem, not a server fault. */
91
+ function isClientStatus(status) {
92
+ return typeof status === "number" && status >= 400 && status < 500;
93
+ }
94
+ /** Attach the diagnostic fields to `error.context`. */
95
+ function buildContext(shape) {
96
+ const context = {};
97
+ if (shape.statusCode !== void 0) context.status = shape.statusCode;
98
+ if (shape.code) context.code = shape.code;
99
+ return context;
100
+ }
101
+
102
+ //#endregion
103
+ export { wrapOllamaError };
104
+ //# sourceMappingURL=wrap-ollama-error.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-ollama-error.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-ollama/src/utils/wrap-ollama-error.ts"],"sourcesContent":["import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n} from \"@warlock.js/ai\";\n\n/**\n * Raw-error fields the wrapper reads off an Ollama client error. The\n * `ollama` client throws a `ResponseError` (`name: \"ResponseError\"`,\n * numeric `status_code`, message = the server's `error` text) for HTTP\n * faults; transport failures surface as a `fetch`-layer `TypeError`\n * with an `ECONNREFUSED` / `ETIMEDOUT` cause. We duck-type both —\n * `ResponseError` is internal to the package and not exported.\n */\ntype OllamaErrorShape = {\n name?: string;\n message?: string;\n statusCode?: number;\n code?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the Ollama adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** HTTP faults carry `status_code`; the local\n * daemon being down surfaces as a connection error (`ECONNREFUSED` /\n * \"fetch failed\") — mapped to `ProviderError` since it's an\n * operational \"is Ollama running?\" condition, not a request defect.\n * `400` with context-length phrasing maps to\n * `ContextLengthExceededError`.\n *\n * `AIError` instances pass through unchanged so `catch/throw wrap(e)`\n * pipelines never double-wrap.\n *\n * @example\n * try {\n * return await this.client.chat({ ... });\n * } catch (thrown) {\n * throw wrapOllamaError(thrown);\n * }\n */\nexport function wrapOllamaError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (isConnectionRefused(shape, message)) {\n return new ProviderError(message, { cause: thrown, context });\n }\n\n if (shape.statusCode === 401 || shape.statusCode === 403) {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.statusCode === 429) {\n return new ProviderRateLimitError(message, { cause: thrown, context });\n }\n\n if (isClientStatus(shape.statusCode)) {\n if (/context length|too long|exceeds|maximum context/i.test(message)) {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape. `ResponseError` exposes `status_code`;\n * fetch-layer errors carry a `cause` whose `code` is the OS-level\n * socket error.\n */\nfunction toShape(thrown: unknown): OllamaErrorShape {\n if (typeof thrown !== \"object\" || thrown === null) {\n return {};\n }\n\n const raw = thrown as Record<string, unknown>;\n const cause = raw.cause as Record<string, unknown> | undefined;\n\n return {\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n statusCode: typeof raw.status_code === \"number\" ? raw.status_code : undefined,\n code:\n typeof raw.code === \"string\"\n ? raw.code\n : cause && typeof cause.code === \"string\"\n ? cause.code\n : undefined,\n };\n}\n\n/** Transport-level timeout signals. */\nfunction isTimeout(shape: OllamaErrorShape): boolean {\n if (shape.name === \"AbortError\" || shape.name === \"TimeoutError\") {\n return true;\n }\n\n return shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\";\n}\n\n/**\n * The Ollama daemon not being reachable (most common local failure):\n * connection refused at the socket layer, or the `fetch failed`\n * TypeError the client surfaces when the host is down.\n */\nfunction isConnectionRefused(shape: OllamaErrorShape, message: string): boolean {\n return shape.code === \"ECONNREFUSED\" || /fetch failed|econnrefused/i.test(message);\n}\n\n/** True for HTTP 4xx — a client-side request problem, not a server fault. */\nfunction isClientStatus(status: number | undefined): boolean {\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/** Attach the diagnostic fields to `error.context`. */\nfunction buildContext(shape: OllamaErrorShape): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.statusCode !== undefined) {\n context.status = shape.statusCode;\n }\n\n if (shape.code) {\n context.code = shape.code;\n }\n\n return context;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,gBAAgB,QAA0B;CACxD,IAAI,kBAAkB,SACpB,OAAO;CAGT,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,UAAU,aAAa,KAAK;CAClC,MAAM,UAAU,MAAM,YAAY,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;CAE1F,IAAI,UAAU,KAAK,GACjB,OAAO,IAAI,qBAAqB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGrE,IAAI,oBAAoB,OAAO,OAAO,GACpC,OAAO,IAAI,cAAc,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAG9D,IAAI,MAAM,eAAe,OAAO,MAAM,eAAe,KACnD,OAAO,IAAI,kBAAkB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGlE,IAAI,MAAM,eAAe,KACvB,OAAO,IAAI,uBAAuB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGvE,IAAI,eAAe,MAAM,UAAU,GAAG;EACpC,IAAI,mDAAmD,KAAK,OAAO,GACjE,OAAO,IAAI,2BAA2B,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;EAG3E,OAAO,IAAI,oBAAoB,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;CACpE;CAEA,OAAO,IAAI,cAAc,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;AAC9D;;;;;;AAOA,SAAS,QAAQ,QAAmC;CAClD,IAAI,OAAO,WAAW,YAAY,WAAW,MAC3C,OAAO,CAAC;CAGV,MAAM,MAAM;CACZ,MAAM,QAAQ,IAAI;CAElB,OAAO;EACL,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;EAChD,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;EACzD,YAAY,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;EACpE,MACE,OAAO,IAAI,SAAS,WAChB,IAAI,OACJ,SAAS,OAAO,MAAM,SAAS,WAC7B,MAAM,OACN;CACV;AACF;;AAGA,SAAS,UAAU,OAAkC;CACnD,IAAI,MAAM,SAAS,gBAAgB,MAAM,SAAS,gBAChD,OAAO;CAGT,OAAO,MAAM,SAAS,eAAe,MAAM,SAAS;AACtD;;;;;;AAOA,SAAS,oBAAoB,OAAyB,SAA0B;CAC9E,OAAO,MAAM,SAAS,kBAAkB,6BAA6B,KAAK,OAAO;AACnF;;AAGA,SAAS,eAAe,QAAqC;CAC3D,OAAO,OAAO,WAAW,YAAY,UAAU,OAAO,SAAS;AACjE;;AAGA,SAAS,aAAa,OAAkD;CACtE,MAAM,UAAmC,CAAC;CAE1C,IAAI,MAAM,eAAe,QACvB,QAAQ,SAAS,MAAM;CAGzB,IAAI,MAAM,MACR,QAAQ,OAAO,MAAM;CAGvB,OAAO;AACT"}
package/llms-full.txt ADDED
@@ -0,0 +1,122 @@
1
+ # Warlock AI Ollama — full skills
2
+
3
+ > Package: `@warlock.js/ai-ollama`
4
+
5
+ > Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/ai-ollama/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
6
+
7
+ ## setup-ollama `@warlock.js/ai-ollama/setup-ollama/SKILL.md`
8
+
9
+ ---
10
+ name: setup-ollama
11
+ description: 'Wire @warlock.js/ai-ollama — new OllamaSDK({host?, headers?}) for local / self-hosted Ollama via the official ollama client (not OpenAI-compat). chat + embed, daemon-down error handling. Triggers: `OllamaSDK`, `ollama.model`, `ollama.embedder`, `embedder.embedMany`, `ollama.count`, `host`, `headers`; "use ollama with warlock", "run llama3 locally", "self-hosted llama"; typical import `import { OllamaSDK } from "@warlock.js/ai-ollama"`. Skip: agent loop — `@warlock.js/ai/run-ai-agent/SKILL.md`; provider choice — `@warlock.js/ai/pick-ai-provider/SKILL.md`; embeddings core — `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`; raw `ollama` npm, Vercel `@ai-sdk/ollama`; OpenAI-compat gateway via `@warlock.js/ai-openai` `baseURL`.'
12
+ ---
13
+
14
+ # `@warlock.js/ai-ollama`
15
+
16
+ Provider adapter that turns a local (or self-hosted) Ollama server into a vendor-neutral `ModelContract`, plus an Ollama embedder. Uses the **official `ollama` npm package** (not OpenAI-compat). Mirrors the openai / anthropic / bedrock / google adapters.
17
+
18
+ ## Construction
19
+
20
+ ```ts
21
+ import { OllamaSDK } from "@warlock.js/ai-ollama";
22
+
23
+ const ollama = new OllamaSDK(); // local default host
24
+ const remote = new OllamaSDK({ host: "http://gpu-box.internal:11434" });
25
+ const gated = new OllamaSDK({
26
+ host: "https://ollama.internal",
27
+ headers: { Authorization: `Bearer ${process.env.OLLAMA_TOKEN}` },
28
+ });
29
+ ```
30
+
31
+ `OllamaSDK` is a class with a long-lived `Ollama` client. Config is `Partial<Config>` (host defaults to `http://127.0.0.1:11434`) + `provider` (default `"ollama"`) + optional `pricing` (local is free; kept for parity/chargeback).
32
+
33
+ ## Producing a model
34
+
35
+ ```ts
36
+ ollama.model({ name: "llama3.1" })
37
+ ollama.model({ name: "qwen2.5:14b", temperature: 0.2 })
38
+ ollama.model({ name: "llama3.2-vision", maxTokens: 1024 })
39
+ ```
40
+
41
+ ## Capabilities — what's auto-set
42
+
43
+ | Flag | Default |
44
+ | --- | --- |
45
+ | `structuredOutput` | `true` (via Ollama's native `format` JSON-schema field) |
46
+ | `vision` | Inferred from model tag substring. `true` for `llava`, `bakllava`, `*-vision`, `moondream`, `minicpm-v`, `qwen2-vl`, `qwen2.5-vl`, `llama4`, `gemma3`; `false` otherwise. |
47
+
48
+ Explicit config always wins.
49
+
50
+ ## System prompt & roles
51
+
52
+ Unlike Anthropic/Gemini/Bedrock, **Ollama keeps a first-class `system` role inside `messages`** — no hoisting. Neutral roles (`system`/`user`/`assistant`/`tool`) pass straight through.
53
+
54
+ ## Tool calls
55
+
56
+ - Outgoing: neutral tools → `{ type: "function", function: { name, description, parameters } }`.
57
+ - Assistant tool calls → `tool_calls: [{ function: { name, arguments } }]` (Ollama has **no tool-call id**).
58
+ - Tool results (`role: "tool"`) → a `tool` message with `tool_name` set from `toolCallId` (Ollama matches a result to its call by name).
59
+
60
+ **Synthesized ids.** Because Ollama tool calls carry no id, the adapter sets neutral `id` = tool name. **Parallel calls to the same tool in one turn share an id** — a documented v1 limitation. Ollama reports `done_reason: "stop"` even when it called a tool; the adapter derives `finishReason: "tool_calls"` from tool-call presence.
61
+
62
+ ## Structured output
63
+
64
+ Object-root `responseSchema` + `structuredOutput`-capable → `chat({ format: <schema> })` (Ollama's `format` accepts a JSON Schema object).
65
+
66
+ ## Multipart messages (vision)
67
+
68
+ A multipart user message collapses to a single `content` string + an `images` array of **base64 strings**. `{ type: "image", source: { url } }` → **throws `InvalidRequestError`** (Ollama cannot fetch remote URLs). Resolve images to base64 first.
69
+
70
+ ## Streaming
71
+
72
+ `model.stream()` drains `chat({ stream: true })` (an `AbortableAsyncIterator`). Each chunk's `message.content` → `{ type: "delta" }`; `message.tool_calls` are emitted as `{ type: "tool-call" }` **fully formed**. Terminal `{ type: "done", finishReason, usage }` — usage from the final (`done: true`) chunk's `prompt_eval_count` / `eval_count`.
73
+
74
+ **`options.signal` is honored** by calling the iterator's `abort()` (stream path; non-stream `complete()` ignores it — the agent still honors the signal at trip boundaries).
75
+
76
+ ## Finish-reason mapping
77
+
78
+ `stop` → `stop` · `length` → `length` · `load` / unknown / null → `error`. `tool_calls` derived from tool-call presence.
79
+
80
+ ## Embeddings
81
+
82
+ ```ts
83
+ const embedder = ollama.embedder({ name: "nomic-embed-text" });
84
+ const { vector } = await embedder.embed("Hello world");
85
+ const { vectors } = await embedder.embedMany(["a", "b"]); // single batched call
86
+ const truncated = ollama.embedder({ name: "mxbai-embed-large", dimensions: 512 });
87
+ ```
88
+
89
+ `client.embed` accepts a string array natively, so `embedMany` is **one request** (like the Gemini adapter). Usage comes from `prompt_eval_count` (reported as both `promptTokens` and `totalTokens`). Local Ollama runs without a prompt cache, so model usage has no `cachedTokens`.
90
+
91
+ `dimensions` is optional. When set it's forwarded as Ollama's `dimensions` truncation field (newer embedding models) and seeds `embedder.dimensions`; when omitted, `embedder.dimensions` starts at `0` and is resolved lazily from the first response's vector length, then cached.
92
+
93
+ ## Errors
94
+
95
+ Wrapped into the typed `@warlock.js/ai` `AIError` hierarchy. The `ollama` client throws an internal `ResponseError` (`status_code` + message); transport failures surface as `fetch` `TypeError` with `ECONNREFUSED` cause:
96
+
97
+ - **Daemon-down (`ECONNREFUSED` / "fetch failed") → `ProviderError`** (operational "is Ollama running?", not a request defect)
98
+ - Timeouts → `ProviderTimeoutError`
99
+ - 401/403 → `ProviderAuthError`
100
+ - 429 → `ProviderRateLimitError`
101
+ - 4xx with context phrasing → `ContextLengthExceededError`, else `InvalidRequestError`
102
+ - 5xx → `ProviderError`
103
+
104
+ ## Token counting
105
+
106
+ ```ts
107
+ await ollama.count("some text") // approximate heuristic, offline
108
+ ```
109
+
110
+ ## When NOT to use this skill
111
+
112
+ - Direct `ollama` client calls without going through `@warlock.js/ai` agents.
113
+ - OpenAI / Anthropic / Bedrock / Google models — those have their own adapter packages.
114
+ - An OpenAI-compatible Ollama gateway you specifically want to drive through the OpenAI protocol — use `@warlock.js/ai-openai` with `baseURL` instead.
115
+
116
+ ## See also
117
+
118
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md)
119
+ - [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md)
120
+ - [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md)
121
+
122
+
package/llms.txt ADDED
@@ -0,0 +1,9 @@
1
+ # Warlock AI Ollama
2
+
3
+ > Package: `@warlock.js/ai-ollama`
4
+
5
+ > Ollama adapter for @warlock.js/ai
6
+
7
+ ## Skills
8
+
9
+ - [setup-ollama](@warlock.js/ai-ollama/setup-ollama/SKILL.md): Wire @warlock.js/ai-ollama — new OllamaSDK({host?, headers?}) for local / self-hosted Ollama via the official ollama client (not OpenAI-compat). chat + embed, daemon-down error handling. Triggers: `OllamaSDK`, `ollama.model`, `ollama.embedder`, `embedder.embedMany`, `ollama.count`, `host`, `headers`; "use ollama with warlock", "run llama3 locally", "self-hosted llama"; typical import `import { OllamaSDK } from "@warlock.js/ai-ollama"`. Skip: agent loop — `@warlock.js/ai/run-ai-agent/SKILL.md`; provider choice — `@warlock.js/ai/pick-ai-provider/SKILL.md`; embeddings core — `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`; raw `ollama` npm, Vercel `@ai-sdk/ollama`; OpenAI-compat gateway via `@warlock.js/ai-openai` `baseURL`.
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@warlock.js/ai-ollama",
3
+ "description": "Ollama adapter for @warlock.js/ai",
4
+ "keywords": [
5
+ "warlock",
6
+ "ai",
7
+ "ollama"
8
+ ],
9
+ "author": "Hasan Zohdy",
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/warlockjs/ai-ollama"
14
+ },
15
+ "dependencies": {
16
+ "ollama": "^0.6.3",
17
+ "@warlock.js/logger": "*"
18
+ },
19
+ "peerDependencies": {
20
+ "@warlock.js/ai": "*"
21
+ },
22
+ "version": "4.1.1",
23
+ "main": "./cjs/index.cjs",
24
+ "module": "./esm/index.mjs",
25
+ "types": "./esm/index.d.mts",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./esm/index.d.mts",
30
+ "default": "./esm/index.mjs"
31
+ },
32
+ "require": {
33
+ "types": "./esm/index.d.mts",
34
+ "default": "./cjs/index.cjs"
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,9 @@
1
+ # `@warlock.js/ai-ollama` — skills index
2
+
3
+ Per-task skills. All cross-references use the form `@warlock.js/<pkg>/<skill>/SKILL.md`.
4
+
5
+ ## Skills
6
+
7
+ ### [`setup-ollama/`](./setup-ollama/SKILL.md)
8
+
9
+ Wire @warlock.js/ai-ollama — new OllamaSDK({host?, headers?}) for local / self-hosted Ollama via the official ollama client (not OpenAI-compat). chat + embed, daemon-down error handling. Load when wiring a local or self-hosted Ollama model into a @warlock.js agent.