@mmnto/cli 0.21.0 → 0.23.0
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/dist/commands/drift.d.ts +2 -0
- package/dist/commands/drift.d.ts.map +1 -0
- package/dist/commands/drift.js +45 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/drift.test.d.ts +2 -0
- package/dist/commands/drift.test.d.ts.map +1 -0
- package/dist/commands/drift.test.js +92 -0
- package/dist/commands/drift.test.js.map +1 -0
- package/dist/commands/extract.d.ts +22 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +119 -28
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/extract.test.js +216 -2
- package/dist/commands/extract.test.js.map +1 -1
- package/dist/commands/handoff.d.ts +2 -0
- package/dist/commands/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +61 -2
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/handoff.test.js +33 -1
- package/dist/commands/handoff.test.js.map +1 -1
- package/dist/commands/shield-learn.test.d.ts +2 -0
- package/dist/commands/shield-learn.test.d.ts.map +1 -0
- package/dist/commands/shield-learn.test.js +123 -0
- package/dist/commands/shield-learn.test.js.map +1 -0
- package/dist/commands/shield.d.ts +5 -0
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +154 -6
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/shield.test.js +28 -1
- package/dist/commands/shield.test.js.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/orchestrators/conformance.test.js +25 -1
- package/dist/orchestrators/conformance.test.js.map +1 -1
- package/dist/orchestrators/ollama-orchestrator.d.ts +13 -0
- package/dist/orchestrators/ollama-orchestrator.d.ts.map +1 -0
- package/dist/orchestrators/ollama-orchestrator.js +91 -0
- package/dist/orchestrators/ollama-orchestrator.js.map +1 -0
- package/dist/orchestrators/ollama-orchestrator.test.d.ts +2 -0
- package/dist/orchestrators/ollama-orchestrator.test.d.ts.map +1 -0
- package/dist/orchestrators/ollama-orchestrator.test.js +131 -0
- package/dist/orchestrators/ollama-orchestrator.test.js.map +1 -0
- package/dist/orchestrators/openai-orchestrator.d.ts +12 -0
- package/dist/orchestrators/openai-orchestrator.d.ts.map +1 -0
- package/dist/orchestrators/openai-orchestrator.js +70 -0
- package/dist/orchestrators/openai-orchestrator.js.map +1 -0
- package/dist/orchestrators/openai-orchestrator.test.d.ts +2 -0
- package/dist/orchestrators/openai-orchestrator.test.d.ts.map +1 -0
- package/dist/orchestrators/openai-orchestrator.test.js +114 -0
- package/dist/orchestrators/openai-orchestrator.test.js.map +1 -0
- package/dist/orchestrators/orchestrator.d.ts.map +1 -1
- package/dist/orchestrators/orchestrator.js +15 -1
- package/dist/orchestrators/orchestrator.js.map +1 -1
- package/dist/orchestrators/orchestrator.test.js +35 -0
- package/dist/orchestrators/orchestrator.test.js.map +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { log } from '../ui.js';
|
|
3
|
+
import { isQuotaError } from './orchestrator.js';
|
|
4
|
+
// ─── Constants ───────────────────────────────────────
|
|
5
|
+
const DEFAULT_BASE_URL = 'http://localhost:11434';
|
|
6
|
+
// ─── Response schema ────────────────────────────────
|
|
7
|
+
const OllamaChatResponseSchema = z.object({
|
|
8
|
+
message: z.object({ content: z.string().optional() }).optional(),
|
|
9
|
+
prompt_eval_count: z.number().optional(),
|
|
10
|
+
eval_count: z.number().optional(),
|
|
11
|
+
done: z.boolean().optional(),
|
|
12
|
+
done_reason: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
// ─── Native Ollama orchestrator ─────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Invoke Ollama's native /api/chat endpoint directly via fetch.
|
|
17
|
+
* Unlike the OpenAI-compatible adapter, this supports passing `num_ctx`
|
|
18
|
+
* to dynamically control context length (and VRAM usage).
|
|
19
|
+
*
|
|
20
|
+
* @see https://github.com/mmnto-ai/totem/issues/298
|
|
21
|
+
*/
|
|
22
|
+
export async function invokeOllamaOrchestrator(opts) {
|
|
23
|
+
const { prompt, model, tag, baseUrl, numCtx } = opts;
|
|
24
|
+
// Normalize base URL (strip trailing slash)
|
|
25
|
+
const base = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
26
|
+
const url = `${base}/api/chat`;
|
|
27
|
+
const ctxLabel = numCtx ? ` (num_ctx: ${numCtx})` : '';
|
|
28
|
+
log.info(tag, `Invoking Ollama at ${base}${ctxLabel} (this may take 15-60 seconds)...`);
|
|
29
|
+
const startMs = Date.now();
|
|
30
|
+
const body = {
|
|
31
|
+
model,
|
|
32
|
+
stream: false,
|
|
33
|
+
messages: [{ role: 'user', content: prompt }],
|
|
34
|
+
};
|
|
35
|
+
// Only inject num_ctx if explicitly configured
|
|
36
|
+
if (numCtx) {
|
|
37
|
+
body.options = { num_ctx: numCtx };
|
|
38
|
+
}
|
|
39
|
+
let response;
|
|
40
|
+
try {
|
|
41
|
+
response = await fetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
49
|
+
throw new Error(`[Totem Error] Cannot connect to Ollama at ${base}.\n` +
|
|
50
|
+
`Is Ollama running? Start it with: ollama serve\n` +
|
|
51
|
+
`Details: ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const errorBody = await response.text();
|
|
55
|
+
if (isQuotaError(Object.assign(new Error(errorBody), { status: response.status }))) {
|
|
56
|
+
const err = new Error(`Ollama rate limit: ${errorBody}`);
|
|
57
|
+
err.name = 'QuotaError';
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
// 500 errors from Ollama are often VRAM/context exhaustion
|
|
61
|
+
if (response.status >= 500) {
|
|
62
|
+
throw new Error(`[Totem Error] Ollama server error (${response.status}): ${errorBody}\n` +
|
|
63
|
+
(numCtx
|
|
64
|
+
? `Try lowering numCtx (currently ${numCtx}) in your orchestrator config.`
|
|
65
|
+
: `Try setting a smaller numCtx in your orchestrator config to limit VRAM usage.`));
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`[Totem Error] Ollama API error (${response.status}): ${errorBody}`);
|
|
68
|
+
}
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = await response.json();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`[Totem Error] Ollama returned invalid JSON. Is the model loaded?`);
|
|
75
|
+
}
|
|
76
|
+
const parsed = OllamaChatResponseSchema.safeParse(raw);
|
|
77
|
+
if (!parsed.success) {
|
|
78
|
+
throw new Error(`[Totem Error] Unexpected response from Ollama API.\n` +
|
|
79
|
+
`Validation: ${parsed.error.issues.map((i) => i.message).join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
const data = parsed.data;
|
|
82
|
+
const durationMs = Date.now() - startMs;
|
|
83
|
+
return {
|
|
84
|
+
content: data.message?.content ?? '',
|
|
85
|
+
inputTokens: data.prompt_eval_count ?? null,
|
|
86
|
+
outputTokens: data.eval_count ?? null,
|
|
87
|
+
durationMs,
|
|
88
|
+
finishReason: data.done_reason ?? (data.done ? 'stop' : undefined),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=ollama-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama-orchestrator.js","sourceRoot":"","sources":["../../src/orchestrators/ollama-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama-orchestrator.test.d.ts","sourceRoot":"","sources":["../../src/orchestrators/ollama-orchestrator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { invokeOllamaOrchestrator } from './ollama-orchestrator.js';
|
|
3
|
+
// ─── Mocks ──────────────────────────────────────────
|
|
4
|
+
vi.mock('../ui.js', () => ({
|
|
5
|
+
log: { info: vi.fn(), success: vi.fn(), warn: vi.fn(), error: vi.fn(), dim: vi.fn() },
|
|
6
|
+
}));
|
|
7
|
+
const mockFetch = vi.fn();
|
|
8
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
mockFetch.mockReset();
|
|
11
|
+
});
|
|
12
|
+
// ─── Tests ──────────────────────────────────────────
|
|
13
|
+
describe('invokeOllamaOrchestrator', () => {
|
|
14
|
+
const baseOpts = {
|
|
15
|
+
prompt: 'test prompt',
|
|
16
|
+
model: 'gemma2:27b',
|
|
17
|
+
cwd: '.',
|
|
18
|
+
tag: 'Test',
|
|
19
|
+
totemDir: '.totem',
|
|
20
|
+
};
|
|
21
|
+
const okResponse = (body) => new Response(JSON.stringify(body), {
|
|
22
|
+
status: 200,
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
});
|
|
25
|
+
it('returns structured result from Ollama', async () => {
|
|
26
|
+
mockFetch.mockResolvedValueOnce(okResponse({
|
|
27
|
+
message: { content: 'Hello from Ollama' },
|
|
28
|
+
prompt_eval_count: 150,
|
|
29
|
+
eval_count: 50,
|
|
30
|
+
done: true,
|
|
31
|
+
done_reason: 'stop',
|
|
32
|
+
}));
|
|
33
|
+
const result = await invokeOllamaOrchestrator(baseOpts);
|
|
34
|
+
expect(result.content).toBe('Hello from Ollama');
|
|
35
|
+
expect(result.inputTokens).toBe(150);
|
|
36
|
+
expect(result.outputTokens).toBe(50);
|
|
37
|
+
expect(result.finishReason).toBe('stop');
|
|
38
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
39
|
+
});
|
|
40
|
+
it('passes num_ctx in options when configured', async () => {
|
|
41
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
42
|
+
await invokeOllamaOrchestrator({ ...baseOpts, numCtx: 8192 });
|
|
43
|
+
const [, fetchOpts] = mockFetch.mock.calls[0];
|
|
44
|
+
const body = JSON.parse(fetchOpts.body);
|
|
45
|
+
expect(body.options).toEqual({ num_ctx: 8192 });
|
|
46
|
+
});
|
|
47
|
+
it('omits options.num_ctx when not configured', async () => {
|
|
48
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
49
|
+
await invokeOllamaOrchestrator(baseOpts);
|
|
50
|
+
const [, fetchOpts] = mockFetch.mock.calls[0];
|
|
51
|
+
const body = JSON.parse(fetchOpts.body);
|
|
52
|
+
expect(body.options).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
it('defaults baseUrl to localhost:11434', async () => {
|
|
55
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
56
|
+
await invokeOllamaOrchestrator(baseOpts);
|
|
57
|
+
const [url] = mockFetch.mock.calls[0];
|
|
58
|
+
expect(url).toBe('http://localhost:11434/api/chat');
|
|
59
|
+
});
|
|
60
|
+
it('uses custom baseUrl and strips trailing slash', async () => {
|
|
61
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
62
|
+
await invokeOllamaOrchestrator({ ...baseOpts, baseUrl: 'http://myserver:11434/' });
|
|
63
|
+
const [url] = mockFetch.mock.calls[0];
|
|
64
|
+
expect(url).toBe('http://myserver:11434/api/chat');
|
|
65
|
+
});
|
|
66
|
+
it('sends model and prompt in correct Ollama format', async () => {
|
|
67
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
68
|
+
await invokeOllamaOrchestrator(baseOpts);
|
|
69
|
+
const [, fetchOpts] = mockFetch.mock.calls[0];
|
|
70
|
+
const body = JSON.parse(fetchOpts.body);
|
|
71
|
+
expect(body.model).toBe('gemma2:27b');
|
|
72
|
+
expect(body.stream).toBe(false);
|
|
73
|
+
expect(body.messages).toEqual([{ role: 'user', content: 'test prompt' }]);
|
|
74
|
+
});
|
|
75
|
+
it('requests non-streaming response', async () => {
|
|
76
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'ok' }, done: true }));
|
|
77
|
+
await invokeOllamaOrchestrator(baseOpts);
|
|
78
|
+
const [, fetchOpts] = mockFetch.mock.calls[0];
|
|
79
|
+
const body = JSON.parse(fetchOpts.body);
|
|
80
|
+
expect(body.stream).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it('throws connection error when Ollama is unreachable', async () => {
|
|
83
|
+
mockFetch.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
84
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('Cannot connect to Ollama');
|
|
85
|
+
});
|
|
86
|
+
it('suggests ollama serve in connection error', async () => {
|
|
87
|
+
mockFetch.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
88
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('ollama serve');
|
|
89
|
+
});
|
|
90
|
+
it('throws VRAM-friendly error on 500 with numCtx', async () => {
|
|
91
|
+
mockFetch.mockResolvedValueOnce(new Response('out of memory', { status: 500 }));
|
|
92
|
+
await expect(invokeOllamaOrchestrator({ ...baseOpts, numCtx: 32768 })).rejects.toThrow('lowering numCtx');
|
|
93
|
+
});
|
|
94
|
+
it('throws QuotaError on 429 responses', async () => {
|
|
95
|
+
mockFetch.mockResolvedValueOnce(new Response('too many requests', { status: 429 }));
|
|
96
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toSatisfy((err) => {
|
|
97
|
+
return err.name === 'QuotaError' && err.message.includes('Ollama rate limit');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it('throws VRAM-friendly error on 500 without numCtx', async () => {
|
|
101
|
+
mockFetch.mockResolvedValueOnce(new Response('out of memory', { status: 500 }));
|
|
102
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('smaller numCtx');
|
|
103
|
+
});
|
|
104
|
+
it('handles missing token counts gracefully', async () => {
|
|
105
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: { content: 'no counts' }, done: true }));
|
|
106
|
+
const result = await invokeOllamaOrchestrator(baseOpts);
|
|
107
|
+
expect(result.inputTokens).toBeNull();
|
|
108
|
+
expect(result.outputTokens).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
it('handles missing message content', async () => {
|
|
111
|
+
mockFetch.mockResolvedValueOnce(okResponse({ message: {}, done: true }));
|
|
112
|
+
const result = await invokeOllamaOrchestrator(baseOpts);
|
|
113
|
+
expect(result.content).toBe('');
|
|
114
|
+
});
|
|
115
|
+
it('wraps non-500 API errors', async () => {
|
|
116
|
+
mockFetch.mockResolvedValueOnce(new Response('model not found', { status: 404 }));
|
|
117
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('[Totem Error] Ollama API error (404)');
|
|
118
|
+
});
|
|
119
|
+
it('throws friendly error on non-JSON response body', async () => {
|
|
120
|
+
mockFetch.mockResolvedValueOnce(new Response('not json at all', { status: 200 }));
|
|
121
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('Ollama returned invalid JSON');
|
|
122
|
+
});
|
|
123
|
+
it('throws friendly error on malformed JSON response', async () => {
|
|
124
|
+
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify([{ unexpected: 'array' }]), {
|
|
125
|
+
status: 200,
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
}));
|
|
128
|
+
await expect(invokeOllamaOrchestrator(baseOpts)).rejects.toThrow('Unexpected response from Ollama API');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=ollama-orchestrator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama-orchestrator.test.js","sourceRoot":"","sources":["../../src/orchestrators/ollama-orchestrator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OrchestratorInvokeOptions, OrchestratorResult } from './orchestrator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Invoke an OpenAI-compatible API via the `openai` SDK.
|
|
4
|
+
* Supports OpenAI, Ollama, LM Studio, Groq, OpenRouter, and any
|
|
5
|
+
* server implementing the `/v1/chat/completions` endpoint.
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/mmnto-ai/totem/issues/285
|
|
8
|
+
*/
|
|
9
|
+
export declare function invokeOpenAIOrchestrator(opts: OrchestratorInvokeOptions & {
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}): Promise<OrchestratorResult>;
|
|
12
|
+
//# sourceMappingURL=openai-orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrators/openai-orchestrator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AA6BvF;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,yBAAyB,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { log } from '../ui.js';
|
|
2
|
+
import { detectPackageManager, isQuotaError } from './orchestrator.js';
|
|
3
|
+
// ─── Constants ───────────────────────────────────────
|
|
4
|
+
const DEFAULT_MAX_TOKENS = 16_384;
|
|
5
|
+
/**
|
|
6
|
+
* Dummy API key for local OpenAI-compatible servers (Ollama, LM Studio)
|
|
7
|
+
* that don't require authentication but where the SDK mandates a key.
|
|
8
|
+
*/
|
|
9
|
+
const LOCAL_DUMMY_KEY = 'totem-local';
|
|
10
|
+
// ─── SDK loader (BYOSD) ─────────────────────────────
|
|
11
|
+
async function importOpenAISdk() {
|
|
12
|
+
try {
|
|
13
|
+
return (await import('openai')).default;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error('[Totem Error] OpenAI SDK (openai) is not installed.\n' +
|
|
17
|
+
`Install it with: ${detectPackageManager()} add openai\n` +
|
|
18
|
+
"Or use provider: 'shell' in your orchestrator config.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// ─── OpenAI-compatible API orchestrator ─────────────
|
|
22
|
+
/**
|
|
23
|
+
* Invoke an OpenAI-compatible API via the `openai` SDK.
|
|
24
|
+
* Supports OpenAI, Ollama, LM Studio, Groq, OpenRouter, and any
|
|
25
|
+
* server implementing the `/v1/chat/completions` endpoint.
|
|
26
|
+
*
|
|
27
|
+
* @see https://github.com/mmnto-ai/totem/issues/285
|
|
28
|
+
*/
|
|
29
|
+
export async function invokeOpenAIOrchestrator(opts) {
|
|
30
|
+
const { prompt, model, tag, baseUrl } = opts;
|
|
31
|
+
// For local servers, use a dummy key if none is set
|
|
32
|
+
const apiKey = process.env['OPENAI_API_KEY'] ?? (baseUrl ? LOCAL_DUMMY_KEY : undefined);
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
throw new Error('[Totem Error] No OpenAI API key found.\n' +
|
|
35
|
+
'Set OPENAI_API_KEY in your .env file, or add a baseUrl for local servers (Ollama, LM Studio).');
|
|
36
|
+
}
|
|
37
|
+
const OpenAI = await importOpenAISdk();
|
|
38
|
+
const client = new OpenAI({
|
|
39
|
+
apiKey,
|
|
40
|
+
...(baseUrl && { baseURL: baseUrl }),
|
|
41
|
+
});
|
|
42
|
+
const serverLabel = baseUrl ? `OpenAI-compatible API (${baseUrl})` : 'OpenAI API';
|
|
43
|
+
log.info(tag, `Invoking ${serverLabel} (this may take 15-60 seconds)...`);
|
|
44
|
+
const startMs = Date.now();
|
|
45
|
+
try {
|
|
46
|
+
const response = await client.chat.completions.create({
|
|
47
|
+
model,
|
|
48
|
+
max_tokens: DEFAULT_MAX_TOKENS,
|
|
49
|
+
messages: [{ role: 'user', content: prompt }],
|
|
50
|
+
});
|
|
51
|
+
const durationMs = Date.now() - startMs;
|
|
52
|
+
const choice = response.choices[0];
|
|
53
|
+
return {
|
|
54
|
+
content: choice?.message?.content ?? '',
|
|
55
|
+
inputTokens: response.usage?.prompt_tokens ?? null,
|
|
56
|
+
outputTokens: response.usage?.completion_tokens ?? null,
|
|
57
|
+
durationMs,
|
|
58
|
+
finishReason: choice?.finish_reason ?? undefined,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (isQuotaError(err)) {
|
|
63
|
+
err.name = 'QuotaError';
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67
|
+
throw new Error(`[Totem Error] OpenAI API call failed: ${msg}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=openai-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-orchestrator.js","sourceRoot":"","sources":["../../src/orchestrators/openai-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-orchestrator.test.d.ts","sourceRoot":"","sources":["../../src/orchestrators/openai-orchestrator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { invokeOpenAIOrchestrator } from './openai-orchestrator.js';
|
|
3
|
+
// ─── Mock SDK ───────────────────────────────────────
|
|
4
|
+
const { mockCreate } = vi.hoisted(() => ({
|
|
5
|
+
mockCreate: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('openai', () => ({
|
|
8
|
+
default: class {
|
|
9
|
+
chat = { completions: { create: mockCreate } };
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('../ui.js', () => ({
|
|
13
|
+
log: { info: vi.fn(), success: vi.fn(), warn: vi.fn(), error: vi.fn(), dim: vi.fn() },
|
|
14
|
+
}));
|
|
15
|
+
// ─── Tests ──────────────────────────────────────────
|
|
16
|
+
describe('invokeOpenAIOrchestrator', () => {
|
|
17
|
+
const baseOpts = {
|
|
18
|
+
prompt: 'test prompt',
|
|
19
|
+
model: 'gpt-4o',
|
|
20
|
+
cwd: '.',
|
|
21
|
+
tag: 'Test',
|
|
22
|
+
totemDir: '.totem',
|
|
23
|
+
};
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
process.env['OPENAI_API_KEY'] = 'test-key';
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
delete process.env['OPENAI_API_KEY'];
|
|
30
|
+
});
|
|
31
|
+
it('returns structured result from OpenAI API', async () => {
|
|
32
|
+
mockCreate.mockResolvedValueOnce({
|
|
33
|
+
choices: [{ message: { content: 'Hello from GPT' }, finish_reason: 'stop' }],
|
|
34
|
+
usage: { prompt_tokens: 200, completion_tokens: 75 },
|
|
35
|
+
});
|
|
36
|
+
const result = await invokeOpenAIOrchestrator(baseOpts);
|
|
37
|
+
expect(result.content).toBe('Hello from GPT');
|
|
38
|
+
expect(result.inputTokens).toBe(200);
|
|
39
|
+
expect(result.outputTokens).toBe(75);
|
|
40
|
+
expect(result.finishReason).toBe('stop');
|
|
41
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
42
|
+
});
|
|
43
|
+
it('passes model and prompt to the SDK', async () => {
|
|
44
|
+
mockCreate.mockResolvedValueOnce({
|
|
45
|
+
choices: [{ message: { content: 'ok' }, finish_reason: 'stop' }],
|
|
46
|
+
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
|
47
|
+
});
|
|
48
|
+
await invokeOpenAIOrchestrator(baseOpts);
|
|
49
|
+
expect(mockCreate).toHaveBeenCalledWith(expect.objectContaining({
|
|
50
|
+
model: 'gpt-4o',
|
|
51
|
+
messages: [{ role: 'user', content: 'test prompt' }],
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
it('throws when no API key and no baseUrl', async () => {
|
|
55
|
+
delete process.env['OPENAI_API_KEY'];
|
|
56
|
+
await expect(invokeOpenAIOrchestrator(baseOpts)).rejects.toThrow('No OpenAI API key found');
|
|
57
|
+
});
|
|
58
|
+
it('uses dummy key for local servers with baseUrl', async () => {
|
|
59
|
+
delete process.env['OPENAI_API_KEY'];
|
|
60
|
+
mockCreate.mockResolvedValueOnce({
|
|
61
|
+
choices: [{ message: { content: 'local response' }, finish_reason: 'stop' }],
|
|
62
|
+
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
|
63
|
+
});
|
|
64
|
+
const result = await invokeOpenAIOrchestrator({
|
|
65
|
+
...baseOpts,
|
|
66
|
+
model: 'llama3.1',
|
|
67
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
68
|
+
});
|
|
69
|
+
expect(result.content).toBe('local response');
|
|
70
|
+
});
|
|
71
|
+
it('converts 429 errors to QuotaError', async () => {
|
|
72
|
+
const rateLimitErr = Object.assign(new Error('rate_limit_error'), { status: 429 });
|
|
73
|
+
mockCreate.mockRejectedValueOnce(rateLimitErr);
|
|
74
|
+
try {
|
|
75
|
+
await invokeOpenAIOrchestrator(baseOpts);
|
|
76
|
+
expect.fail('Should have thrown');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
expect(err.name).toBe('QuotaError');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('wraps other API errors with [Totem Error] prefix', async () => {
|
|
83
|
+
mockCreate.mockRejectedValueOnce(new Error('invalid_api_key'));
|
|
84
|
+
await expect(invokeOpenAIOrchestrator(baseOpts)).rejects.toThrow('[Totem Error] OpenAI API call failed');
|
|
85
|
+
});
|
|
86
|
+
it('handles missing usage gracefully (local servers)', async () => {
|
|
87
|
+
mockCreate.mockResolvedValueOnce({
|
|
88
|
+
choices: [{ message: { content: 'no usage' }, finish_reason: 'stop' }],
|
|
89
|
+
// No usage field — some local servers omit this
|
|
90
|
+
});
|
|
91
|
+
const result = await invokeOpenAIOrchestrator(baseOpts);
|
|
92
|
+
expect(result.content).toBe('no usage');
|
|
93
|
+
expect(result.inputTokens).toBeNull();
|
|
94
|
+
expect(result.outputTokens).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
it('handles empty choices array', async () => {
|
|
97
|
+
mockCreate.mockResolvedValueOnce({
|
|
98
|
+
choices: [],
|
|
99
|
+
usage: { prompt_tokens: 10, completion_tokens: 0 },
|
|
100
|
+
});
|
|
101
|
+
const result = await invokeOpenAIOrchestrator(baseOpts);
|
|
102
|
+
expect(result.content).toBe('');
|
|
103
|
+
expect(result.finishReason).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
it('handles null message content', async () => {
|
|
106
|
+
mockCreate.mockResolvedValueOnce({
|
|
107
|
+
choices: [{ message: { content: null }, finish_reason: 'stop' }],
|
|
108
|
+
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
|
109
|
+
});
|
|
110
|
+
const result = await invokeOpenAIOrchestrator(baseOpts);
|
|
111
|
+
expect(result.content).toBe('');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=openai-orchestrator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-orchestrator.test.js","sourceRoot":"","sources":["../../src/orchestrators/openai-orchestrator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAMvE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAIjC;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUlD;AAMD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CASrC;AAOD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,kBAAkB,GAC7B,oBAAoB,CA8BtB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAMvE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAIjC;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUlD;AAMD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CASrC;AAOD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,kBAAkB,GAC7B,oBAAoB,CA8BtB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CA6BjF"}
|
|
@@ -31,7 +31,7 @@ export function isQuotaError(err) {
|
|
|
31
31
|
msg.includes('too many requests'));
|
|
32
32
|
}
|
|
33
33
|
// ─── Model string parsing (#243) ─────────────────────
|
|
34
|
-
const KNOWN_PROVIDERS = ['gemini', 'anthropic', 'shell'];
|
|
34
|
+
const KNOWN_PROVIDERS = ['gemini', 'anthropic', 'openai', 'ollama', 'shell'];
|
|
35
35
|
/**
|
|
36
36
|
* Parse a `provider:model` string into its components.
|
|
37
37
|
* If the prefix before the first colon is a known provider, splits it out.
|
|
@@ -91,6 +91,20 @@ export function createOrchestrator(config) {
|
|
|
91
91
|
const { invokeAnthropicOrchestrator } = await import('./anthropic-orchestrator.js');
|
|
92
92
|
return invokeAnthropicOrchestrator(opts);
|
|
93
93
|
};
|
|
94
|
+
case 'openai':
|
|
95
|
+
return async (opts) => {
|
|
96
|
+
const { invokeOpenAIOrchestrator } = await import('./openai-orchestrator.js');
|
|
97
|
+
return invokeOpenAIOrchestrator({ ...opts, baseUrl: config.baseUrl });
|
|
98
|
+
};
|
|
99
|
+
case 'ollama':
|
|
100
|
+
return async (opts) => {
|
|
101
|
+
const { invokeOllamaOrchestrator } = await import('./ollama-orchestrator.js');
|
|
102
|
+
return invokeOllamaOrchestrator({
|
|
103
|
+
...opts,
|
|
104
|
+
baseUrl: config.baseUrl,
|
|
105
|
+
numCtx: config.numCtx,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAyBlE,uDAAuD;AAEvD;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC;IACtD,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wDAAwD;AAExD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,QAAQ,IAAI,GAAG,IAAK,GAA+B,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACpF,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,CACL,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;QACnB,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QACrB,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC1B,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAClC,CAAC;AACJ,CAAC;AAED,wDAAwD;AAExD,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,14 @@ vi.mock('./anthropic-orchestrator.js', () => ({
|
|
|
17
17
|
durationMs: 2000,
|
|
18
18
|
}),
|
|
19
19
|
}));
|
|
20
|
+
vi.mock('./openai-orchestrator.js', () => ({
|
|
21
|
+
invokeOpenAIOrchestrator: vi.fn().mockResolvedValue({
|
|
22
|
+
content: 'openai result',
|
|
23
|
+
inputTokens: 150,
|
|
24
|
+
outputTokens: 60,
|
|
25
|
+
durationMs: 1500,
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
20
28
|
// ─── Tests ──────────────────────────────────────────
|
|
21
29
|
describe('createOrchestrator', () => {
|
|
22
30
|
it('returns a function for shell provider', () => {
|
|
@@ -69,6 +77,27 @@ describe('createOrchestrator', () => {
|
|
|
69
77
|
expect(result.content).toBe('anthropic result');
|
|
70
78
|
expect(result.inputTokens).toBe(200);
|
|
71
79
|
});
|
|
80
|
+
it('returns a function for openai provider', () => {
|
|
81
|
+
const config = {
|
|
82
|
+
provider: 'openai',
|
|
83
|
+
defaultModel: 'gpt-4o',
|
|
84
|
+
};
|
|
85
|
+
const invoke = createOrchestrator(config);
|
|
86
|
+
expect(typeof invoke).toBe('function');
|
|
87
|
+
});
|
|
88
|
+
it('openai invoker dispatches to openai-orchestrator module', async () => {
|
|
89
|
+
const config = { provider: 'openai' };
|
|
90
|
+
const invoke = createOrchestrator(config);
|
|
91
|
+
const result = await invoke({
|
|
92
|
+
prompt: 'test',
|
|
93
|
+
model: 'gpt-4o',
|
|
94
|
+
cwd: '.',
|
|
95
|
+
tag: 'Test',
|
|
96
|
+
totemDir: '.totem',
|
|
97
|
+
});
|
|
98
|
+
expect(result.content).toBe('openai result');
|
|
99
|
+
expect(result.inputTokens).toBe(150);
|
|
100
|
+
});
|
|
72
101
|
});
|
|
73
102
|
// ─── detectPackageManager ───────────────────────────
|
|
74
103
|
describe('detectPackageManager', () => {
|
|
@@ -182,6 +211,12 @@ describe('parseModelString', () => {
|
|
|
182
211
|
model: 'gemini-3.1-pro-preview',
|
|
183
212
|
});
|
|
184
213
|
});
|
|
214
|
+
it('parses openai:model into provider and model', () => {
|
|
215
|
+
expect(parseModelString('openai:gpt-4o', 'gemini')).toEqual({
|
|
216
|
+
provider: 'openai',
|
|
217
|
+
model: 'gpt-4o',
|
|
218
|
+
});
|
|
219
|
+
});
|
|
185
220
|
it('parses shell:model into provider and model', () => {
|
|
186
221
|
expect(parseModelString('shell:my-model', 'gemini')).toEqual({
|
|
187
222
|
provider: 'shell',
|