@mmnto/cli 0.19.0 → 0.21.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/LICENSE +190 -21
- package/dist/commands/compile.d.ts +1 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +94 -78
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/docs.d.ts +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +32 -3
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/docs.test.js +111 -95
- package/dist/commands/docs.test.js.map +1 -1
- package/dist/commands/extract.d.ts +1 -0
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +15 -5
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/extract.test.js +54 -1
- package/dist/commands/extract.test.js.map +1 -1
- package/dist/commands/shield.d.ts +12 -0
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +90 -2
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/shield.test.js +58 -1
- package/dist/commands/shield.test.js.map +1 -1
- package/dist/commands/wrap.js +1 -1
- package/dist/commands/wrap.js.map +1 -1
- package/dist/commands/wrap.test.js +2 -2
- package/dist/commands/wrap.test.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/orchestrators/conformance.test.d.ts +2 -0
- package/dist/orchestrators/conformance.test.d.ts.map +1 -0
- package/dist/orchestrators/conformance.test.js +208 -0
- package/dist/orchestrators/conformance.test.js.map +1 -0
- package/dist/orchestrators/orchestrator.d.ts +23 -0
- package/dist/orchestrators/orchestrator.d.ts.map +1 -1
- package/dist/orchestrators/orchestrator.js +42 -0
- package/dist/orchestrators/orchestrator.js.map +1 -1
- package/dist/orchestrators/orchestrator.test.js +95 -1
- package/dist/orchestrators/orchestrator.test.js.map +1 -1
- package/dist/orchestrators/smoke.integration.test.d.ts +2 -0
- package/dist/orchestrators/smoke.integration.test.d.ts.map +1 -0
- package/dist/orchestrators/smoke.integration.test.js +47 -0
- package/dist/orchestrators/smoke.integration.test.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +29 -18
- package/dist/utils.js.map +1 -1
- package/dist/utils.test.js +183 -1
- package/dist/utils.test.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
// ─── Mock SDKs ──────────────────────────────────────
|
|
7
|
+
const { mockGenerateContent, mockCreate } = vi.hoisted(() => ({
|
|
8
|
+
mockGenerateContent: vi.fn(),
|
|
9
|
+
mockCreate: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
vi.mock('@google/genai', () => ({
|
|
12
|
+
GoogleGenAI: class {
|
|
13
|
+
models = { generateContent: mockGenerateContent };
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('@anthropic-ai/sdk', () => ({
|
|
17
|
+
default: class {
|
|
18
|
+
messages = { create: mockCreate };
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
vi.mock('../ui.js', () => ({
|
|
22
|
+
log: { info: vi.fn(), success: vi.fn(), warn: vi.fn(), error: vi.fn(), dim: vi.fn() },
|
|
23
|
+
}));
|
|
24
|
+
function createMockChild() {
|
|
25
|
+
const child = new EventEmitter();
|
|
26
|
+
child.stdout = new EventEmitter();
|
|
27
|
+
child.stderr = new EventEmitter();
|
|
28
|
+
child.kill = vi.fn();
|
|
29
|
+
return child;
|
|
30
|
+
}
|
|
31
|
+
let mockChild;
|
|
32
|
+
vi.mock('node:child_process', () => ({
|
|
33
|
+
spawn: vi.fn(() => mockChild),
|
|
34
|
+
}));
|
|
35
|
+
// ─── Import orchestrators ───────────────────────────
|
|
36
|
+
import { invokeAnthropicOrchestrator } from './anthropic-orchestrator.js';
|
|
37
|
+
import { invokeGeminiOrchestrator } from './gemini-orchestrator.js';
|
|
38
|
+
import { invokeShellOrchestrator } from './shell-orchestrator.js';
|
|
39
|
+
// ─── Shared test opts ───────────────────────────────
|
|
40
|
+
const baseOpts = {
|
|
41
|
+
prompt: 'conformance test prompt',
|
|
42
|
+
model: 'test-model',
|
|
43
|
+
cwd: '.',
|
|
44
|
+
tag: 'Conformance',
|
|
45
|
+
totemDir: '.totem',
|
|
46
|
+
};
|
|
47
|
+
// ─── Conformance contract assertions ────────────────
|
|
48
|
+
function assertOrchestratorResult(result) {
|
|
49
|
+
expect(typeof result.content).toBe('string');
|
|
50
|
+
expect(typeof result.durationMs).toBe('number');
|
|
51
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
52
|
+
// Token counts are number | null
|
|
53
|
+
if (result.inputTokens !== null) {
|
|
54
|
+
expect(typeof result.inputTokens).toBe('number');
|
|
55
|
+
}
|
|
56
|
+
if (result.outputTokens !== null) {
|
|
57
|
+
expect(typeof result.outputTokens).toBe('number');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const sdkFixtures = [
|
|
61
|
+
{
|
|
62
|
+
name: 'gemini',
|
|
63
|
+
envKey: 'GEMINI_API_KEY',
|
|
64
|
+
setupHappy: () => {
|
|
65
|
+
mockGenerateContent.mockResolvedValueOnce({
|
|
66
|
+
text: 'conformance-ok',
|
|
67
|
+
usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5 },
|
|
68
|
+
candidates: [{ finishReason: 'STOP' }],
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
setupQuota: () => {
|
|
72
|
+
mockGenerateContent.mockRejectedValueOnce(Object.assign(new Error('quota exceeded'), { status: 429 }));
|
|
73
|
+
},
|
|
74
|
+
setupGenericError: () => {
|
|
75
|
+
mockGenerateContent.mockRejectedValueOnce(new Error('Model not found'));
|
|
76
|
+
},
|
|
77
|
+
invoke: () => invokeGeminiOrchestrator(baseOpts),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'anthropic',
|
|
81
|
+
envKey: 'ANTHROPIC_API_KEY',
|
|
82
|
+
setupHappy: () => {
|
|
83
|
+
mockCreate.mockResolvedValueOnce({
|
|
84
|
+
content: [{ type: 'text', text: 'conformance-ok' }],
|
|
85
|
+
usage: { input_tokens: 10, output_tokens: 5 },
|
|
86
|
+
stop_reason: 'end_turn',
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
setupQuota: () => {
|
|
90
|
+
mockCreate.mockRejectedValueOnce(Object.assign(new Error('rate limit'), { status: 429 }));
|
|
91
|
+
},
|
|
92
|
+
setupGenericError: () => {
|
|
93
|
+
mockCreate.mockRejectedValueOnce(new Error('invalid_api_key'));
|
|
94
|
+
},
|
|
95
|
+
invoke: () => invokeAnthropicOrchestrator(baseOpts),
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
describe.each(sdkFixtures)('$name provider conformance', (fixture) => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
vi.clearAllMocks();
|
|
101
|
+
process.env[fixture.envKey] = 'test-key';
|
|
102
|
+
});
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
delete process.env[fixture.envKey];
|
|
105
|
+
});
|
|
106
|
+
it('returns a valid OrchestratorResult on happy path', async () => {
|
|
107
|
+
fixture.setupHappy();
|
|
108
|
+
const result = await fixture.invoke();
|
|
109
|
+
assertOrchestratorResult(result);
|
|
110
|
+
expect(result.content).toBe('conformance-ok');
|
|
111
|
+
expect(result.inputTokens).toBe(10);
|
|
112
|
+
expect(result.outputTokens).toBe(5);
|
|
113
|
+
});
|
|
114
|
+
it('throws descriptive error when API key is missing', async () => {
|
|
115
|
+
delete process.env[fixture.envKey];
|
|
116
|
+
await expect(fixture.invoke()).rejects.toThrow(/\[Totem Error\].*No .+ API key found/);
|
|
117
|
+
});
|
|
118
|
+
it('converts quota/429 errors to QuotaError', async () => {
|
|
119
|
+
fixture.setupQuota();
|
|
120
|
+
await expect(fixture.invoke()).rejects.toHaveProperty('name', 'QuotaError');
|
|
121
|
+
});
|
|
122
|
+
it('wraps generic API errors with [Totem Error] prefix', async () => {
|
|
123
|
+
fixture.setupGenericError();
|
|
124
|
+
await expect(fixture.invoke()).rejects.toThrow('[Totem Error]');
|
|
125
|
+
});
|
|
126
|
+
it('includes durationMs >= 0 in result', async () => {
|
|
127
|
+
fixture.setupHappy();
|
|
128
|
+
const result = await fixture.invoke();
|
|
129
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
// ─── Shell Provider Conformance ─────────────────────
|
|
133
|
+
describe('shell provider conformance', () => {
|
|
134
|
+
let tmpDir;
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-conf-'));
|
|
137
|
+
vi.clearAllMocks();
|
|
138
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
139
|
+
mockChild = createMockChild();
|
|
140
|
+
});
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
143
|
+
vi.restoreAllMocks();
|
|
144
|
+
});
|
|
145
|
+
const shellOpts = () => ({
|
|
146
|
+
prompt: 'conformance test prompt',
|
|
147
|
+
command: 'echo {file}',
|
|
148
|
+
model: 'test-model',
|
|
149
|
+
cwd: tmpDir,
|
|
150
|
+
tag: 'Conformance',
|
|
151
|
+
totemDir: '.totem',
|
|
152
|
+
});
|
|
153
|
+
function emitSuccess(data) {
|
|
154
|
+
process.nextTick(() => {
|
|
155
|
+
mockChild.stdout.emit('data', Buffer.from(data));
|
|
156
|
+
mockChild.emit('close', 0);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function emitFailure(code, stderr = '') {
|
|
160
|
+
process.nextTick(() => {
|
|
161
|
+
if (stderr)
|
|
162
|
+
mockChild.stderr.emit('data', Buffer.from(stderr));
|
|
163
|
+
mockChild.emit('close', code);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
it('returns a valid OrchestratorResult on happy path', async () => {
|
|
167
|
+
emitSuccess('conformance-ok');
|
|
168
|
+
const result = await invokeShellOrchestrator(shellOpts());
|
|
169
|
+
assertOrchestratorResult(result);
|
|
170
|
+
expect(result.content).toBe('conformance-ok');
|
|
171
|
+
// Shell orchestrator returns null tokens for non-Gemini output
|
|
172
|
+
expect(result.inputTokens).toBeNull();
|
|
173
|
+
expect(result.outputTokens).toBeNull();
|
|
174
|
+
});
|
|
175
|
+
it('returns structured tokens when output is Gemini JSON', async () => {
|
|
176
|
+
const geminiOutput = JSON.stringify({
|
|
177
|
+
response: 'conformance-ok',
|
|
178
|
+
stats: {
|
|
179
|
+
models: {
|
|
180
|
+
'test-model': {
|
|
181
|
+
tokens: { input: 10, candidates: 5 },
|
|
182
|
+
api: { totalLatencyMs: 1000 },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
emitSuccess(geminiOutput);
|
|
188
|
+
const result = await invokeShellOrchestrator(shellOpts());
|
|
189
|
+
assertOrchestratorResult(result);
|
|
190
|
+
expect(result.content).toBe('conformance-ok');
|
|
191
|
+
expect(result.inputTokens).toBe(10);
|
|
192
|
+
expect(result.outputTokens).toBe(5);
|
|
193
|
+
});
|
|
194
|
+
it('converts quota errors to QuotaError', async () => {
|
|
195
|
+
emitFailure(1, '429 Too Many Requests quota exceeded');
|
|
196
|
+
await expect(invokeShellOrchestrator(shellOpts())).rejects.toHaveProperty('name', 'QuotaError');
|
|
197
|
+
});
|
|
198
|
+
it('wraps generic errors with [Totem Error] prefix', async () => {
|
|
199
|
+
emitFailure(1, 'something went wrong');
|
|
200
|
+
await expect(invokeShellOrchestrator(shellOpts())).rejects.toThrow('[Totem Error]');
|
|
201
|
+
});
|
|
202
|
+
it('includes durationMs >= 0 in result', async () => {
|
|
203
|
+
emitSuccess('ok');
|
|
204
|
+
const result = await invokeShellOrchestrator(shellOpts());
|
|
205
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
//# sourceMappingURL=conformance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../../src/orchestrators/conformance.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,29 @@ export declare function detectPackageManager(): string;
|
|
|
25
25
|
* Used by both Gemini and Anthropic orchestrators to normalize QuotaError.
|
|
26
26
|
*/
|
|
27
27
|
export declare function isQuotaError(err: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Parse a `provider:model` string into its components.
|
|
30
|
+
* If the prefix before the first colon is a known provider, splits it out.
|
|
31
|
+
* Otherwise, returns the full string as the model with the default provider.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseModelString(value: string, defaultProvider: string): {
|
|
34
|
+
provider: string;
|
|
35
|
+
model: string;
|
|
36
|
+
};
|
|
37
|
+
export interface ResolvedOrchestrator {
|
|
38
|
+
parsed: {
|
|
39
|
+
provider: string;
|
|
40
|
+
model: string;
|
|
41
|
+
};
|
|
42
|
+
invoke: InvokeOrchestrator;
|
|
43
|
+
qualifiedModel: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse and validate a model string, resolve cross-provider routing,
|
|
47
|
+
* and return the appropriate invoker. Centralizes the validation logic
|
|
48
|
+
* to guarantee symmetric validation across primary and fallback paths.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveOrchestrator(rawModel: string, baseProvider: string, baseInvoke: InvokeOrchestrator): ResolvedOrchestrator;
|
|
28
51
|
/**
|
|
29
52
|
* Create an orchestrator invoker bound to the given provider config.
|
|
30
53
|
* Mirrors the `createEmbedder()` pattern from `packages/core/src/embedders/`.
|
|
@@ -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;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAejF"}
|
|
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,CAejF"}
|
|
@@ -30,6 +30,48 @@ export function isQuotaError(err) {
|
|
|
30
30
|
msg.includes('rate limit') ||
|
|
31
31
|
msg.includes('too many requests'));
|
|
32
32
|
}
|
|
33
|
+
// ─── Model string parsing (#243) ─────────────────────
|
|
34
|
+
const KNOWN_PROVIDERS = ['gemini', 'anthropic', 'shell'];
|
|
35
|
+
/**
|
|
36
|
+
* Parse a `provider:model` string into its components.
|
|
37
|
+
* If the prefix before the first colon is a known provider, splits it out.
|
|
38
|
+
* Otherwise, returns the full string as the model with the default provider.
|
|
39
|
+
*/
|
|
40
|
+
export function parseModelString(value, defaultProvider) {
|
|
41
|
+
const colonIdx = value.indexOf(':');
|
|
42
|
+
if (colonIdx > 0) {
|
|
43
|
+
const prefix = value.slice(0, colonIdx);
|
|
44
|
+
if (KNOWN_PROVIDERS.includes(prefix)) {
|
|
45
|
+
return { provider: prefix, model: value.slice(colonIdx + 1) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { provider: defaultProvider, model: value };
|
|
49
|
+
}
|
|
50
|
+
// ─── Centralized model resolution (#248) ────────────
|
|
51
|
+
/** Characters allowed in model names — restricts shell metacharacters. */
|
|
52
|
+
const MODEL_NAME_RE = /^[\w./:_-]+$/;
|
|
53
|
+
/**
|
|
54
|
+
* Parse and validate a model string, resolve cross-provider routing,
|
|
55
|
+
* and return the appropriate invoker. Centralizes the validation logic
|
|
56
|
+
* to guarantee symmetric validation across primary and fallback paths.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveOrchestrator(rawModel, baseProvider, baseInvoke) {
|
|
59
|
+
if (rawModel.startsWith('-') || !MODEL_NAME_RE.test(rawModel)) {
|
|
60
|
+
throw new Error(`[Totem Error] Invalid model name '${rawModel}'. Model names may only contain word characters, dots, slashes, colons, underscores, and hyphens.`);
|
|
61
|
+
}
|
|
62
|
+
const parsed = parseModelString(rawModel, baseProvider);
|
|
63
|
+
if (parsed.provider === 'shell' && baseProvider !== 'shell') {
|
|
64
|
+
throw new Error(`[Totem Error] Cannot route to 'shell' provider from a '${baseProvider}' config.\n` +
|
|
65
|
+
`The shell provider requires a 'command' template in the orchestrator config.`);
|
|
66
|
+
}
|
|
67
|
+
if (!parsed.model || parsed.model.startsWith('-')) {
|
|
68
|
+
throw new Error(`[Totem Error] Invalid model name in '${rawModel}'. The model portion must not be empty or start with a hyphen.`);
|
|
69
|
+
}
|
|
70
|
+
const invoke = parsed.provider === baseProvider
|
|
71
|
+
? baseInvoke
|
|
72
|
+
: createOrchestrator({ provider: parsed.provider });
|
|
73
|
+
return { parsed, invoke, qualifiedModel: rawModel };
|
|
74
|
+
}
|
|
33
75
|
// ─── Factory ─────────────────────────────────────────
|
|
34
76
|
/**
|
|
35
77
|
* Create an orchestrator invoker bound to the given provider config.
|
|
@@ -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;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA0B;IAC3D,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,OAAO;YACV,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,KAAK,QAAQ;YACX,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE;gBACpB,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;gBAC9E,OAAO,wBAAwB,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC,CAAC;QACJ,KAAK,WAAW;YACd,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE;gBACpB,MAAM,EAAE,2BAA2B,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC;gBACpF,OAAO,2BAA2B,CAAC,IAAI,CAAC,CAAC;YAC3C,CAAC,CAAC;IACN,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { createOrchestrator, detectPackageManager, isQuotaError } from './orchestrator.js';
|
|
2
|
+
import { createOrchestrator, detectPackageManager, isQuotaError, parseModelString, resolveOrchestrator, } from './orchestrator.js';
|
|
3
3
|
// ─── Mock provider modules ──────────────────────────
|
|
4
4
|
vi.mock('./gemini-orchestrator.js', () => ({
|
|
5
5
|
invokeGeminiOrchestrator: vi.fn().mockResolvedValue({
|
|
@@ -125,4 +125,98 @@ describe('isQuotaError', () => {
|
|
|
125
125
|
expect(isQuotaError(null)).toBe(false);
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
|
+
// ─── resolveOrchestrator (#248) ─────────────────────
|
|
129
|
+
describe('resolveOrchestrator', () => {
|
|
130
|
+
const mockInvoke = vi.fn().mockResolvedValue({
|
|
131
|
+
content: 'mock result',
|
|
132
|
+
inputTokens: 100,
|
|
133
|
+
outputTokens: 50,
|
|
134
|
+
durationMs: 500,
|
|
135
|
+
});
|
|
136
|
+
it('reuses baseInvoke for same-provider resolution', () => {
|
|
137
|
+
const result = resolveOrchestrator('gemini-3-flash-preview', 'gemini', mockInvoke);
|
|
138
|
+
expect(result.invoke).toBe(mockInvoke);
|
|
139
|
+
expect(result.parsed).toEqual({ provider: 'gemini', model: 'gemini-3-flash-preview' });
|
|
140
|
+
expect(result.qualifiedModel).toBe('gemini-3-flash-preview');
|
|
141
|
+
});
|
|
142
|
+
it('creates new invoker for cross-provider resolution', () => {
|
|
143
|
+
const result = resolveOrchestrator('anthropic:claude-sonnet-4-20250514', 'gemini', mockInvoke);
|
|
144
|
+
expect(result.invoke).not.toBe(mockInvoke);
|
|
145
|
+
expect(result.parsed).toEqual({ provider: 'anthropic', model: 'claude-sonnet-4-20250514' });
|
|
146
|
+
expect(result.qualifiedModel).toBe('anthropic:claude-sonnet-4-20250514');
|
|
147
|
+
});
|
|
148
|
+
it('throws on empty model string (provider:)', () => {
|
|
149
|
+
expect(() => resolveOrchestrator('anthropic:', 'gemini', mockInvoke)).toThrow('must not be empty or start with a hyphen');
|
|
150
|
+
});
|
|
151
|
+
it('throws on model starting with hyphen', () => {
|
|
152
|
+
expect(() => resolveOrchestrator('anthropic:-bad', 'gemini', mockInvoke)).toThrow('must not be empty or start with a hyphen');
|
|
153
|
+
});
|
|
154
|
+
it('throws when cross-routing to shell from API provider', () => {
|
|
155
|
+
expect(() => resolveOrchestrator('shell:my-model', 'gemini', mockInvoke)).toThrow("Cannot route to 'shell' provider");
|
|
156
|
+
});
|
|
157
|
+
it('allows shell-to-shell routing', () => {
|
|
158
|
+
const result = resolveOrchestrator('shell:my-model', 'shell', mockInvoke);
|
|
159
|
+
expect(result.invoke).toBe(mockInvoke);
|
|
160
|
+
expect(result.parsed).toEqual({ provider: 'shell', model: 'my-model' });
|
|
161
|
+
});
|
|
162
|
+
it('preserves provider:model as qualifiedModel for cross-route', () => {
|
|
163
|
+
const result = resolveOrchestrator('gemini:gemini-3.1-pro-preview', 'anthropic', mockInvoke);
|
|
164
|
+
expect(result.qualifiedModel).toBe('gemini:gemini-3.1-pro-preview');
|
|
165
|
+
});
|
|
166
|
+
it('rejects model names with shell metacharacters', () => {
|
|
167
|
+
expect(() => resolveOrchestrator('$(touch /tmp/pwned)', 'shell', mockInvoke)).toThrow('Invalid model name');
|
|
168
|
+
expect(() => resolveOrchestrator('model;rm -rf /', 'shell', mockInvoke)).toThrow('Invalid model name');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// ─── parseModelString (#243) ────────────────────────
|
|
172
|
+
describe('parseModelString', () => {
|
|
173
|
+
it('parses anthropic:model into provider and model', () => {
|
|
174
|
+
expect(parseModelString('anthropic:claude-sonnet-4-20250514', 'gemini')).toEqual({
|
|
175
|
+
provider: 'anthropic',
|
|
176
|
+
model: 'claude-sonnet-4-20250514',
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it('parses gemini:model into provider and model', () => {
|
|
180
|
+
expect(parseModelString('gemini:gemini-3.1-pro-preview', 'anthropic')).toEqual({
|
|
181
|
+
provider: 'gemini',
|
|
182
|
+
model: 'gemini-3.1-pro-preview',
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
it('parses shell:model into provider and model', () => {
|
|
186
|
+
expect(parseModelString('shell:my-model', 'gemini')).toEqual({
|
|
187
|
+
provider: 'shell',
|
|
188
|
+
model: 'my-model',
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
it('returns default provider for plain model string', () => {
|
|
192
|
+
expect(parseModelString('gemini-3-flash-preview', 'gemini')).toEqual({
|
|
193
|
+
provider: 'gemini',
|
|
194
|
+
model: 'gemini-3-flash-preview',
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
it('returns default provider for unknown prefix (not a known provider)', () => {
|
|
198
|
+
expect(parseModelString('unknown:my-model:v1', 'gemini')).toEqual({
|
|
199
|
+
provider: 'gemini',
|
|
200
|
+
model: 'unknown:my-model:v1',
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
it('returns default provider for org/namespace:model patterns', () => {
|
|
204
|
+
expect(parseModelString('myorg/namespace:model-v1', 'anthropic')).toEqual({
|
|
205
|
+
provider: 'anthropic',
|
|
206
|
+
model: 'myorg/namespace:model-v1',
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
it('parses empty model after colon (caller should validate)', () => {
|
|
210
|
+
expect(parseModelString('anthropic:', 'gemini')).toEqual({
|
|
211
|
+
provider: 'anthropic',
|
|
212
|
+
model: '',
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it('does not split on leading colon', () => {
|
|
216
|
+
expect(parseModelString(':some-model', 'gemini')).toEqual({
|
|
217
|
+
provider: 'gemini',
|
|
218
|
+
model: ':some-model',
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
128
222
|
//# sourceMappingURL=orchestrator.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.test.js","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAI7D,OAAO,
|
|
1
|
+
{"version":3,"file":"orchestrator.test.js","sourceRoot":"","sources":["../../src/orchestrators/orchestrator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.integration.test.d.ts","sourceRoot":"","sources":["../../src/orchestrators/smoke.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration smoke tests — verify orchestrator providers work end-to-end
|
|
3
|
+
* against live APIs. Gated behind CI_INTEGRATION=true env var.
|
|
4
|
+
*
|
|
5
|
+
* Run locally: CI_INTEGRATION=true GEMINI_API_KEY=... ANTHROPIC_API_KEY=... pnpm vitest run -c vitest.integration.config.ts
|
|
6
|
+
* Run in CI: .github/workflows/ci-integration.yml (nightly schedule)
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/mmnto-ai/totem/issues/245
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
const MINIMAL_PROMPT = 'Respond with exactly one word: TOTEM';
|
|
12
|
+
const TIMEOUT_MS = 90_000; // generous — cold starts can be slow
|
|
13
|
+
function assertSmokeResult(result) {
|
|
14
|
+
expect(result.content).toBeTruthy();
|
|
15
|
+
expect(result.durationMs).toBeGreaterThan(0);
|
|
16
|
+
expect(typeof result.inputTokens).toBe('number');
|
|
17
|
+
expect(typeof result.outputTokens).toBe('number');
|
|
18
|
+
}
|
|
19
|
+
describe.runIf(process.env['CI_INTEGRATION'] === 'true')('Integration Smoke Tests', () => {
|
|
20
|
+
describe('Gemini', () => {
|
|
21
|
+
it('returns a valid response from the Gemini API', async () => {
|
|
22
|
+
const { invokeGeminiOrchestrator } = await import('./gemini-orchestrator.js');
|
|
23
|
+
const result = await invokeGeminiOrchestrator({
|
|
24
|
+
prompt: MINIMAL_PROMPT,
|
|
25
|
+
model: 'gemini-2.5-flash',
|
|
26
|
+
cwd: '.',
|
|
27
|
+
tag: 'Smoke',
|
|
28
|
+
totemDir: '.totem',
|
|
29
|
+
});
|
|
30
|
+
assertSmokeResult(result);
|
|
31
|
+
}, TIMEOUT_MS);
|
|
32
|
+
});
|
|
33
|
+
describe('Anthropic', () => {
|
|
34
|
+
it('returns a valid response from the Anthropic API', async () => {
|
|
35
|
+
const { invokeAnthropicOrchestrator } = await import('./anthropic-orchestrator.js');
|
|
36
|
+
const result = await invokeAnthropicOrchestrator({
|
|
37
|
+
prompt: MINIMAL_PROMPT,
|
|
38
|
+
model: 'claude-haiku-4-5-20251001',
|
|
39
|
+
cwd: '.',
|
|
40
|
+
tag: 'Smoke',
|
|
41
|
+
totemDir: '.totem',
|
|
42
|
+
});
|
|
43
|
+
assertSmokeResult(result);
|
|
44
|
+
}, TIMEOUT_MS);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=smoke.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.integration.test.js","sourceRoot":"","sources":["../../src/orchestrators/smoke.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAI9C,MAAM,cAAc,GAAG,sCAAsC,CAAC;AAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,qCAAqC;AAEhE,SAAS,iBAAiB,CAAC,MAA0B;IACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,CAAC,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,MAAM,CAAC,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvF,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CACA,8CAA8C,EAC9C,KAAK,IAAI,EAAE;YACT,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;YAE9E,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;gBAC5C,MAAM,EAAE,cAAc;gBACtB,KAAK,EAAE,kBAAkB;gBACzB,GAAG,EAAE,GAAG;gBACR,GAAG,EAAE,OAAO;gBACZ,QAAQ,EAAE,QAAQ;aACnB,CAAC,CAAC;YAEH,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,EACD,UAAU,CACX,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CACA,iDAAiD,EACjD,KAAK,IAAI,EAAE;YACT,MAAM,EAAE,2BAA2B,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC;YAEpF,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC;gBAC/C,MAAM,EAAE,cAAc;gBACtB,KAAK,EAAE,2BAA2B;gBAClC,GAAG,EAAE,GAAG;gBACR,GAAG,EAAE,OAAO;gBACZ,QAAQ,EAAE,QAAQ;aACnB,CAAC,CAAC;YAEH,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,EACD,UAAU,CACX,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAW9D,+EAA+E;AAC/E,eAAO,MAAM,MAAM,SAA+B,CAAC;AAEnD,yCAAyC;AACzC,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBzC;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAMzE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMrD;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA+BhD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAwB,GACjC,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,MAAM,CAaR;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAUnE;AAKD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAOvC,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAqBR;AAID,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAYD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsK9B"}
|
package/dist/utils.js
CHANGED
|
@@ -2,10 +2,9 @@ import * as crypto from 'node:crypto';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { TotemConfigSchema } from '@mmnto/totem';
|
|
5
|
-
import { createOrchestrator } from './orchestrators/orchestrator.js';
|
|
5
|
+
import { createOrchestrator, resolveOrchestrator } from './orchestrators/orchestrator.js';
|
|
6
6
|
import { bold, log } from './ui.js';
|
|
7
7
|
// ─── Shared constants ────────────────────────────────────
|
|
8
|
-
const MODEL_NAME_RE = /^[\w./:_-]+$/;
|
|
9
8
|
const TELEMETRY_FILE = 'telemetry.jsonl';
|
|
10
9
|
/** execFileSync on Windows can't resolve executables without `shell: true`. */
|
|
11
10
|
export const IS_WIN = process.platform === 'win32';
|
|
@@ -196,16 +195,18 @@ export async function runOrchestrator(opts) {
|
|
|
196
195
|
throw new Error(`[Totem Error] No orchestrator configured. Add an 'orchestrator' block to totem.config.ts.\n` +
|
|
197
196
|
`Example:\n orchestrator: {\n provider: 'shell',\n command: 'gemini --model {model} -e none < {file}',\n defaultModel: 'gemini-2.5-pro',\n }`);
|
|
198
197
|
}
|
|
199
|
-
const
|
|
198
|
+
const baseProvider = config.orchestrator.provider;
|
|
199
|
+
const baseInvoke = createOrchestrator(config.orchestrator);
|
|
200
200
|
const tagKey = tag.toLowerCase();
|
|
201
|
-
|
|
202
|
-
if (!
|
|
201
|
+
const rawModel = options.model ?? config.orchestrator.overrides?.[tagKey] ?? config.orchestrator.defaultModel;
|
|
202
|
+
if (!rawModel) {
|
|
203
203
|
throw new Error(`[Totem Error] No model specified. Provide one with --model, set a command-specific model in 'overrides', or set a 'defaultModel' in your orchestrator config.`);
|
|
204
204
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
let resolved = resolveOrchestrator(rawModel, baseProvider, baseInvoke);
|
|
206
|
+
let model = resolved.parsed.model;
|
|
207
|
+
let qualifiedModel = resolved.qualifiedModel;
|
|
208
|
+
let invoke = resolved.invoke;
|
|
209
|
+
log.info(tag, `Model: ${bold(rawModel)}`);
|
|
209
210
|
const ttlSeconds = config.orchestrator.cacheTtls?.[tagKey] ?? DEFAULT_TTLS[tagKey] ?? 0;
|
|
210
211
|
const useCache = ttlSeconds > 0 && !options.fresh;
|
|
211
212
|
let cachePath = '';
|
|
@@ -213,7 +214,7 @@ export async function runOrchestrator(opts) {
|
|
|
213
214
|
const hash = crypto
|
|
214
215
|
.createHash('sha256')
|
|
215
216
|
.update(prompt)
|
|
216
|
-
.update(
|
|
217
|
+
.update(qualifiedModel)
|
|
217
218
|
.digest('hex')
|
|
218
219
|
.slice(0, 16);
|
|
219
220
|
const cacheDir = path.join(cwd, config.totemDir, 'cache');
|
|
@@ -238,18 +239,28 @@ export async function runOrchestrator(opts) {
|
|
|
238
239
|
}
|
|
239
240
|
catch (err) {
|
|
240
241
|
if (err instanceof Error && err.name === 'QuotaError') {
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
log.warn(tag, `Quota exhausted for ${
|
|
242
|
+
const rawFallback = config.orchestrator.fallbackModel;
|
|
243
|
+
if (rawFallback && rawModel !== rawFallback) {
|
|
244
|
+
log.warn(tag, `Quota exhausted for ${rawModel}. Retrying with fallback model: ${bold(rawFallback)}...`);
|
|
245
|
+
const fallbackResolved = resolveOrchestrator(rawFallback, baseProvider, baseInvoke);
|
|
244
246
|
try {
|
|
245
|
-
result = await invoke({
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
result = await fallbackResolved.invoke({
|
|
248
|
+
prompt,
|
|
249
|
+
model: fallbackResolved.parsed.model,
|
|
250
|
+
cwd,
|
|
251
|
+
tag,
|
|
252
|
+
totemDir: config.totemDir,
|
|
253
|
+
});
|
|
254
|
+
// Update model/invoke so telemetry and cache log the correct values
|
|
255
|
+
model = fallbackResolved.parsed.model;
|
|
256
|
+
qualifiedModel = fallbackResolved.qualifiedModel;
|
|
257
|
+
resolved = fallbackResolved;
|
|
258
|
+
invoke = fallbackResolved.invoke;
|
|
248
259
|
}
|
|
249
260
|
catch (fallbackErr) {
|
|
250
261
|
const originalMsg = err.message;
|
|
251
262
|
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
252
|
-
throw new Error(`[Totem Error] Primary model '${
|
|
263
|
+
throw new Error(`[Totem Error] Primary model '${rawModel}' failed and fallback model '${rawFallback}' also failed.\n\n` +
|
|
253
264
|
`Primary error:\n${originalMsg}\n\n` +
|
|
254
265
|
`Fallback error:\n${fallbackMsg}`);
|
|
255
266
|
}
|
|
@@ -292,7 +303,7 @@ export async function runOrchestrator(opts) {
|
|
|
292
303
|
appendTelemetry({
|
|
293
304
|
timestamp: new Date().toISOString(),
|
|
294
305
|
tag,
|
|
295
|
-
model,
|
|
306
|
+
model: qualifiedModel,
|
|
296
307
|
promptChars: prompt.length,
|
|
297
308
|
inputTokens: result.inputTokens,
|
|
298
309
|
outputTokens: result.outputTokens,
|