cognitive-modules-cli 2.2.1 → 2.2.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +35 -29
- package/dist/cli.js +519 -23
- package/dist/commands/add.d.ts +33 -14
- package/dist/commands/add.js +383 -16
- package/dist/commands/compose.js +60 -23
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/init.js +23 -1
- package/dist/commands/migrate.d.ts +30 -0
- package/dist/commands/migrate.js +650 -0
- package/dist/commands/pipe.d.ts +1 -0
- package/dist/commands/pipe.js +31 -11
- package/dist/commands/remove.js +33 -2
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +61 -28
- package/dist/commands/search.d.ts +28 -0
- package/dist/commands/search.js +143 -0
- package/dist/commands/test.d.ts +65 -0
- package/dist/commands/test.js +454 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +106 -14
- package/dist/commands/validate.d.ts +36 -0
- package/dist/commands/validate.js +97 -0
- package/dist/errors/index.d.ts +225 -0
- package/dist/errors/index.js +420 -0
- package/dist/mcp/server.js +84 -79
- package/dist/modules/composition.js +97 -32
- package/dist/modules/loader.js +4 -2
- package/dist/modules/runner.d.ts +72 -5
- package/dist/modules/runner.js +306 -59
- package/dist/modules/subagent.d.ts +6 -1
- package/dist/modules/subagent.js +18 -13
- package/dist/modules/validator.js +14 -6
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.js +147 -5
- package/dist/providers/base.d.ts +11 -0
- package/dist/providers/base.js +18 -0
- package/dist/providers/gemini.d.ts +15 -0
- package/dist/providers/gemini.js +122 -5
- package/dist/providers/ollama.d.ts +15 -0
- package/dist/providers/ollama.js +111 -3
- package/dist/providers/openai.d.ts +11 -0
- package/dist/providers/openai.js +133 -0
- package/dist/registry/client.d.ts +212 -0
- package/dist/registry/client.js +359 -0
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/tar.d.ts +8 -0
- package/dist/registry/tar.js +353 -0
- package/dist/server/http.js +301 -45
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/sse.d.ts +13 -0
- package/dist/server/sse.js +22 -0
- package/dist/types.d.ts +32 -1
- package/dist/types.js +4 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +31 -7
- package/dist/modules/composition.test.d.ts +0 -11
- package/dist/modules/composition.test.js +0 -450
- package/dist/modules/policy.test.d.ts +0 -10
- package/dist/modules/policy.test.js +0 -369
- package/src/cli.ts +0 -471
- package/src/commands/add.ts +0 -315
- package/src/commands/compose.ts +0 -185
- package/src/commands/index.ts +0 -13
- package/src/commands/init.ts +0 -94
- package/src/commands/list.ts +0 -33
- package/src/commands/pipe.ts +0 -76
- package/src/commands/remove.ts +0 -57
- package/src/commands/run.ts +0 -80
- package/src/commands/update.ts +0 -130
- package/src/commands/versions.ts +0 -79
- package/src/index.ts +0 -90
- package/src/mcp/index.ts +0 -5
- package/src/mcp/server.ts +0 -403
- package/src/modules/composition.test.ts +0 -558
- package/src/modules/composition.ts +0 -1674
- package/src/modules/index.ts +0 -9
- package/src/modules/loader.ts +0 -508
- package/src/modules/policy.test.ts +0 -455
- package/src/modules/runner.ts +0 -1983
- package/src/modules/subagent.ts +0 -277
- package/src/modules/validator.ts +0 -700
- package/src/providers/anthropic.ts +0 -89
- package/src/providers/base.ts +0 -29
- package/src/providers/deepseek.ts +0 -83
- package/src/providers/gemini.ts +0 -117
- package/src/providers/index.ts +0 -78
- package/src/providers/minimax.ts +0 -81
- package/src/providers/moonshot.ts +0 -82
- package/src/providers/ollama.ts +0 -83
- package/src/providers/openai.ts +0 -84
- package/src/providers/qwen.ts +0 -82
- package/src/server/http.ts +0 -316
- package/src/server/index.ts +0 -6
- package/src/types.ts +0 -599
- package/tsconfig.json +0 -17
package/dist/modules/subagent.js
CHANGED
|
@@ -75,11 +75,15 @@ export function parseCalls(text) {
|
|
|
75
75
|
*/
|
|
76
76
|
export function substituteCallResults(text, callResults) {
|
|
77
77
|
let result = text;
|
|
78
|
-
for (const
|
|
79
|
-
const resultStr = typeof
|
|
80
|
-
? JSON.stringify(
|
|
81
|
-
: String(
|
|
82
|
-
|
|
78
|
+
for (const entry of callResults) {
|
|
79
|
+
const resultStr = typeof entry.result === 'object'
|
|
80
|
+
? JSON.stringify(entry.result, null, 2)
|
|
81
|
+
: String(entry.result);
|
|
82
|
+
const replacement = `[Result from ${entry.module}]:\n${resultStr}`;
|
|
83
|
+
const idx = result.indexOf(entry.match);
|
|
84
|
+
if (idx !== -1) {
|
|
85
|
+
result = result.slice(0, idx) + replacement + result.slice(idx + entry.match.length);
|
|
86
|
+
}
|
|
83
87
|
}
|
|
84
88
|
return result;
|
|
85
89
|
}
|
|
@@ -99,7 +103,7 @@ export class SubagentOrchestrator {
|
|
|
99
103
|
* Recursively resolves @call directives before final execution.
|
|
100
104
|
*/
|
|
101
105
|
async run(moduleName, options = {}, context) {
|
|
102
|
-
const { input = {}, validateInput = true, validateOutput = true, maxDepth = 5 } = options;
|
|
106
|
+
const { input = {}, args, validateInput = true, validateOutput = true, maxDepth = 5 } = options;
|
|
103
107
|
// Initialize context
|
|
104
108
|
const ctx = context ?? createContext(maxDepth);
|
|
105
109
|
// Check depth limit
|
|
@@ -122,15 +126,14 @@ export class SubagentOrchestrator {
|
|
|
122
126
|
const moduleContextMode = module.context ?? 'main';
|
|
123
127
|
// Parse @call directives from prompt
|
|
124
128
|
const calls = parseCalls(module.prompt);
|
|
125
|
-
const callResults =
|
|
129
|
+
const callResults = [];
|
|
126
130
|
// Resolve each @call directive
|
|
127
131
|
for (const call of calls) {
|
|
128
132
|
const childModule = call.module;
|
|
129
133
|
const childArgs = call.args;
|
|
130
134
|
// Prepare child input
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
: { ...input };
|
|
135
|
+
const hasChildArgs = childArgs.length > 0;
|
|
136
|
+
const childInput = hasChildArgs ? {} : { ...input };
|
|
134
137
|
// Determine child context
|
|
135
138
|
const childContext = moduleContextMode === 'fork'
|
|
136
139
|
? forkContext(ctx, moduleName)
|
|
@@ -138,20 +141,21 @@ export class SubagentOrchestrator {
|
|
|
138
141
|
// Recursively run child module
|
|
139
142
|
const childResult = await this.run(childModule, {
|
|
140
143
|
input: childInput,
|
|
144
|
+
args: hasChildArgs ? childArgs : undefined,
|
|
141
145
|
validateInput: false, // Skip validation for @call args
|
|
142
146
|
validateOutput
|
|
143
147
|
}, childContext);
|
|
144
148
|
// Store result
|
|
145
149
|
if (childResult.ok && 'data' in childResult) {
|
|
146
|
-
callResults
|
|
150
|
+
callResults.push({ match: call.match, module: call.module, result: childResult.data });
|
|
147
151
|
}
|
|
148
152
|
else if ('error' in childResult) {
|
|
149
|
-
callResults
|
|
153
|
+
callResults.push({ match: call.match, module: call.module, result: { error: childResult.error } });
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
// Substitute call results into prompt
|
|
153
157
|
let modifiedModule = module;
|
|
154
|
-
if (
|
|
158
|
+
if (callResults.length > 0) {
|
|
155
159
|
const modifiedPrompt = substituteCallResults(module.prompt, callResults);
|
|
156
160
|
modifiedModule = {
|
|
157
161
|
...module,
|
|
@@ -161,6 +165,7 @@ export class SubagentOrchestrator {
|
|
|
161
165
|
// Run the module
|
|
162
166
|
const result = await runModule(modifiedModule, this.provider, {
|
|
163
167
|
input,
|
|
168
|
+
args,
|
|
164
169
|
validateInput,
|
|
165
170
|
validateOutput,
|
|
166
171
|
verbose: false,
|
|
@@ -45,7 +45,7 @@ export async function validateModule(modulePath, v22 = false) {
|
|
|
45
45
|
else if (hasModuleMd) {
|
|
46
46
|
// v1 format
|
|
47
47
|
if (v22) {
|
|
48
|
-
errors.push("Module is v1 format. Use '
|
|
48
|
+
errors.push("Module is v1 format. Use 'cog migrate' to upgrade to v2.2");
|
|
49
49
|
return { valid: false, errors, warnings };
|
|
50
50
|
}
|
|
51
51
|
return validateV1Format(modulePath);
|
|
@@ -53,7 +53,7 @@ export async function validateModule(modulePath, v22 = false) {
|
|
|
53
53
|
else if (hasOldModuleMd) {
|
|
54
54
|
// v0 format
|
|
55
55
|
if (v22) {
|
|
56
|
-
errors.push("Module is v0 format. Use '
|
|
56
|
+
errors.push("Module is v0 format. Use 'cog migrate' to upgrade to v2.2");
|
|
57
57
|
return { valid: false, errors, warnings };
|
|
58
58
|
}
|
|
59
59
|
return validateV0Format(modulePath);
|
|
@@ -73,7 +73,8 @@ async function validateV22Format(modulePath) {
|
|
|
73
73
|
let manifest;
|
|
74
74
|
try {
|
|
75
75
|
const content = await fs.readFile(moduleYamlPath, 'utf-8');
|
|
76
|
-
|
|
76
|
+
const loaded = yaml.load(content);
|
|
77
|
+
manifest = loaded && typeof loaded === 'object' ? loaded : {};
|
|
77
78
|
}
|
|
78
79
|
catch (e) {
|
|
79
80
|
errors.push(`Invalid YAML in module.yaml: ${e.message}`);
|
|
@@ -244,7 +245,8 @@ async function validateV2Format(modulePath) {
|
|
|
244
245
|
let manifest;
|
|
245
246
|
try {
|
|
246
247
|
const content = await fs.readFile(moduleYamlPath, 'utf-8');
|
|
247
|
-
|
|
248
|
+
const loaded = yaml.load(content);
|
|
249
|
+
manifest = loaded && typeof loaded === 'object' ? loaded : {};
|
|
248
250
|
}
|
|
249
251
|
catch (e) {
|
|
250
252
|
errors.push(`Invalid YAML in module.yaml: ${e.message}`);
|
|
@@ -297,7 +299,7 @@ async function validateV2Format(modulePath) {
|
|
|
297
299
|
}
|
|
298
300
|
// Check for v2.2 features and suggest upgrade
|
|
299
301
|
if (!manifest.tier) {
|
|
300
|
-
warnings.push("Consider adding 'tier' for v2.2 (use '
|
|
302
|
+
warnings.push("Consider adding 'tier' for v2.2 (use 'cog validate --v22' for full check)");
|
|
301
303
|
}
|
|
302
304
|
return { valid: errors.length === 0, errors, warnings };
|
|
303
305
|
}
|
|
@@ -573,6 +575,9 @@ export function validateV22Envelope(response) {
|
|
|
573
575
|
if (!('meta' in response)) {
|
|
574
576
|
errors.push("Missing 'meta' field (required for v2.2)");
|
|
575
577
|
}
|
|
578
|
+
else if (typeof response.meta !== 'object' || response.meta === null || Array.isArray(response.meta)) {
|
|
579
|
+
errors.push("meta must be an object");
|
|
580
|
+
}
|
|
576
581
|
else {
|
|
577
582
|
const meta = response.meta;
|
|
578
583
|
if (!('confidence' in meta)) {
|
|
@@ -596,8 +601,11 @@ export function validateV22Envelope(response) {
|
|
|
596
601
|
if (!('explain' in meta)) {
|
|
597
602
|
errors.push("meta missing 'explain'");
|
|
598
603
|
}
|
|
604
|
+
else if (typeof meta.explain !== 'string') {
|
|
605
|
+
errors.push("meta.explain must be a string");
|
|
606
|
+
}
|
|
599
607
|
else {
|
|
600
|
-
const explain = meta.explain
|
|
608
|
+
const explain = meta.explain;
|
|
601
609
|
if (explain.length > 280) {
|
|
602
610
|
errors.push(`meta.explain exceeds 280 chars (${explain.length} chars)`);
|
|
603
611
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic Provider - Claude API
|
|
3
|
+
*
|
|
4
|
+
* Supports both streaming and non-streaming invocation.
|
|
3
5
|
*/
|
|
4
6
|
import { BaseProvider } from './base.js';
|
|
5
7
|
import type { InvokeParams, InvokeResult } from '../types.js';
|
|
@@ -10,5 +12,18 @@ export declare class AnthropicProvider extends BaseProvider {
|
|
|
10
12
|
private baseUrl;
|
|
11
13
|
constructor(apiKey?: string, model?: string);
|
|
12
14
|
isConfigured(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Anthropic supports streaming.
|
|
17
|
+
*/
|
|
18
|
+
supportsStreaming(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Build request body for Anthropic API
|
|
21
|
+
*/
|
|
22
|
+
private buildRequestBody;
|
|
13
23
|
invoke(params: InvokeParams): Promise<InvokeResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Stream-based invoke using Anthropic's streaming API.
|
|
26
|
+
* Yields content chunks as they arrive from the API.
|
|
27
|
+
*/
|
|
28
|
+
invokeStream(params: InvokeParams): AsyncGenerator<string, InvokeResult, unknown>;
|
|
14
29
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic Provider - Claude API
|
|
3
|
+
*
|
|
4
|
+
* Supports both streaming and non-streaming invocation.
|
|
3
5
|
*/
|
|
4
6
|
import { BaseProvider } from './base.js';
|
|
5
7
|
export class AnthropicProvider extends BaseProvider {
|
|
@@ -15,11 +17,16 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
15
17
|
isConfigured() {
|
|
16
18
|
return !!this.apiKey;
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Anthropic supports streaming.
|
|
22
|
+
*/
|
|
23
|
+
supportsStreaming() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build request body for Anthropic API
|
|
28
|
+
*/
|
|
29
|
+
buildRequestBody(params, stream) {
|
|
23
30
|
// Extract system message
|
|
24
31
|
const systemMessage = params.messages.find(m => m.role === 'system');
|
|
25
32
|
const otherMessages = params.messages.filter(m => m.role !== 'system');
|
|
@@ -39,10 +46,19 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
39
46
|
model: this.model,
|
|
40
47
|
messages: messages.map(m => ({ role: m.role, content: m.content })),
|
|
41
48
|
max_tokens: params.maxTokens ?? 4096,
|
|
49
|
+
stream,
|
|
42
50
|
};
|
|
43
51
|
if (systemMessage) {
|
|
44
52
|
body.system = systemMessage.content;
|
|
45
53
|
}
|
|
54
|
+
return { body, systemContent: systemMessage?.content };
|
|
55
|
+
}
|
|
56
|
+
async invoke(params) {
|
|
57
|
+
if (!this.isConfigured()) {
|
|
58
|
+
throw new Error('Anthropic API key not configured. Set ANTHROPIC_API_KEY environment variable.');
|
|
59
|
+
}
|
|
60
|
+
const url = `${this.baseUrl}/messages`;
|
|
61
|
+
const { body } = this.buildRequestBody(params, false);
|
|
46
62
|
const response = await fetch(url, {
|
|
47
63
|
method: 'POST',
|
|
48
64
|
headers: {
|
|
@@ -67,4 +83,130 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
67
83
|
} : undefined,
|
|
68
84
|
};
|
|
69
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Stream-based invoke using Anthropic's streaming API.
|
|
88
|
+
* Yields content chunks as they arrive from the API.
|
|
89
|
+
*/
|
|
90
|
+
async *invokeStream(params) {
|
|
91
|
+
if (!this.isConfigured()) {
|
|
92
|
+
throw new Error('Anthropic API key not configured. Set ANTHROPIC_API_KEY environment variable.');
|
|
93
|
+
}
|
|
94
|
+
const url = `${this.baseUrl}/messages`;
|
|
95
|
+
const { body } = this.buildRequestBody(params, true);
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
'x-api-key': this.apiKey,
|
|
101
|
+
'anthropic-version': '2023-06-01',
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(body),
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const error = await response.text();
|
|
107
|
+
throw new Error(`Anthropic API error: ${response.status} - ${error}`);
|
|
108
|
+
}
|
|
109
|
+
if (!response.body) {
|
|
110
|
+
throw new Error('Anthropic API returned no body for streaming request');
|
|
111
|
+
}
|
|
112
|
+
const reader = response.body.getReader();
|
|
113
|
+
const decoder = new TextDecoder('utf-8');
|
|
114
|
+
const collectedChunks = [];
|
|
115
|
+
let usage;
|
|
116
|
+
let buffer = '';
|
|
117
|
+
try {
|
|
118
|
+
while (true) {
|
|
119
|
+
const { done, value } = await reader.read();
|
|
120
|
+
if (done)
|
|
121
|
+
break;
|
|
122
|
+
buffer += decoder.decode(value, { stream: true });
|
|
123
|
+
// Process complete lines from the buffer
|
|
124
|
+
const lines = buffer.split('\n');
|
|
125
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
129
|
+
continue; // Skip empty lines and comments
|
|
130
|
+
if (trimmed.startsWith('data: ')) {
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
133
|
+
// Extract content chunk (content_block_delta event)
|
|
134
|
+
if (data.type === 'content_block_delta' && data.delta?.type === 'text_delta') {
|
|
135
|
+
const text = data.delta.text || '';
|
|
136
|
+
if (text) {
|
|
137
|
+
collectedChunks.push(text);
|
|
138
|
+
yield text;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Extract usage info (message_delta or message_stop event)
|
|
142
|
+
if (data.type === 'message_delta' && data.usage) {
|
|
143
|
+
usage = {
|
|
144
|
+
promptTokens: data.usage.input_tokens || 0,
|
|
145
|
+
completionTokens: data.usage.output_tokens || 0,
|
|
146
|
+
totalTokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Also check message_start for input tokens
|
|
150
|
+
if (data.type === 'message_start' && data.message?.usage) {
|
|
151
|
+
const inputTokens = data.message.usage.input_tokens || 0;
|
|
152
|
+
usage = {
|
|
153
|
+
promptTokens: inputTokens,
|
|
154
|
+
completionTokens: usage?.completionTokens || 0,
|
|
155
|
+
totalTokens: inputTokens + (usage?.completionTokens || 0),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Skip invalid JSON chunks
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Flush decoder and process trailing buffered data even without trailing newline.
|
|
166
|
+
buffer += decoder.decode();
|
|
167
|
+
for (const line of buffer.split('\n')) {
|
|
168
|
+
const trimmed = line.trim();
|
|
169
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
170
|
+
continue;
|
|
171
|
+
if (trimmed.startsWith('data: ')) {
|
|
172
|
+
try {
|
|
173
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
174
|
+
if (data.type === 'content_block_delta' && data.delta?.type === 'text_delta') {
|
|
175
|
+
const text = data.delta.text || '';
|
|
176
|
+
if (text) {
|
|
177
|
+
collectedChunks.push(text);
|
|
178
|
+
yield text;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (data.type === 'message_delta' && data.usage) {
|
|
182
|
+
usage = {
|
|
183
|
+
promptTokens: data.usage.input_tokens || 0,
|
|
184
|
+
completionTokens: data.usage.output_tokens || 0,
|
|
185
|
+
totalTokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (data.type === 'message_start' && data.message?.usage) {
|
|
189
|
+
const inputTokens = data.message.usage.input_tokens || 0;
|
|
190
|
+
usage = {
|
|
191
|
+
promptTokens: inputTokens,
|
|
192
|
+
completionTokens: usage?.completionTokens || 0,
|
|
193
|
+
totalTokens: inputTokens + (usage?.completionTokens || 0),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Skip invalid JSON chunks
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
reader.releaseLock();
|
|
205
|
+
}
|
|
206
|
+
const fullContent = collectedChunks.join('');
|
|
207
|
+
return {
|
|
208
|
+
content: fullContent,
|
|
209
|
+
usage,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
70
212
|
}
|
package/dist/providers/base.d.ts
CHANGED
|
@@ -6,6 +6,17 @@ export declare abstract class BaseProvider implements Provider {
|
|
|
6
6
|
abstract name: string;
|
|
7
7
|
abstract invoke(params: InvokeParams): Promise<InvokeResult>;
|
|
8
8
|
abstract isConfigured(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Check if this provider supports streaming.
|
|
11
|
+
* Override in subclasses that implement streaming.
|
|
12
|
+
*/
|
|
13
|
+
supportsStreaming(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Stream-based invoke (optional).
|
|
16
|
+
* Default implementation falls back to non-streaming invoke.
|
|
17
|
+
* Override in subclasses that support streaming.
|
|
18
|
+
*/
|
|
19
|
+
invokeStream(params: InvokeParams): AsyncGenerator<string, InvokeResult, unknown>;
|
|
9
20
|
protected buildJsonPrompt(schema: object): string;
|
|
10
21
|
protected parseJsonResponse(content: string): unknown;
|
|
11
22
|
}
|
package/dist/providers/base.js
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
* Base Provider - Abstract class for all LLM providers
|
|
3
3
|
*/
|
|
4
4
|
export class BaseProvider {
|
|
5
|
+
/**
|
|
6
|
+
* Check if this provider supports streaming.
|
|
7
|
+
* Override in subclasses that implement streaming.
|
|
8
|
+
*/
|
|
9
|
+
supportsStreaming() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Stream-based invoke (optional).
|
|
14
|
+
* Default implementation falls back to non-streaming invoke.
|
|
15
|
+
* Override in subclasses that support streaming.
|
|
16
|
+
*/
|
|
17
|
+
async *invokeStream(params) {
|
|
18
|
+
// Default fallback: use non-streaming invoke and yield the entire result at once
|
|
19
|
+
const result = await this.invoke(params);
|
|
20
|
+
yield result.content;
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
5
23
|
buildJsonPrompt(schema) {
|
|
6
24
|
return `\n\nYou MUST respond with valid JSON matching this schema:\n${JSON.stringify(schema, null, 2)}\n\nRespond with ONLY the JSON, no markdown code blocks.`;
|
|
7
25
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gemini Provider - Google Gemini API
|
|
3
|
+
*
|
|
4
|
+
* Supports both streaming and non-streaming invocation.
|
|
3
5
|
*/
|
|
4
6
|
import { BaseProvider } from './base.js';
|
|
5
7
|
import type { InvokeParams, InvokeResult } from '../types.js';
|
|
@@ -10,10 +12,23 @@ export declare class GeminiProvider extends BaseProvider {
|
|
|
10
12
|
private baseUrl;
|
|
11
13
|
constructor(apiKey?: string, model?: string);
|
|
12
14
|
isConfigured(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Gemini supports streaming.
|
|
17
|
+
*/
|
|
18
|
+
supportsStreaming(): boolean;
|
|
13
19
|
/**
|
|
14
20
|
* Clean JSON Schema for Gemini API compatibility
|
|
15
21
|
* Removes unsupported fields like additionalProperties
|
|
16
22
|
*/
|
|
17
23
|
private cleanSchemaForGemini;
|
|
24
|
+
/**
|
|
25
|
+
* Build request body for Gemini API
|
|
26
|
+
*/
|
|
27
|
+
private buildRequestBody;
|
|
18
28
|
invoke(params: InvokeParams): Promise<InvokeResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Stream-based invoke using Gemini's streaming API.
|
|
31
|
+
* Yields content chunks as they arrive from the API.
|
|
32
|
+
*/
|
|
33
|
+
invokeStream(params: InvokeParams): AsyncGenerator<string, InvokeResult, unknown>;
|
|
19
34
|
}
|
package/dist/providers/gemini.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gemini Provider - Google Gemini API
|
|
3
|
+
*
|
|
4
|
+
* Supports both streaming and non-streaming invocation.
|
|
3
5
|
*/
|
|
4
6
|
import { BaseProvider } from './base.js';
|
|
5
7
|
export class GeminiProvider extends BaseProvider {
|
|
@@ -15,6 +17,12 @@ export class GeminiProvider extends BaseProvider {
|
|
|
15
17
|
isConfigured() {
|
|
16
18
|
return !!this.apiKey;
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Gemini supports streaming.
|
|
22
|
+
*/
|
|
23
|
+
supportsStreaming() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
18
26
|
/**
|
|
19
27
|
* Clean JSON Schema for Gemini API compatibility
|
|
20
28
|
* Removes unsupported fields like additionalProperties
|
|
@@ -38,11 +46,10 @@ export class GeminiProvider extends BaseProvider {
|
|
|
38
46
|
};
|
|
39
47
|
return clean(schema);
|
|
40
48
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const url = `${this.baseUrl}/models/${this.model}:generateContent?key=${this.apiKey}`;
|
|
49
|
+
/**
|
|
50
|
+
* Build request body for Gemini API
|
|
51
|
+
*/
|
|
52
|
+
buildRequestBody(params) {
|
|
46
53
|
// Convert messages to Gemini format
|
|
47
54
|
const contents = params.messages
|
|
48
55
|
.filter(m => m.role !== 'system')
|
|
@@ -71,6 +78,14 @@ export class GeminiProvider extends BaseProvider {
|
|
|
71
78
|
responseSchema: cleanedSchema,
|
|
72
79
|
};
|
|
73
80
|
}
|
|
81
|
+
return body;
|
|
82
|
+
}
|
|
83
|
+
async invoke(params) {
|
|
84
|
+
if (!this.isConfigured()) {
|
|
85
|
+
throw new Error('Gemini API key not configured. Set GEMINI_API_KEY environment variable.');
|
|
86
|
+
}
|
|
87
|
+
const url = `${this.baseUrl}/models/${this.model}:generateContent?key=${this.apiKey}`;
|
|
88
|
+
const body = this.buildRequestBody(params);
|
|
74
89
|
const response = await fetch(url, {
|
|
75
90
|
method: 'POST',
|
|
76
91
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -91,4 +106,106 @@ export class GeminiProvider extends BaseProvider {
|
|
|
91
106
|
} : undefined,
|
|
92
107
|
};
|
|
93
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Stream-based invoke using Gemini's streaming API.
|
|
111
|
+
* Yields content chunks as they arrive from the API.
|
|
112
|
+
*/
|
|
113
|
+
async *invokeStream(params) {
|
|
114
|
+
if (!this.isConfigured()) {
|
|
115
|
+
throw new Error('Gemini API key not configured. Set GEMINI_API_KEY environment variable.');
|
|
116
|
+
}
|
|
117
|
+
// Use streamGenerateContent endpoint
|
|
118
|
+
const url = `${this.baseUrl}/models/${this.model}:streamGenerateContent?key=${this.apiKey}&alt=sse`;
|
|
119
|
+
const body = this.buildRequestBody(params);
|
|
120
|
+
const response = await fetch(url, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'Content-Type': 'application/json' },
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const error = await response.text();
|
|
127
|
+
throw new Error(`Gemini API error: ${response.status} - ${error}`);
|
|
128
|
+
}
|
|
129
|
+
if (!response.body) {
|
|
130
|
+
throw new Error('Gemini API returned no body for streaming request');
|
|
131
|
+
}
|
|
132
|
+
const reader = response.body.getReader();
|
|
133
|
+
const decoder = new TextDecoder('utf-8');
|
|
134
|
+
const collectedChunks = [];
|
|
135
|
+
let usage;
|
|
136
|
+
let buffer = '';
|
|
137
|
+
try {
|
|
138
|
+
while (true) {
|
|
139
|
+
const { done, value } = await reader.read();
|
|
140
|
+
if (done)
|
|
141
|
+
break;
|
|
142
|
+
buffer += decoder.decode(value, { stream: true });
|
|
143
|
+
// Process complete lines from the buffer
|
|
144
|
+
const lines = buffer.split('\n');
|
|
145
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const trimmed = line.trim();
|
|
148
|
+
if (!trimmed)
|
|
149
|
+
continue;
|
|
150
|
+
if (trimmed.startsWith('data: ')) {
|
|
151
|
+
try {
|
|
152
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
153
|
+
// Extract content chunk
|
|
154
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
155
|
+
if (text) {
|
|
156
|
+
collectedChunks.push(text);
|
|
157
|
+
yield text;
|
|
158
|
+
}
|
|
159
|
+
// Extract usage info (usually in the last chunk)
|
|
160
|
+
if (data.usageMetadata) {
|
|
161
|
+
usage = {
|
|
162
|
+
promptTokens: data.usageMetadata.promptTokenCount || 0,
|
|
163
|
+
completionTokens: data.usageMetadata.candidatesTokenCount || 0,
|
|
164
|
+
totalTokens: data.usageMetadata.totalTokenCount || 0,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Skip invalid JSON chunks
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Flush decoder and process trailing buffered data even without trailing newline.
|
|
175
|
+
buffer += decoder.decode();
|
|
176
|
+
for (const line of buffer.split('\n')) {
|
|
177
|
+
const trimmed = line.trim();
|
|
178
|
+
if (!trimmed)
|
|
179
|
+
continue;
|
|
180
|
+
if (trimmed.startsWith('data: ')) {
|
|
181
|
+
try {
|
|
182
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
183
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
184
|
+
if (text) {
|
|
185
|
+
collectedChunks.push(text);
|
|
186
|
+
yield text;
|
|
187
|
+
}
|
|
188
|
+
if (data.usageMetadata) {
|
|
189
|
+
usage = {
|
|
190
|
+
promptTokens: data.usageMetadata.promptTokenCount || 0,
|
|
191
|
+
completionTokens: data.usageMetadata.candidatesTokenCount || 0,
|
|
192
|
+
totalTokens: data.usageMetadata.totalTokenCount || 0,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Skip invalid JSON chunks
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
reader.releaseLock();
|
|
204
|
+
}
|
|
205
|
+
const fullContent = collectedChunks.join('');
|
|
206
|
+
return {
|
|
207
|
+
content: fullContent,
|
|
208
|
+
usage,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
94
211
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ollama Provider - Local LLM via Ollama
|
|
3
|
+
*
|
|
4
|
+
* Supports both streaming and non-streaming invocation.
|
|
3
5
|
*/
|
|
4
6
|
import { BaseProvider } from './base.js';
|
|
5
7
|
import type { InvokeParams, InvokeResult } from '../types.js';
|
|
@@ -9,5 +11,18 @@ export declare class OllamaProvider extends BaseProvider {
|
|
|
9
11
|
private baseUrl;
|
|
10
12
|
constructor(model?: string, baseUrl?: string);
|
|
11
13
|
isConfigured(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Ollama supports streaming.
|
|
16
|
+
*/
|
|
17
|
+
supportsStreaming(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Build request body for Ollama API
|
|
20
|
+
*/
|
|
21
|
+
private buildRequestBody;
|
|
12
22
|
invoke(params: InvokeParams): Promise<InvokeResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Stream-based invoke using Ollama's streaming API.
|
|
25
|
+
* Yields content chunks as they arrive from the API.
|
|
26
|
+
*/
|
|
27
|
+
invokeStream(params: InvokeParams): AsyncGenerator<string, InvokeResult, unknown>;
|
|
13
28
|
}
|