aiwcli 0.12.3 → 0.12.7
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/bin/dev.cmd +3 -3
- package/bin/dev.js +16 -16
- package/bin/run.cmd +3 -3
- package/bin/run.js +21 -21
- package/dist/commands/branch.js +7 -2
- package/dist/lib/bmad-installer.js +37 -37
- package/dist/lib/terminal.d.ts +2 -0
- package/dist/lib/terminal.js +57 -7
- package/dist/templates/CLAUDE.md +205 -205
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +12 -64
- package/dist/templates/_shared/.claude/commands/handoff.md +12 -198
- package/dist/templates/_shared/.claude/settings.json +65 -65
- package/dist/templates/_shared/.codex/workflows/handoff.md +226 -226
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +226 -226
- package/dist/templates/_shared/handoff-system/CLAUDE.md +421 -0
- package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/document-generator.ts +215 -216
- package/dist/templates/_shared/{lib-ts/handoff → handoff-system/lib}/handoff-reader.ts +157 -158
- package/dist/templates/_shared/{scripts → handoff-system/scripts}/resume_handoff.ts +373 -373
- package/dist/templates/_shared/{scripts → handoff-system/scripts}/save_handoff.ts +469 -358
- package/dist/templates/_shared/handoff-system/workflows/handoff-resume.md +66 -0
- package/dist/templates/_shared/{workflows → handoff-system/workflows}/handoff.md +254 -254
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -2
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -159
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -147
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +128 -128
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -49
- package/dist/templates/_shared/hooks-ts/session_end.ts +196 -183
- package/dist/templates/_shared/hooks-ts/session_start.ts +163 -151
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -48
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -74
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +93 -93
- package/dist/templates/_shared/lib-ts/CLAUDE.md +367 -367
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -138
- package/dist/templates/_shared/lib-ts/base/constants.ts +303 -303
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -58
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +582 -582
- package/dist/templates/_shared/lib-ts/base/inference.ts +301 -301
- package/dist/templates/_shared/lib-ts/base/logger.ts +247 -247
- package/dist/templates/_shared/lib-ts/base/state-io.ts +202 -130
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -184
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +56 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -184
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +566 -560
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +524 -515
- package/dist/templates/_shared/lib-ts/context/context-store.ts +712 -668
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +312 -312
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -185
- package/dist/templates/_shared/lib-ts/package.json +20 -20
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -102
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +58 -58
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -13
- package/dist/templates/_shared/lib-ts/types.ts +186 -180
- package/dist/templates/_shared/scripts/resolve_context.ts +33 -33
- package/dist/templates/_shared/scripts/status_line.ts +690 -690
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/ask.md +136 -136
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/index.md +21 -21
- package/dist/templates/cc-native/.claude/commands/{rlm → cc-native/rlm}/overview.md +56 -56
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -10
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -8
- package/dist/templates/cc-native/CC-NATIVE-README.md +189 -189
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +304 -304
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +143 -143
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +213 -213
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -70
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +96 -96
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +247 -247
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -76
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -54
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -51
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -53
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -61
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -163
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -156
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -597
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -26
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -107
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +319 -319
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -144
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -57
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +79 -79
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -132
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -168
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +70 -70
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -130
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -80
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -41
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -101
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +511 -511
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +71 -71
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -217
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +12 -12
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +66 -65
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -39
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +196 -195
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +21 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -480
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -287
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -148
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -54
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -58
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -208
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -460
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +446 -447
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -280
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -274
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -278
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +275 -275
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -72
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -9
- package/oclif.manifest.json +1 -1
- package/package.json +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +0 -3
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ollama HTTP client for local embeddings.
|
|
3
|
-
*
|
|
4
|
-
* Uses nomic-embed-text via Ollama's /api/embed endpoint.
|
|
5
|
-
* Zero API cost, fast, private.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import {
|
|
10
|
-
OLLAMA_BASE_URL,
|
|
11
|
-
OLLAMA_EMBED_MODEL,
|
|
12
|
-
EMBED_DIMENSIONS,
|
|
13
|
-
HYDE_OLLAMA_MODEL,
|
|
14
|
-
} from "./types.js";
|
|
15
|
-
import { logDebug, logError } from "./logger.js";
|
|
16
|
-
|
|
17
|
-
const HOOK_NAME = "rlm_ollama";
|
|
18
|
-
const BATCH_SIZE = 32;
|
|
19
|
-
|
|
20
|
-
// Zod schemas for runtime validation
|
|
21
|
-
const OllamaTagsResponseSchema = z.object({
|
|
22
|
-
models: z.array(z.object({ name: z.string() })).optional(),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const OllamaEmbedResponseSchema = z.object({
|
|
26
|
-
embeddings: z.array(z.array(z.number())),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export interface OllamaConfig {
|
|
30
|
-
baseUrl: string;
|
|
31
|
-
model: string;
|
|
32
|
-
dimensions: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const DEFAULT_CONFIG: OllamaConfig = {
|
|
36
|
-
baseUrl: OLLAMA_BASE_URL,
|
|
37
|
-
model: OLLAMA_EMBED_MODEL,
|
|
38
|
-
dimensions: EMBED_DIMENSIONS,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export interface HealthResult {
|
|
42
|
-
ok: boolean;
|
|
43
|
-
error?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Check if Ollama is running and the embedding model is available.
|
|
48
|
-
*/
|
|
49
|
-
export async function checkOllamaHealth(
|
|
50
|
-
config: Partial<OllamaConfig> = {},
|
|
51
|
-
): Promise<HealthResult> {
|
|
52
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
53
|
-
try {
|
|
54
|
-
const resp = await fetch(`${cfg.baseUrl}/api/tags`, {
|
|
55
|
-
signal: AbortSignal.timeout(5_000),
|
|
56
|
-
});
|
|
57
|
-
if (!resp.ok) {
|
|
58
|
-
return { ok: false, error: `Ollama responded with ${resp.status}` };
|
|
59
|
-
}
|
|
60
|
-
const json = await resp.json();
|
|
61
|
-
const parseResult = OllamaTagsResponseSchema.safeParse(json);
|
|
62
|
-
if (!parseResult.success) {
|
|
63
|
-
return { ok: false, error: `Invalid Ollama API response: ${parseResult.error.message}` };
|
|
64
|
-
}
|
|
65
|
-
const models = parseResult.data.models ?? [];
|
|
66
|
-
const found = models.some(
|
|
67
|
-
(m) => m.name === cfg.model || m.name.startsWith(`${cfg.model}:`),
|
|
68
|
-
);
|
|
69
|
-
if (!found) {
|
|
70
|
-
const available = models.map((m) => m.name).join(", ") || "none";
|
|
71
|
-
return {
|
|
72
|
-
ok: false,
|
|
73
|
-
error: `Model "${cfg.model}" not found. Available: ${available}. Run: ollama pull ${cfg.model}`,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return { ok: true };
|
|
77
|
-
} catch (e: unknown) {
|
|
78
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
79
|
-
return {
|
|
80
|
-
ok: false,
|
|
81
|
-
error: `Cannot reach Ollama at ${cfg.baseUrl}: ${msg}. Is Ollama running?`,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Embed multiple texts via Ollama /api/embed.
|
|
88
|
-
* Batches at BATCH_SIZE (Ollama processes sequentially internally).
|
|
89
|
-
* Returns one Float32Array per input text.
|
|
90
|
-
*/
|
|
91
|
-
export async function embed(
|
|
92
|
-
texts: string[],
|
|
93
|
-
config: Partial<OllamaConfig> = {},
|
|
94
|
-
): Promise<Float32Array[]> {
|
|
95
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
96
|
-
const results: Float32Array[] = [];
|
|
97
|
-
|
|
98
|
-
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
99
|
-
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
100
|
-
logDebug(HOOK_NAME, `Embedding batch ${i / BATCH_SIZE + 1} (${batch.length} texts)`);
|
|
101
|
-
|
|
102
|
-
const resp = await fetch(`${cfg.baseUrl}/api/embed`, {
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "Content-Type": "application/json" },
|
|
105
|
-
body: JSON.stringify({ model: cfg.model, input: batch }),
|
|
106
|
-
signal: AbortSignal.timeout(30_000),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
if (!resp.ok) {
|
|
110
|
-
const body = await resp.text().catch(() => "");
|
|
111
|
-
throw new Error(`Ollama embed failed (${resp.status}): ${body}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const json = await resp.json();
|
|
115
|
-
const parseResult = OllamaEmbedResponseSchema.safeParse(json);
|
|
116
|
-
if (!parseResult.success) {
|
|
117
|
-
throw new Error(`Invalid Ollama embed response: ${parseResult.error.message}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const data = parseResult.data;
|
|
121
|
-
if (data.embeddings.length !== batch.length) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Expected ${batch.length} embeddings, got ${data.embeddings.length}`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
for (const vec of data.embeddings) {
|
|
128
|
-
results.push(new Float32Array(vec));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return results;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Embed a single text. Convenience wrapper around embed().
|
|
137
|
-
*/
|
|
138
|
-
export async function embedOne(
|
|
139
|
-
text: string,
|
|
140
|
-
config: Partial<OllamaConfig> = {},
|
|
141
|
-
): Promise<Float32Array> {
|
|
142
|
-
const results = await embed([text], config);
|
|
143
|
-
if (results.length === 0) {
|
|
144
|
-
throw new Error("Embedding failed: received empty result array");
|
|
145
|
-
}
|
|
146
|
-
return results[0];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Generate text using Ollama's /api/generate endpoint.
|
|
151
|
-
* Follows same pattern as embed()/embedOne() for consistency.
|
|
152
|
-
*/
|
|
153
|
-
export async function generateText(
|
|
154
|
-
prompt: string,
|
|
155
|
-
options?: {
|
|
156
|
-
systemPrompt?: string;
|
|
157
|
-
temperature?: number;
|
|
158
|
-
maxTokens?: number;
|
|
159
|
-
timeout?: number;
|
|
160
|
-
model?: string;
|
|
161
|
-
},
|
|
162
|
-
): Promise<{ success: boolean; text: string; error?: string; latency_ms: number }> {
|
|
163
|
-
const startTime = Date.now();
|
|
164
|
-
const model = options?.model ?? HYDE_OLLAMA_MODEL;
|
|
165
|
-
const baseUrl = process.env.OLLAMA_BASE_URL ?? OLLAMA_BASE_URL;
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
169
|
-
method: "POST",
|
|
170
|
-
headers: { "Content-Type": "application/json" },
|
|
171
|
-
body: JSON.stringify({
|
|
172
|
-
model,
|
|
173
|
-
prompt: options?.systemPrompt
|
|
174
|
-
? `${options.systemPrompt}\n\n${prompt}`
|
|
175
|
-
: prompt,
|
|
176
|
-
stream: false, // Non-streaming for simplicity
|
|
177
|
-
options: {
|
|
178
|
-
temperature: options?.temperature ?? 0.7,
|
|
179
|
-
num_predict: options?.maxTokens ?? 200,
|
|
180
|
-
},
|
|
181
|
-
}),
|
|
182
|
-
signal: AbortSignal.timeout(options?.timeout ?? 10_000),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (!response.ok) {
|
|
186
|
-
return {
|
|
187
|
-
success: false,
|
|
188
|
-
text: "",
|
|
189
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
190
|
-
latency_ms: Date.now() - startTime,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const data = await response.json();
|
|
195
|
-
return {
|
|
196
|
-
success: true,
|
|
197
|
-
text: data.response || "",
|
|
198
|
-
latency_ms: Date.now() - startTime,
|
|
199
|
-
};
|
|
200
|
-
} catch (e) {
|
|
201
|
-
return {
|
|
202
|
-
success: false,
|
|
203
|
-
text: "",
|
|
204
|
-
error: String(e),
|
|
205
|
-
latency_ms: Date.now() - startTime,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Ollama HTTP client for local embeddings.
|
|
3
|
+
*
|
|
4
|
+
* Uses nomic-embed-text via Ollama's /api/embed endpoint.
|
|
5
|
+
* Zero API cost, fast, private.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import {
|
|
10
|
+
OLLAMA_BASE_URL,
|
|
11
|
+
OLLAMA_EMBED_MODEL,
|
|
12
|
+
EMBED_DIMENSIONS,
|
|
13
|
+
HYDE_OLLAMA_MODEL,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
import { logDebug, logError } from "./logger.js";
|
|
16
|
+
|
|
17
|
+
const HOOK_NAME = "rlm_ollama";
|
|
18
|
+
const BATCH_SIZE = 32;
|
|
19
|
+
|
|
20
|
+
// Zod schemas for runtime validation
|
|
21
|
+
const OllamaTagsResponseSchema = z.object({
|
|
22
|
+
models: z.array(z.object({ name: z.string() })).optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const OllamaEmbedResponseSchema = z.object({
|
|
26
|
+
embeddings: z.array(z.array(z.number())),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export interface OllamaConfig {
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
model: string;
|
|
32
|
+
dimensions: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_CONFIG: OllamaConfig = {
|
|
36
|
+
baseUrl: OLLAMA_BASE_URL,
|
|
37
|
+
model: OLLAMA_EMBED_MODEL,
|
|
38
|
+
dimensions: EMBED_DIMENSIONS,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export interface HealthResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if Ollama is running and the embedding model is available.
|
|
48
|
+
*/
|
|
49
|
+
export async function checkOllamaHealth(
|
|
50
|
+
config: Partial<OllamaConfig> = {},
|
|
51
|
+
): Promise<HealthResult> {
|
|
52
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
53
|
+
try {
|
|
54
|
+
const resp = await fetch(`${cfg.baseUrl}/api/tags`, {
|
|
55
|
+
signal: AbortSignal.timeout(5_000),
|
|
56
|
+
});
|
|
57
|
+
if (!resp.ok) {
|
|
58
|
+
return { ok: false, error: `Ollama responded with ${resp.status}` };
|
|
59
|
+
}
|
|
60
|
+
const json = await resp.json();
|
|
61
|
+
const parseResult = OllamaTagsResponseSchema.safeParse(json);
|
|
62
|
+
if (!parseResult.success) {
|
|
63
|
+
return { ok: false, error: `Invalid Ollama API response: ${parseResult.error.message}` };
|
|
64
|
+
}
|
|
65
|
+
const models = parseResult.data.models ?? [];
|
|
66
|
+
const found = models.some(
|
|
67
|
+
(m) => m.name === cfg.model || m.name.startsWith(`${cfg.model}:`),
|
|
68
|
+
);
|
|
69
|
+
if (!found) {
|
|
70
|
+
const available = models.map((m) => m.name).join(", ") || "none";
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: `Model "${cfg.model}" not found. Available: ${available}. Run: ollama pull ${cfg.model}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { ok: true };
|
|
77
|
+
} catch (e: unknown) {
|
|
78
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
error: `Cannot reach Ollama at ${cfg.baseUrl}: ${msg}. Is Ollama running?`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Embed multiple texts via Ollama /api/embed.
|
|
88
|
+
* Batches at BATCH_SIZE (Ollama processes sequentially internally).
|
|
89
|
+
* Returns one Float32Array per input text.
|
|
90
|
+
*/
|
|
91
|
+
export async function embed(
|
|
92
|
+
texts: string[],
|
|
93
|
+
config: Partial<OllamaConfig> = {},
|
|
94
|
+
): Promise<Float32Array[]> {
|
|
95
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
96
|
+
const results: Float32Array[] = [];
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
99
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
100
|
+
logDebug(HOOK_NAME, `Embedding batch ${i / BATCH_SIZE + 1} (${batch.length} texts)`);
|
|
101
|
+
|
|
102
|
+
const resp = await fetch(`${cfg.baseUrl}/api/embed`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ model: cfg.model, input: batch }),
|
|
106
|
+
signal: AbortSignal.timeout(30_000),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!resp.ok) {
|
|
110
|
+
const body = await resp.text().catch(() => "");
|
|
111
|
+
throw new Error(`Ollama embed failed (${resp.status}): ${body}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const json = await resp.json();
|
|
115
|
+
const parseResult = OllamaEmbedResponseSchema.safeParse(json);
|
|
116
|
+
if (!parseResult.success) {
|
|
117
|
+
throw new Error(`Invalid Ollama embed response: ${parseResult.error.message}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const data = parseResult.data;
|
|
121
|
+
if (data.embeddings.length !== batch.length) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Expected ${batch.length} embeddings, got ${data.embeddings.length}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const vec of data.embeddings) {
|
|
128
|
+
results.push(new Float32Array(vec));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Embed a single text. Convenience wrapper around embed().
|
|
137
|
+
*/
|
|
138
|
+
export async function embedOne(
|
|
139
|
+
text: string,
|
|
140
|
+
config: Partial<OllamaConfig> = {},
|
|
141
|
+
): Promise<Float32Array> {
|
|
142
|
+
const results = await embed([text], config);
|
|
143
|
+
if (results.length === 0) {
|
|
144
|
+
throw new Error("Embedding failed: received empty result array");
|
|
145
|
+
}
|
|
146
|
+
return results[0];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate text using Ollama's /api/generate endpoint.
|
|
151
|
+
* Follows same pattern as embed()/embedOne() for consistency.
|
|
152
|
+
*/
|
|
153
|
+
export async function generateText(
|
|
154
|
+
prompt: string,
|
|
155
|
+
options?: {
|
|
156
|
+
systemPrompt?: string;
|
|
157
|
+
temperature?: number;
|
|
158
|
+
maxTokens?: number;
|
|
159
|
+
timeout?: number;
|
|
160
|
+
model?: string;
|
|
161
|
+
},
|
|
162
|
+
): Promise<{ success: boolean; text: string; error?: string; latency_ms: number }> {
|
|
163
|
+
const startTime = Date.now();
|
|
164
|
+
const model = options?.model ?? HYDE_OLLAMA_MODEL;
|
|
165
|
+
const baseUrl = process.env.OLLAMA_BASE_URL ?? OLLAMA_BASE_URL;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: { "Content-Type": "application/json" },
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
model,
|
|
173
|
+
prompt: options?.systemPrompt
|
|
174
|
+
? `${options.systemPrompt}\n\n${prompt}`
|
|
175
|
+
: prompt,
|
|
176
|
+
stream: false, // Non-streaming for simplicity
|
|
177
|
+
options: {
|
|
178
|
+
temperature: options?.temperature ?? 0.7,
|
|
179
|
+
num_predict: options?.maxTokens ?? 200,
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
signal: AbortSignal.timeout(options?.timeout ?? 10_000),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
text: "",
|
|
189
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
190
|
+
latency_ms: Date.now() - startTime,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const data = await response.json();
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
text: data.response || "",
|
|
198
|
+
latency_ms: Date.now() - startTime,
|
|
199
|
+
};
|
|
200
|
+
} catch (e) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
text: "",
|
|
204
|
+
error: String(e),
|
|
205
|
+
latency_ms: Date.now() - startTime,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|