@sisu-ai/adapter-ollama 9.0.1 → 9.0.2
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 +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +113 -74
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<div align="right">
|
|
2
|
+
<a href="https://github.com/finger-gun/sisu"><img src="https://github.com/finger-gun/sisu/raw/main/sisu-light.svg" alt="ProjectSpecs" width="100" /></a>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# @sisu-ai/adapter-ollama
|
|
2
8
|
|
|
3
9
|
Ollama Chat adapter with native tools support.
|
|
@@ -116,3 +122,78 @@ Discover what you can do through examples or documentation. Check it out at http
|
|
|
116
122
|
- [License](https://github.com/finger-gun/sisu/blob/main/LICENSE)
|
|
117
123
|
- [Report a Bug](https://github.com/finger-gun/sisu/issues/new?template=bug_report.md)
|
|
118
124
|
- [Request a Feature](https://github.com/finger-gun/sisu/issues/new?template=feature_request.md)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
**Core** — [Package docs](packages/core/README.md) · [Error types](packages/core/ERROR_TYPES.md)
|
|
131
|
+
|
|
132
|
+
**Adapters** — [OpenAI](packages/adapters/openai/README.md) · [Anthropic](packages/adapters/anthropic/README.md) · [Ollama](packages/adapters/ollama/README.md)
|
|
133
|
+
|
|
134
|
+
<details>
|
|
135
|
+
<summary>All middleware packages</summary>
|
|
136
|
+
|
|
137
|
+
- [@sisu-ai/mw-agent-run-api](packages/middleware/agent-run-api/README.md)
|
|
138
|
+
- [@sisu-ai/mw-context-compressor](packages/middleware/context-compressor/README.md)
|
|
139
|
+
- [@sisu-ai/mw-control-flow](packages/middleware/control-flow/README.md)
|
|
140
|
+
- [@sisu-ai/mw-conversation-buffer](packages/middleware/conversation-buffer/README.md)
|
|
141
|
+
- [@sisu-ai/mw-cors](packages/middleware/cors/README.md)
|
|
142
|
+
- [@sisu-ai/mw-error-boundary](packages/middleware/error-boundary/README.md)
|
|
143
|
+
- [@sisu-ai/mw-guardrails](packages/middleware/guardrails/README.md)
|
|
144
|
+
- [@sisu-ai/mw-invariants](packages/middleware/invariants/README.md)
|
|
145
|
+
- [@sisu-ai/mw-orchestration](packages/middleware/orchestration/README.md)
|
|
146
|
+
- [@sisu-ai/mw-rag](packages/middleware/rag/README.md)
|
|
147
|
+
- [@sisu-ai/mw-react-parser](packages/middleware/react-parser/README.md)
|
|
148
|
+
- [@sisu-ai/mw-register-tools](packages/middleware/register-tools/README.md)
|
|
149
|
+
- [@sisu-ai/mw-tool-calling](packages/middleware/tool-calling/README.md)
|
|
150
|
+
- [@sisu-ai/mw-trace-viewer](packages/middleware/trace-viewer/README.md)
|
|
151
|
+
- [@sisu-ai/mw-usage-tracker](packages/middleware/usage-tracker/README.md)
|
|
152
|
+
</details>
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>All tool packages</summary>
|
|
156
|
+
|
|
157
|
+
- [@sisu-ai/tool-aws-s3](packages/tools/aws-s3/README.md)
|
|
158
|
+
- [@sisu-ai/tool-azure-blob](packages/tools/azure-blob/README.md)
|
|
159
|
+
- [@sisu-ai/tool-extract-urls](packages/tools/extract-urls/README.md)
|
|
160
|
+
- [@sisu-ai/tool-github-projects](packages/tools/github-projects/README.md)
|
|
161
|
+
- [@sisu-ai/tool-summarize-text](packages/tools/summarize-text/README.md)
|
|
162
|
+
- [@sisu-ai/tool-terminal](packages/tools/terminal/README.md)
|
|
163
|
+
- [@sisu-ai/tool-vec-chroma](packages/tools/vec-chroma/README.md)
|
|
164
|
+
- [@sisu-ai/tool-web-fetch](packages/tools/web-fetch/README.md)
|
|
165
|
+
- [@sisu-ai/tool-web-search-duckduckgo](packages/tools/web-search-duckduckgo/README.md)
|
|
166
|
+
- [@sisu-ai/tool-web-search-google](packages/tools/web-search-google/README.md)
|
|
167
|
+
- [@sisu-ai/tool-web-search-openai](packages/tools/web-search-openai/README.md)
|
|
168
|
+
- [@sisu-ai/tool-wikipedia](packages/tools/wikipedia/README.md)
|
|
169
|
+
</details>
|
|
170
|
+
|
|
171
|
+
<details>
|
|
172
|
+
<summary>All examples</summary>
|
|
173
|
+
|
|
174
|
+
**Anthropic** — [hello](examples/anthropic-hello/README.md) · [control-flow](examples/anthropic-control-flow/README.md) · [stream](examples/anthropic-stream/README.md) · [weather](examples/anthropic-weather/README.md)
|
|
175
|
+
|
|
176
|
+
**Ollama** — [hello](examples/ollama-hello/README.md) · [stream](examples/ollama-stream/README.md) · [vision](examples/ollama-vision/README.md) · [weather](examples/ollama-weather/README.md) · [web-search](examples/ollama-web-search/README.md)
|
|
177
|
+
|
|
178
|
+
**OpenAI** — [hello](examples/openai-hello/README.md) · [weather](examples/openai-weather/README.md) · [stream](examples/openai-stream/README.md) · [vision](examples/openai-vision/README.md) · [reasoning](examples/openai-reasoning/README.md) · [react](examples/openai-react/README.md) · [control-flow](examples/openai-control-flow/README.md) · [branch](examples/openai-branch/README.md) · [parallel](examples/openai-parallel/README.md) · [graph](examples/openai-graph/README.md) · [orchestration](examples/openai-orchestration/README.md) · [orchestration-adaptive](examples/openai-orchestration-adaptive/README.md) · [guardrails](examples/openai-guardrails/README.md) · [error-handling](examples/openai-error-handling/README.md) · [rag-chroma](examples/openai-rag-chroma/README.md) · [web-search](examples/openai-web-search/README.md) · [web-fetch](examples/openai-web-fetch/README.md) · [wikipedia](examples/openai-wikipedia/README.md) · [terminal](examples/openai-terminal/README.md) · [github-projects](examples/openai-github-projects/README.md) · [server](examples/openai-server/README.md) · [aws-s3](examples/openai-aws-s3/README.md) · [azure-blob](examples/openai-azure-blob/README.md)
|
|
179
|
+
</details>
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
We build Sisu in the open. Contributions welcome.
|
|
186
|
+
|
|
187
|
+
[Contributing Guide](CONTRIBUTING.md) · [Report a Bug](https://github.com/finger-gun/sisu/issues/new?template=bug_report.md) · [Request a Feature](https://github.com/finger-gun/sisu/issues/new?template=feature_request.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
<div align="center">
|
|
192
|
+
|
|
193
|
+
**[Star on GitHub](https://github.com/finger-gun/sisu)** if Sisu helps you build better agents.
|
|
194
|
+
|
|
195
|
+
*Quiet, determined, relentlessly useful.*
|
|
196
|
+
|
|
197
|
+
[Apache 2.0 License](LICENSE)
|
|
198
|
+
|
|
199
|
+
</div>
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import { firstConfigValue } from
|
|
1
|
+
import { firstConfigValue } from "@sisu-ai/core";
|
|
2
2
|
export function ollamaAdapter(opts) {
|
|
3
|
-
const envBase = firstConfigValue([
|
|
4
|
-
const baseUrl = (opts.baseUrl ?? envBase ??
|
|
3
|
+
const envBase = firstConfigValue(["OLLAMA_BASE_URL", "BASE_URL"]);
|
|
4
|
+
const baseUrl = (opts.baseUrl ?? envBase ?? "http://localhost:11434").replace(/\/$/, "");
|
|
5
5
|
const modelName = `ollama:${opts.model}`;
|
|
6
|
-
|
|
6
|
+
const generate = ((messages, genOpts) => {
|
|
7
7
|
// Map messages to Ollama format; include assistant tool_calls and tool messages
|
|
8
8
|
async function mapMessagesWithImages() {
|
|
9
9
|
const out = [];
|
|
10
10
|
for (const m of messages) {
|
|
11
11
|
const base = { role: m.role };
|
|
12
12
|
const anyM = m;
|
|
13
|
-
if (m.role ===
|
|
14
|
-
base.tool_calls = anyM.tool_calls.map((tc) => ({
|
|
13
|
+
if (m.role === "assistant" && Array.isArray(anyM.tool_calls)) {
|
|
14
|
+
base.tool_calls = anyM.tool_calls.map((tc) => ({
|
|
15
|
+
id: tc.id,
|
|
16
|
+
type: "function",
|
|
17
|
+
function: { name: tc.name, arguments: tc.arguments ?? {} },
|
|
18
|
+
}));
|
|
15
19
|
const ti = buildTextAndImages(anyM);
|
|
16
|
-
base.content =
|
|
20
|
+
base.content =
|
|
21
|
+
ti.content ?? (m.content !== undefined ? m.content : null);
|
|
17
22
|
if (ti.images?.length)
|
|
18
23
|
base.images = await toBase64Images(ti.images);
|
|
19
24
|
}
|
|
20
|
-
else if (m.role ===
|
|
21
|
-
base.content = String(m.content ??
|
|
25
|
+
else if (m.role === "tool") {
|
|
26
|
+
base.content = String(m.content ?? "");
|
|
22
27
|
if (m.tool_call_id)
|
|
23
28
|
base.tool_call_id = m.tool_call_id;
|
|
24
29
|
if (m.name && !m.tool_call_id)
|
|
@@ -26,7 +31,7 @@ export function ollamaAdapter(opts) {
|
|
|
26
31
|
}
|
|
27
32
|
else {
|
|
28
33
|
const ti = buildTextAndImages(anyM);
|
|
29
|
-
base.content = ti.content ??
|
|
34
|
+
base.content = ti.content ?? m.content ?? "";
|
|
30
35
|
if (ti.images?.length)
|
|
31
36
|
base.images = await toBase64Images(ti.images);
|
|
32
37
|
if (m.name)
|
|
@@ -40,14 +45,17 @@ export function ollamaAdapter(opts) {
|
|
|
40
45
|
return (async function* () {
|
|
41
46
|
const toolsParam = (genOpts?.tools ?? []).map(toOllamaTool);
|
|
42
47
|
const mapped = await mapMessagesWithImages();
|
|
43
|
-
const baseBody = {
|
|
48
|
+
const baseBody = {
|
|
49
|
+
model: opts.model,
|
|
50
|
+
messages: mapped,
|
|
51
|
+
};
|
|
44
52
|
if (toolsParam.length)
|
|
45
53
|
baseBody.tools = toolsParam;
|
|
46
54
|
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
47
|
-
method:
|
|
55
|
+
method: "POST",
|
|
48
56
|
headers: {
|
|
49
|
-
|
|
50
|
-
Accept:
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json",
|
|
51
59
|
...(opts.headers ?? {}),
|
|
52
60
|
},
|
|
53
61
|
body: JSON.stringify({ ...baseBody, stream: true }),
|
|
@@ -57,30 +65,33 @@ export function ollamaAdapter(opts) {
|
|
|
57
65
|
throw new Error(`Ollama API error: ${res.status} ${res.statusText} — ${String(err).slice(0, 500)}`);
|
|
58
66
|
}
|
|
59
67
|
const decoder = new TextDecoder();
|
|
60
|
-
let buf =
|
|
61
|
-
let full =
|
|
68
|
+
let buf = "";
|
|
69
|
+
let full = "";
|
|
62
70
|
for await (const chunk of res.body) {
|
|
63
|
-
const piece = typeof chunk ===
|
|
71
|
+
const piece = typeof chunk === "string" ? chunk : decoder.decode(chunk);
|
|
64
72
|
buf += piece;
|
|
65
|
-
const lines = buf.split(
|
|
66
|
-
buf = lines.pop() ??
|
|
73
|
+
const lines = buf.split("\n");
|
|
74
|
+
buf = lines.pop() ?? "";
|
|
67
75
|
for (const line of lines) {
|
|
68
76
|
if (!line.trim())
|
|
69
77
|
continue;
|
|
70
78
|
try {
|
|
71
79
|
const j = JSON.parse(line);
|
|
72
80
|
if (j.done) {
|
|
73
|
-
yield {
|
|
81
|
+
yield {
|
|
82
|
+
type: "assistant_message",
|
|
83
|
+
message: { role: "assistant", content: full },
|
|
84
|
+
};
|
|
74
85
|
return;
|
|
75
86
|
}
|
|
76
87
|
const token = j.message?.content;
|
|
77
|
-
if (typeof token ===
|
|
88
|
+
if (typeof token === "string" && token) {
|
|
78
89
|
full += token;
|
|
79
|
-
yield { type:
|
|
90
|
+
yield { type: "token", token };
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
catch (e) {
|
|
83
|
-
console.error(
|
|
94
|
+
console.error("[DEBUG_LLM] stream_parse_error", { error: e });
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
}
|
|
@@ -90,14 +101,17 @@ export function ollamaAdapter(opts) {
|
|
|
90
101
|
return (async () => {
|
|
91
102
|
const toolsParam = (genOpts?.tools ?? []).map(toOllamaTool);
|
|
92
103
|
const mapped = await mapMessagesWithImages();
|
|
93
|
-
const baseBody = {
|
|
104
|
+
const baseBody = {
|
|
105
|
+
model: opts.model,
|
|
106
|
+
messages: mapped,
|
|
107
|
+
};
|
|
94
108
|
if (toolsParam.length)
|
|
95
109
|
baseBody.tools = toolsParam;
|
|
96
110
|
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
97
|
-
method:
|
|
111
|
+
method: "POST",
|
|
98
112
|
headers: {
|
|
99
|
-
|
|
100
|
-
Accept:
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
Accept: "application/json",
|
|
101
115
|
...(opts.headers ?? {}),
|
|
102
116
|
},
|
|
103
117
|
body: JSON.stringify({ ...baseBody, stream: false }),
|
|
@@ -110,31 +124,40 @@ export function ollamaAdapter(opts) {
|
|
|
110
124
|
details = j.error ?? j.message ?? raw;
|
|
111
125
|
}
|
|
112
126
|
catch (e) {
|
|
113
|
-
console.error(
|
|
127
|
+
console.error("[DEBUG_LLM] request_error", { error: e });
|
|
114
128
|
}
|
|
115
129
|
throw new Error(`Ollama API error: ${res.status} ${res.statusText} — ${String(details).slice(0, 500)}`);
|
|
116
130
|
}
|
|
117
131
|
const data = raw ? JSON.parse(raw) : {};
|
|
118
|
-
const choice = data
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
132
|
+
const choice = data
|
|
133
|
+
.message ?? {};
|
|
134
|
+
const content = choice.content;
|
|
135
|
+
const tcs = Array.isArray(choice.tool_calls)
|
|
136
|
+
? choice.tool_calls
|
|
137
|
+
.map((tc) => ({
|
|
138
|
+
id: tc.id ?? "",
|
|
139
|
+
name: tc.function?.name ?? "",
|
|
140
|
+
arguments: safeJson(tc.function?.arguments),
|
|
141
|
+
}))
|
|
142
|
+
.filter((tc) => tc.id && tc.name)
|
|
122
143
|
: undefined;
|
|
123
|
-
const out = {
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
const out = {
|
|
145
|
+
role: "assistant",
|
|
146
|
+
content: typeof content === "string" ? content : "",
|
|
147
|
+
...(tcs ? { tool_calls: tcs } : {}),
|
|
148
|
+
};
|
|
126
149
|
return { message: out };
|
|
127
150
|
})();
|
|
128
|
-
}
|
|
151
|
+
});
|
|
129
152
|
return {
|
|
130
153
|
name: modelName,
|
|
131
154
|
capabilities: { functionCall: true, streaming: true },
|
|
132
|
-
generate
|
|
155
|
+
generate,
|
|
133
156
|
};
|
|
134
157
|
}
|
|
135
158
|
function toOllamaTool(tool) {
|
|
136
159
|
return {
|
|
137
|
-
type:
|
|
160
|
+
type: "function",
|
|
138
161
|
function: {
|
|
139
162
|
name: tool.name,
|
|
140
163
|
description: tool.description,
|
|
@@ -144,34 +167,43 @@ function toOllamaTool(tool) {
|
|
|
144
167
|
}
|
|
145
168
|
function toJsonSchema(schema) {
|
|
146
169
|
if (!schema)
|
|
147
|
-
return { type:
|
|
170
|
+
return { type: "object" };
|
|
148
171
|
const t = schema?._def?.typeName;
|
|
149
|
-
if (t ===
|
|
150
|
-
return { type:
|
|
151
|
-
if (t ===
|
|
152
|
-
return { type:
|
|
153
|
-
if (t ===
|
|
154
|
-
return { type:
|
|
155
|
-
if (t ===
|
|
156
|
-
return {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
if (t === "ZodString")
|
|
173
|
+
return { type: "string" };
|
|
174
|
+
if (t === "ZodNumber")
|
|
175
|
+
return { type: "number" };
|
|
176
|
+
if (t === "ZodBoolean")
|
|
177
|
+
return { type: "boolean" };
|
|
178
|
+
if (t === "ZodArray")
|
|
179
|
+
return {
|
|
180
|
+
type: "array",
|
|
181
|
+
items: toJsonSchema(schema?._def?.type),
|
|
182
|
+
};
|
|
183
|
+
if (t === "ZodOptional")
|
|
184
|
+
return toJsonSchema(schema?._def?.innerType);
|
|
185
|
+
if (t === "ZodObject") {
|
|
186
|
+
const shape = typeof schema?._def?.shape === "function"
|
|
187
|
+
? schema._def?.shape?.()
|
|
188
|
+
: schema?._def?.shape;
|
|
161
189
|
const props = {};
|
|
162
190
|
const required = [];
|
|
163
191
|
for (const [key, val] of Object.entries(shape ?? {})) {
|
|
164
192
|
props[key] = toJsonSchema(val);
|
|
165
193
|
const innerTypeName = val?._def?.typeName;
|
|
166
|
-
if (innerTypeName !==
|
|
194
|
+
if (innerTypeName !== "ZodOptional" && innerTypeName !== "ZodDefault")
|
|
167
195
|
required.push(key);
|
|
168
196
|
}
|
|
169
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: props,
|
|
200
|
+
...(required.length ? { required } : {}),
|
|
201
|
+
};
|
|
170
202
|
}
|
|
171
|
-
return { type:
|
|
203
|
+
return { type: "object" };
|
|
172
204
|
}
|
|
173
205
|
function safeJson(s) {
|
|
174
|
-
if (typeof s !==
|
|
206
|
+
if (typeof s !== "string")
|
|
175
207
|
return s;
|
|
176
208
|
try {
|
|
177
209
|
return JSON.parse(s);
|
|
@@ -183,49 +215,56 @@ function safeJson(s) {
|
|
|
183
215
|
// Accept OpenAI-style content parts or convenience fields and map to
|
|
184
216
|
// Ollama's expected shape: { content: string, images?: string[] }
|
|
185
217
|
function buildTextAndImages(m) {
|
|
186
|
-
if (!m || typeof m !==
|
|
218
|
+
if (!m || typeof m !== "object")
|
|
187
219
|
return {};
|
|
188
220
|
const obj = m;
|
|
189
221
|
// If content is parts, normalize
|
|
190
222
|
if (Array.isArray(obj.content) || Array.isArray(obj.contentParts)) {
|
|
191
|
-
const parts = Array.isArray(obj.content)
|
|
223
|
+
const parts = Array.isArray(obj.content)
|
|
224
|
+
? obj.content
|
|
225
|
+
: obj.contentParts;
|
|
192
226
|
const texts = [];
|
|
193
227
|
const images = [];
|
|
194
228
|
for (const p of parts) {
|
|
195
|
-
if (typeof p ===
|
|
229
|
+
if (typeof p === "string") {
|
|
196
230
|
texts.push(p);
|
|
197
231
|
continue;
|
|
198
232
|
}
|
|
199
|
-
if (!p || typeof p !==
|
|
233
|
+
if (!p || typeof p !== "object")
|
|
200
234
|
continue;
|
|
201
235
|
const po = p;
|
|
202
236
|
const t = po.type;
|
|
203
|
-
if (t ===
|
|
237
|
+
if (t === "text" && typeof po.text === "string") {
|
|
204
238
|
texts.push(po.text);
|
|
205
239
|
continue;
|
|
206
240
|
}
|
|
207
|
-
if (t ===
|
|
241
|
+
if (t === "image_url") {
|
|
208
242
|
const iu = po.image_url;
|
|
209
|
-
if (typeof iu ===
|
|
243
|
+
if (typeof iu === "string")
|
|
210
244
|
images.push(iu);
|
|
211
|
-
else if (iu &&
|
|
245
|
+
else if (iu &&
|
|
246
|
+
typeof iu === "object" &&
|
|
247
|
+
typeof iu.url === "string")
|
|
212
248
|
images.push(String(iu.url));
|
|
213
249
|
continue;
|
|
214
250
|
}
|
|
215
|
-
if (t ===
|
|
251
|
+
if (t === "image" && typeof po.url === "string") {
|
|
216
252
|
images.push(String(po.url));
|
|
217
253
|
continue;
|
|
218
254
|
}
|
|
219
|
-
if (typeof po.image_url ===
|
|
255
|
+
if (typeof po.image_url === "string") {
|
|
220
256
|
images.push(String(po.image_url));
|
|
221
257
|
continue;
|
|
222
258
|
}
|
|
223
|
-
if (typeof po.image ===
|
|
259
|
+
if (typeof po.image === "string") {
|
|
224
260
|
images.push(String(po.image));
|
|
225
261
|
continue;
|
|
226
262
|
}
|
|
227
263
|
}
|
|
228
|
-
return {
|
|
264
|
+
return {
|
|
265
|
+
content: texts.join("\n\n"),
|
|
266
|
+
images: images.length ? images : undefined,
|
|
267
|
+
};
|
|
229
268
|
}
|
|
230
269
|
// Otherwise, use content string (if any) and collect convenience images
|
|
231
270
|
const images = [];
|
|
@@ -233,11 +272,11 @@ function buildTextAndImages(m) {
|
|
|
233
272
|
images.push(...obj.images);
|
|
234
273
|
if (Array.isArray(obj.image_urls))
|
|
235
274
|
images.push(...obj.image_urls);
|
|
236
|
-
if (typeof obj.image_url ===
|
|
275
|
+
if (typeof obj.image_url === "string")
|
|
237
276
|
images.push(obj.image_url);
|
|
238
|
-
if (typeof obj.image ===
|
|
277
|
+
if (typeof obj.image === "string")
|
|
239
278
|
images.push(obj.image);
|
|
240
|
-
const content = typeof obj.content ===
|
|
279
|
+
const content = typeof obj.content === "string" ? obj.content : undefined;
|
|
241
280
|
return { content, images: images.length ? images : undefined };
|
|
242
281
|
}
|
|
243
282
|
async function toBase64Images(images) {
|
|
@@ -253,11 +292,11 @@ function isDataUrl(s) {
|
|
|
253
292
|
return /^data:/i.test(s);
|
|
254
293
|
}
|
|
255
294
|
function fromDataUrl(s) {
|
|
256
|
-
const i = s.indexOf(
|
|
257
|
-
return i >= 0 ? s.slice(i + 1) :
|
|
295
|
+
const i = s.indexOf(",");
|
|
296
|
+
return i >= 0 ? s.slice(i + 1) : "";
|
|
258
297
|
}
|
|
259
298
|
function isProbablyBase64(s) {
|
|
260
|
-
if (!s || /[
|
|
299
|
+
if (!s || /[:/]/.test(s))
|
|
261
300
|
return false; // exclude URLs
|
|
262
301
|
// Basic base64 check: valid chars and length % 4 == 0
|
|
263
302
|
if (s.length % 4 !== 0)
|
|
@@ -272,7 +311,7 @@ async function toBase64(src) {
|
|
|
272
311
|
if (!res.ok)
|
|
273
312
|
throw new Error(`Failed to fetch image: ${res.status} ${res.statusText}`);
|
|
274
313
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
275
|
-
return buf.toString(
|
|
314
|
+
return buf.toString("base64");
|
|
276
315
|
}
|
|
277
316
|
return isProbablyBase64(src) ? src : src;
|
|
278
317
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sisu-ai/adapter-ollama",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"url": "https://github.com/finger-gun/sisu/issues"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@sisu-ai/core": "^2.3.
|
|
24
|
+
"@sisu-ai/core": "^2.3.2"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"sisu",
|