@dex-ai/ollama 0.1.3
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/package.json +27 -0
- package/src/extension.ts +134 -0
- package/src/index.ts +22 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dex-ai/ollama",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Ollama provider for @dex-ai/sdk — thin wrapper over @dex-ai/openai.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"default": "./src/index.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@dex-ai/sdk": "^0.1.1",
|
|
20
|
+
"@dex-ai/openai": "^0.1.3"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org/"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/extension.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Extension — thin wrapper over @dex-ai/openai.
|
|
3
|
+
*
|
|
4
|
+
* Ollama exposes an OpenAI-compatible endpoint at /v1/chat/completions.
|
|
5
|
+
* Model discovery uses /api/tags (Ollama-native) to list local models.
|
|
6
|
+
* No API key required by default.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Extension, Model, ThinkingLevel } from "@dex-ai/sdk";
|
|
10
|
+
import { openaiExtension } from "@dex-ai/openai";
|
|
11
|
+
import type { OpenAIExtensionOptions, ModelConfig } from "@dex-ai/openai";
|
|
12
|
+
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
/* Options */
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
16
|
+
|
|
17
|
+
export interface OllamaExtensionOptions {
|
|
18
|
+
/** Base URL. Default: http://127.0.0.1:11434/v1 */
|
|
19
|
+
readonly baseUrl?: string;
|
|
20
|
+
/** Extension name. Default: "ollama". */
|
|
21
|
+
readonly name?: string;
|
|
22
|
+
/** Model IDs to register statically. Discovery adds more. */
|
|
23
|
+
readonly models?: ReadonlyArray<string | ModelConfig>;
|
|
24
|
+
/**
|
|
25
|
+
* Disable model discovery. Default: false (discovery enabled).
|
|
26
|
+
* When enabled, discovers models via Ollama's GET /api/tags endpoint.
|
|
27
|
+
*/
|
|
28
|
+
readonly noDiscovery?: boolean;
|
|
29
|
+
/** Emit raw-chunk parts during streaming. Default: false. */
|
|
30
|
+
readonly rawChunks?: boolean;
|
|
31
|
+
/** Fetch override. */
|
|
32
|
+
readonly fetch?: (url: string, init: RequestInit) => Promise<Response>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type { ModelConfig };
|
|
36
|
+
|
|
37
|
+
/* ------------------------------------------------------------------ */
|
|
38
|
+
/* Extension factory */
|
|
39
|
+
/* ------------------------------------------------------------------ */
|
|
40
|
+
|
|
41
|
+
export function ollamaExtension(
|
|
42
|
+
opts: OllamaExtensionOptions = {},
|
|
43
|
+
): Extension {
|
|
44
|
+
const extName = opts.name ?? "ollama";
|
|
45
|
+
const baseUrl =
|
|
46
|
+
opts.baseUrl ||
|
|
47
|
+
process.env.OLLAMA_BASE_URL ||
|
|
48
|
+
"http://127.0.0.1:11434/v1";
|
|
49
|
+
// Ollama's OpenAI-compat endpoint is at /v1, strip if caller passed full host
|
|
50
|
+
const openaiBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
51
|
+
// Ollama host without /v1 for native API calls
|
|
52
|
+
const ollamaHost = openaiBaseUrl.replace(/\/v1$/, "");
|
|
53
|
+
|
|
54
|
+
const doFetch =
|
|
55
|
+
opts.fetch ?? ((url: string, init: RequestInit) => fetch(url, init));
|
|
56
|
+
|
|
57
|
+
// Ollama doesn't need an API key, but openaiExtension requires one.
|
|
58
|
+
// Pass a dummy value — Ollama ignores the Authorization header.
|
|
59
|
+
const openaiOpts: OpenAIExtensionOptions = {
|
|
60
|
+
name: extName,
|
|
61
|
+
apiKey: "ollama",
|
|
62
|
+
baseUrl: openaiBaseUrl,
|
|
63
|
+
// Disable OpenAI-style /v1/models discovery — we use /api/tags instead
|
|
64
|
+
modelsPath: null,
|
|
65
|
+
...(opts.models !== undefined ? { models: opts.models } : {}),
|
|
66
|
+
...(opts.rawChunks !== undefined ? { rawChunks: opts.rawChunks } : {}),
|
|
67
|
+
...(opts.fetch !== undefined ? { fetch: opts.fetch } : {}),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const inner = openaiExtension(openaiOpts);
|
|
71
|
+
|
|
72
|
+
// If discovery disabled, return as-is
|
|
73
|
+
if (opts.noDiscovery) return inner;
|
|
74
|
+
|
|
75
|
+
// Wrap with Ollama-native model discovery
|
|
76
|
+
return {
|
|
77
|
+
...inner,
|
|
78
|
+
async init(actx) {
|
|
79
|
+
// Call inner init first (no-op since modelsPath is null)
|
|
80
|
+
if (inner.init) await inner.init(actx);
|
|
81
|
+
|
|
82
|
+
// Discover models via Ollama's /api/tags
|
|
83
|
+
try {
|
|
84
|
+
const res = await doFetch(`${ollamaHost}/api/tags`, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) return;
|
|
88
|
+
|
|
89
|
+
const json = (await res.json()) as {
|
|
90
|
+
models?: Array<{
|
|
91
|
+
name: string;
|
|
92
|
+
size?: number;
|
|
93
|
+
details?: {
|
|
94
|
+
parameter_size?: string;
|
|
95
|
+
family?: string;
|
|
96
|
+
};
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
const modelList = json.models ?? [];
|
|
100
|
+
|
|
101
|
+
const staticIds = new Set(
|
|
102
|
+
(inner.models ?? []).map((m: Model) => m.id),
|
|
103
|
+
);
|
|
104
|
+
const allModels = inner.models as Model[];
|
|
105
|
+
|
|
106
|
+
for (const m of modelList) {
|
|
107
|
+
if (!m.name || staticIds.has(m.name)) continue;
|
|
108
|
+
|
|
109
|
+
// Import createChatStream indirectly via openaiExtension
|
|
110
|
+
// Since we can't easily create new Model instances that use
|
|
111
|
+
// the inner openai stream, we create a mini openai extension
|
|
112
|
+
// for this model and grab the model from it.
|
|
113
|
+
const singleExt = openaiExtension({
|
|
114
|
+
name: extName,
|
|
115
|
+
apiKey: "ollama",
|
|
116
|
+
baseUrl: openaiBaseUrl,
|
|
117
|
+
modelsPath: null,
|
|
118
|
+
models: [m.name],
|
|
119
|
+
...(opts.rawChunks !== undefined
|
|
120
|
+
? { rawChunks: opts.rawChunks }
|
|
121
|
+
: {}),
|
|
122
|
+
...(opts.fetch !== undefined ? { fetch: opts.fetch } : {}),
|
|
123
|
+
});
|
|
124
|
+
const model = singleExt.models?.[0];
|
|
125
|
+
if (model) {
|
|
126
|
+
allModels.push(model);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// Discovery failed — proceed with static models only
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { ollamaExtension } from "./extension";
|
|
2
|
+
export type { OllamaExtensionOptions, ModelConfig } from "./extension";
|
|
3
|
+
|
|
4
|
+
import type { ProviderDescriptor } from "@dex-ai/sdk";
|
|
5
|
+
import { ollamaExtension } from "./extension";
|
|
6
|
+
|
|
7
|
+
export const descriptor: ProviderDescriptor = {
|
|
8
|
+
type: "ollama",
|
|
9
|
+
label: "Ollama (local)",
|
|
10
|
+
fields: [
|
|
11
|
+
{ key: "baseUrl", label: "Base URL", required: false, default: "http://127.0.0.1:11434/v1", hint: "Ollama server URL" },
|
|
12
|
+
{ key: "models", label: "Models", required: false, hint: "Comma-separated model IDs, or leave blank to auto-discover" },
|
|
13
|
+
],
|
|
14
|
+
create(config) {
|
|
15
|
+
const models = config.models as string[] | undefined;
|
|
16
|
+
return ollamaExtension({
|
|
17
|
+
...(config.name ? { name: config.name as string } : {}),
|
|
18
|
+
baseUrl: (config.baseUrl as string) ?? "http://127.0.0.1:11434/v1",
|
|
19
|
+
...(models ? { models } : {}),
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
};
|