cognitive-runtime 0.2.1 → 0.4.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/cli.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import { parseArgs } from 'node:util';
11
11
  import { getProvider, listProviders } from './providers/index.js';
12
12
  import { run, list, pipe, init } from './commands/index.js';
13
- const VERSION = '0.2.1';
13
+ const VERSION = '0.4.0';
14
14
  async function main() {
15
15
  const args = process.argv.slice(2);
16
16
  const command = args[0];
@@ -29,6 +29,7 @@ async function main() {
29
29
  args: { type: 'string', short: 'a' },
30
30
  input: { type: 'string', short: 'i' },
31
31
  module: { type: 'string', short: 'm' },
32
+ model: { type: 'string', short: 'M' },
32
33
  provider: { type: 'string', short: 'p' },
33
34
  pretty: { type: 'boolean', default: false },
34
35
  verbose: { type: 'boolean', short: 'V', default: false },
@@ -39,7 +40,7 @@ async function main() {
39
40
  // Get provider
40
41
  let provider;
41
42
  try {
42
- provider = getProvider(values.provider);
43
+ provider = getProvider(values.provider, values.model);
43
44
  }
44
45
  catch (e) {
45
46
  console.error(`Error: ${e instanceof Error ? e.message : e}`);
@@ -174,7 +175,8 @@ OPTIONS:
174
175
  -a, --args <str> Arguments to pass to module
175
176
  -i, --input <json> JSON input for module
176
177
  -m, --module <name> Module name (for pipe)
177
- -p, --provider <name> LLM provider (gemini, openai, anthropic)
178
+ -M, --model <name> LLM model (e.g., gpt-4o, gemini-2.0-flash)
179
+ -p, --provider <name> LLM provider (gemini, openai, anthropic, deepseek, minimax, moonshot, qwen, ollama)
178
180
  --pretty Pretty-print JSON output
179
181
  -V, --verbose Verbose output
180
182
  --no-validate Skip schema validation
@@ -183,6 +185,7 @@ OPTIONS:
183
185
 
184
186
  EXAMPLES:
185
187
  cog run code-reviewer --args "def foo(): pass"
188
+ cog run code-reviewer --provider openai --model gpt-4o --args "..."
186
189
  cog list
187
190
  echo "review this code" | cog pipe --module code-reviewer
188
191
  cog init my-module
@@ -197,6 +200,7 @@ ENVIRONMENT:
197
200
  MOONSHOT_API_KEY Moonshot (Kimi)
198
201
  DASHSCOPE_API_KEY Alibaba Qwen (通义千问)
199
202
  OLLAMA_HOST Ollama local (default: localhost:11434)
203
+ COG_MODEL Override default model for any provider
200
204
  `);
201
205
  }
202
206
  main().catch(e => {
@@ -36,8 +36,6 @@ export async function pipe(ctx, options) {
36
36
  const result = await runModule(module, ctx.provider, {
37
37
  args: inputData ? undefined : input,
38
38
  input: inputData,
39
- validateInput: !options.noValidate,
40
- validateOutput: !options.noValidate,
41
39
  });
42
40
  // Output JSON to stdout
43
41
  console.log(JSON.stringify(result.output));
@@ -30,8 +30,6 @@ export async function run(moduleName, ctx, options = {}) {
30
30
  const result = await runModule(module, ctx.provider, {
31
31
  args: options.args,
32
32
  input: inputData,
33
- validateInput: !options.noValidate,
34
- validateOutput: !options.noValidate,
35
33
  verbose: options.verbose || ctx.verbose,
36
34
  });
37
35
  return {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Exports all public APIs for programmatic use.
5
5
  */
6
- export type { Provider, InvokeParams, InvokeResult, Message, CognitiveModule, ModuleResult, CommandContext, CommandResult, } from './types.js';
6
+ export type { Provider, InvokeParams, InvokeResult, Message, CognitiveModule, ModuleResult, ModuleInput, ModuleConstraints, ToolsPolicy, OutputContract, FailureContract, CommandContext, CommandResult, } from './types.js';
7
7
  export { getProvider, listProviders, GeminiProvider, OpenAIProvider, AnthropicProvider, BaseProvider, } from './providers/index.js';
8
8
  export { loadModule, findModule, listModules, getDefaultSearchPaths, runModule, } from './modules/index.js';
9
9
  export { run, list, pipe } from './commands/index.js';
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * Module Loader - Load and parse Cognitive Modules
3
+ * Supports both v1 (MODULE.md) and v2 (module.yaml + prompt.md) formats
3
4
  */
4
5
  import type { CognitiveModule } from '../types.js';
6
+ /**
7
+ * Load a Cognitive Module (auto-detects format)
8
+ */
5
9
  export declare function loadModule(modulePath: string): Promise<CognitiveModule>;
6
10
  export declare function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null>;
7
11
  export declare function listModules(searchPaths: string[]): Promise<CognitiveModule[]>;
@@ -1,11 +1,78 @@
1
1
  /**
2
2
  * Module Loader - Load and parse Cognitive Modules
3
+ * Supports both v1 (MODULE.md) and v2 (module.yaml + prompt.md) formats
3
4
  */
4
5
  import * as fs from 'node:fs/promises';
5
6
  import * as path from 'node:path';
6
7
  import yaml from 'js-yaml';
7
8
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
8
- export async function loadModule(modulePath) {
9
+ /**
10
+ * Detect module format version
11
+ */
12
+ async function detectFormat(modulePath) {
13
+ const v2Manifest = path.join(modulePath, 'module.yaml');
14
+ try {
15
+ await fs.access(v2Manifest);
16
+ return 'v2';
17
+ }
18
+ catch {
19
+ return 'v1';
20
+ }
21
+ }
22
+ /**
23
+ * Load v2 format module (module.yaml + prompt.md)
24
+ */
25
+ async function loadModuleV2(modulePath) {
26
+ const manifestFile = path.join(modulePath, 'module.yaml');
27
+ const promptFile = path.join(modulePath, 'prompt.md');
28
+ const schemaFile = path.join(modulePath, 'schema.json');
29
+ // Read module.yaml
30
+ const manifestContent = await fs.readFile(manifestFile, 'utf-8');
31
+ const manifest = yaml.load(manifestContent);
32
+ // Read prompt.md
33
+ let prompt = '';
34
+ try {
35
+ prompt = await fs.readFile(promptFile, 'utf-8');
36
+ }
37
+ catch {
38
+ // prompt.md is optional, manifest may include inline prompt
39
+ }
40
+ // Read schema.json
41
+ let inputSchema;
42
+ let outputSchema;
43
+ let errorSchema;
44
+ try {
45
+ const schemaContent = await fs.readFile(schemaFile, 'utf-8');
46
+ const schema = JSON.parse(schemaContent);
47
+ inputSchema = schema.input;
48
+ outputSchema = schema.output;
49
+ errorSchema = schema.error;
50
+ }
51
+ catch {
52
+ // Schema file is optional but recommended
53
+ }
54
+ return {
55
+ name: manifest.name || path.basename(modulePath),
56
+ version: manifest.version || '1.0.0',
57
+ responsibility: manifest.responsibility || '',
58
+ excludes: manifest.excludes || [],
59
+ constraints: manifest.constraints,
60
+ tools: manifest.tools,
61
+ output: manifest.output,
62
+ failure: manifest.failure,
63
+ context: manifest.context,
64
+ prompt,
65
+ inputSchema,
66
+ outputSchema,
67
+ errorSchema,
68
+ location: modulePath,
69
+ format: 'v2',
70
+ };
71
+ }
72
+ /**
73
+ * Load v1 format module (MODULE.md with frontmatter)
74
+ */
75
+ async function loadModuleV1(modulePath) {
9
76
  const moduleFile = path.join(modulePath, 'MODULE.md');
10
77
  const schemaFile = path.join(modulePath, 'schema.json');
11
78
  // Read MODULE.md
@@ -29,29 +96,66 @@ export async function loadModule(modulePath) {
29
96
  catch {
30
97
  // Schema file is optional
31
98
  }
99
+ // Extract constraints from v1 format
100
+ const constraints = {};
101
+ const v1Constraints = frontmatter.constraints;
102
+ if (v1Constraints) {
103
+ constraints.no_network = v1Constraints.no_network;
104
+ constraints.no_side_effects = v1Constraints.no_side_effects;
105
+ constraints.no_inventing_data = v1Constraints.no_inventing_data;
106
+ }
32
107
  return {
33
108
  name: frontmatter.name || path.basename(modulePath),
34
109
  version: frontmatter.version || '1.0.0',
35
110
  responsibility: frontmatter.responsibility || '',
36
111
  excludes: frontmatter.excludes || [],
112
+ constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
37
113
  context: frontmatter.context,
38
114
  prompt,
39
115
  inputSchema,
40
116
  outputSchema,
41
117
  location: modulePath,
118
+ format: 'v1',
42
119
  };
43
120
  }
121
+ /**
122
+ * Load a Cognitive Module (auto-detects format)
123
+ */
124
+ export async function loadModule(modulePath) {
125
+ const format = await detectFormat(modulePath);
126
+ if (format === 'v2') {
127
+ return loadModuleV2(modulePath);
128
+ }
129
+ else {
130
+ return loadModuleV1(modulePath);
131
+ }
132
+ }
133
+ /**
134
+ * Check if a directory contains a valid module
135
+ */
136
+ async function isValidModule(modulePath) {
137
+ const v2Manifest = path.join(modulePath, 'module.yaml');
138
+ const v1Module = path.join(modulePath, 'MODULE.md');
139
+ try {
140
+ await fs.access(v2Manifest);
141
+ return true;
142
+ }
143
+ catch {
144
+ try {
145
+ await fs.access(v1Module);
146
+ return true;
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ }
152
+ }
44
153
  export async function findModule(name, searchPaths) {
45
154
  for (const basePath of searchPaths) {
46
155
  const modulePath = path.join(basePath, name);
47
- const moduleFile = path.join(modulePath, 'MODULE.md');
48
- try {
49
- await fs.access(moduleFile);
156
+ if (await isValidModule(modulePath)) {
50
157
  return await loadModule(modulePath);
51
158
  }
52
- catch {
53
- // Module not found in this path, continue
54
- }
55
159
  }
56
160
  return null;
57
161
  }
@@ -63,14 +167,14 @@ export async function listModules(searchPaths) {
63
167
  for (const entry of entries) {
64
168
  if (entry.isDirectory()) {
65
169
  const modulePath = path.join(basePath, entry.name);
66
- const moduleFile = path.join(modulePath, 'MODULE.md');
67
- try {
68
- await fs.access(moduleFile);
69
- const module = await loadModule(modulePath);
70
- modules.push(module);
71
- }
72
- catch {
73
- // Not a valid module, skip
170
+ if (await isValidModule(modulePath)) {
171
+ try {
172
+ const module = await loadModule(modulePath);
173
+ modules.push(module);
174
+ }
175
+ catch {
176
+ // Skip invalid modules
177
+ }
74
178
  }
75
179
  }
76
180
  }
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
- import type { Provider, CognitiveModule, ModuleResult } from '../types.js';
5
+ import type { Provider, CognitiveModule, ModuleResult, ModuleInput } from '../types.js';
5
6
  export interface RunOptions {
7
+ input?: ModuleInput;
6
8
  args?: string;
7
- input?: Record<string, unknown>;
8
- validateInput?: boolean;
9
- validateOutput?: boolean;
10
9
  verbose?: boolean;
11
10
  }
12
11
  export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
@@ -1,43 +1,68 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
5
  export async function runModule(module, provider, options = {}) {
5
6
  const { args, input, verbose = false } = options;
6
- // Build input data
7
- let inputData = input || {};
8
- if (args) {
9
- inputData = { $ARGUMENTS: args, query: args };
7
+ // Build clean input data (v2 style: no $ARGUMENTS pollution)
8
+ const inputData = input || {};
9
+ // Map legacy --args to clean input
10
+ if (args && !inputData.code && !inputData.query) {
11
+ // Determine if args looks like code or natural language
12
+ if (looksLikeCode(args)) {
13
+ inputData.code = args;
14
+ }
15
+ else {
16
+ inputData.query = args;
17
+ }
10
18
  }
11
- // Build prompt
19
+ // Build prompt with clean substitution
12
20
  const prompt = buildPrompt(module, inputData);
13
21
  if (verbose) {
22
+ console.error('--- Module ---');
23
+ console.error(`Name: ${module.name} (${module.format})`);
24
+ console.error(`Responsibility: ${module.responsibility}`);
25
+ console.error('--- Input ---');
26
+ console.error(JSON.stringify(inputData, null, 2));
14
27
  console.error('--- Prompt ---');
15
28
  console.error(prompt);
16
- console.error('--- End Prompt ---');
29
+ console.error('--- End ---');
30
+ }
31
+ // Build system message based on module config
32
+ const systemParts = [
33
+ `You are executing the "${module.name}" Cognitive Module.`,
34
+ '',
35
+ `RESPONSIBILITY: ${module.responsibility}`,
36
+ ];
37
+ if (module.excludes.length > 0) {
38
+ systemParts.push('', 'YOU MUST NOT:');
39
+ module.excludes.forEach(e => systemParts.push(`- ${e}`));
40
+ }
41
+ if (module.constraints) {
42
+ systemParts.push('', 'CONSTRAINTS:');
43
+ if (module.constraints.no_network)
44
+ systemParts.push('- No network access');
45
+ if (module.constraints.no_side_effects)
46
+ systemParts.push('- No side effects');
47
+ if (module.constraints.no_file_write)
48
+ systemParts.push('- No file writes');
49
+ if (module.constraints.no_inventing_data)
50
+ systemParts.push('- Do not invent data');
51
+ }
52
+ if (module.output?.require_behavior_equivalence) {
53
+ systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
54
+ systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
55
+ systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
56
+ }
57
+ systemParts.push('', 'OUTPUT FORMAT:');
58
+ systemParts.push('- Respond with ONLY valid JSON');
59
+ systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
60
+ if (module.output?.require_behavior_equivalence) {
61
+ systemParts.push('- Include "behavior_equivalence" (boolean) field');
17
62
  }
18
- // Build messages
19
63
  const messages = [
20
- {
21
- role: 'system',
22
- content: `You are executing the "${module.name}" Cognitive Module.
23
-
24
- RESPONSIBILITY: ${module.responsibility}
25
-
26
- YOU MUST NOT:
27
- ${module.excludes.map(e => `- ${e}`).join('\n')}
28
-
29
- REQUIRED OUTPUT FORMAT:
30
- You MUST respond with a valid JSON object. Include these fields:
31
- - All fields required by the output schema
32
- - "confidence": a number between 0 and 1
33
- - "rationale": a string explaining your reasoning
34
-
35
- Respond with ONLY valid JSON, no markdown code blocks.`,
36
- },
37
- {
38
- role: 'user',
39
- content: prompt,
40
- },
64
+ { role: 'system', content: systemParts.join('\n') },
65
+ { role: 'user', content: prompt },
41
66
  ];
42
67
  // Invoke provider
43
68
  const result = await provider.invoke({
@@ -53,7 +78,6 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
53
78
  // Parse response
54
79
  let output;
55
80
  try {
56
- // Try to extract JSON from markdown code blocks
57
81
  const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
58
82
  const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
59
83
  output = JSON.parse(jsonStr.trim());
@@ -61,33 +85,65 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
61
85
  catch {
62
86
  throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
63
87
  }
64
- // Extract confidence and rationale
88
+ // Extract standard fields
65
89
  const outputObj = output;
66
90
  const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
67
91
  const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
92
+ const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
93
+ ? outputObj.behavior_equivalence
94
+ : undefined;
68
95
  return {
69
96
  output,
70
97
  confidence,
71
98
  rationale,
99
+ behaviorEquivalence,
72
100
  raw: result.content,
73
101
  };
74
102
  }
75
- function buildPrompt(module, inputData) {
103
+ /**
104
+ * Build prompt with clean variable substitution
105
+ */
106
+ function buildPrompt(module, input) {
76
107
  let prompt = module.prompt;
77
- // Substitute $ARGUMENTS
78
- const argsValue = String(inputData.$ARGUMENTS || inputData.query || '');
108
+ // v2 style: substitute ${variable} placeholders
109
+ for (const [key, value] of Object.entries(input)) {
110
+ const strValue = typeof value === 'string' ? value : JSON.stringify(value);
111
+ prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
112
+ }
113
+ // v1 compatibility: substitute $ARGUMENTS
114
+ const argsValue = input.code || input.query || '';
79
115
  prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
80
- // Substitute $N placeholders
81
- const argsList = argsValue.split(/\s+/);
82
- argsList.forEach((arg, i) => {
83
- prompt = prompt.replace(new RegExp(`\\$${i}`, 'g'), arg);
84
- prompt = prompt.replace(new RegExp(`\\$ARGUMENTS\\[${i}\\]`, 'g'), arg);
85
- });
86
- // Substitute other input fields
87
- for (const [key, value] of Object.entries(inputData)) {
88
- if (key !== '$ARGUMENTS' && key !== 'query') {
89
- prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
116
+ // Substitute $N placeholders (v1 compatibility)
117
+ if (typeof argsValue === 'string') {
118
+ const argsList = argsValue.split(/\s+/);
119
+ argsList.forEach((arg, i) => {
120
+ prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
121
+ });
122
+ }
123
+ // Append input summary if not already in prompt
124
+ if (!prompt.includes(argsValue) && argsValue) {
125
+ prompt += '\n\n## Input\n\n';
126
+ if (input.code) {
127
+ prompt += '```\n' + input.code + '\n```\n';
128
+ }
129
+ if (input.query) {
130
+ prompt += input.query + '\n';
131
+ }
132
+ if (input.language) {
133
+ prompt += `\nLanguage: ${input.language}\n`;
90
134
  }
91
135
  }
92
136
  return prompt;
93
137
  }
138
+ /**
139
+ * Heuristic to detect if input looks like code
140
+ */
141
+ function looksLikeCode(str) {
142
+ const codeIndicators = [
143
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
144
+ /[{};()]/,
145
+ /=>/,
146
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
147
+ ];
148
+ return codeIndicators.some(re => re.test(str));
149
+ }
@@ -11,7 +11,7 @@ export { DeepSeekProvider } from './deepseek.js';
11
11
  export { MoonshotProvider } from './moonshot.js';
12
12
  export { QwenProvider } from './qwen.js';
13
13
  export { OllamaProvider } from './ollama.js';
14
- export declare function getProvider(name?: string): Provider;
14
+ export declare function getProvider(name?: string, model?: string): Provider;
15
15
  export declare function listProviders(): Array<{
16
16
  name: string;
17
17
  configured: boolean;
@@ -19,44 +19,46 @@ export { MoonshotProvider } from './moonshot.js';
19
19
  export { QwenProvider } from './qwen.js';
20
20
  export { OllamaProvider } from './ollama.js';
21
21
  const providers = {
22
- gemini: () => new GeminiProvider(),
23
- openai: () => new OpenAIProvider(),
24
- anthropic: () => new AnthropicProvider(),
25
- minimax: () => new MiniMaxProvider(),
26
- deepseek: () => new DeepSeekProvider(),
27
- moonshot: () => new MoonshotProvider(),
28
- kimi: () => new MoonshotProvider(), // Alias
29
- qwen: () => new QwenProvider(),
30
- tongyi: () => new QwenProvider(), // Alias
31
- dashscope: () => new QwenProvider(), // Alias
32
- ollama: () => new OllamaProvider(),
33
- local: () => new OllamaProvider(), // Alias
22
+ gemini: (model) => new GeminiProvider(undefined, model),
23
+ openai: (model) => new OpenAIProvider(undefined, model),
24
+ anthropic: (model) => new AnthropicProvider(undefined, model),
25
+ minimax: (model) => new MiniMaxProvider(undefined, model),
26
+ deepseek: (model) => new DeepSeekProvider(undefined, model),
27
+ moonshot: (model) => new MoonshotProvider(undefined, model),
28
+ kimi: (model) => new MoonshotProvider(undefined, model), // Alias
29
+ qwen: (model) => new QwenProvider(undefined, model),
30
+ tongyi: (model) => new QwenProvider(undefined, model), // Alias
31
+ dashscope: (model) => new QwenProvider(undefined, model), // Alias
32
+ ollama: (model) => new OllamaProvider(model),
33
+ local: (model) => new OllamaProvider(model), // Alias
34
34
  };
35
- export function getProvider(name) {
35
+ export function getProvider(name, model) {
36
+ // Check for model override from environment
37
+ const modelOverride = model || process.env.COG_MODEL;
36
38
  // Auto-detect if not specified
37
39
  if (!name) {
38
40
  if (process.env.GEMINI_API_KEY)
39
- return new GeminiProvider();
41
+ return new GeminiProvider(undefined, modelOverride);
40
42
  if (process.env.OPENAI_API_KEY)
41
- return new OpenAIProvider();
43
+ return new OpenAIProvider(undefined, modelOverride);
42
44
  if (process.env.ANTHROPIC_API_KEY)
43
- return new AnthropicProvider();
45
+ return new AnthropicProvider(undefined, modelOverride);
44
46
  if (process.env.DEEPSEEK_API_KEY)
45
- return new DeepSeekProvider();
47
+ return new DeepSeekProvider(undefined, modelOverride);
46
48
  if (process.env.MINIMAX_API_KEY)
47
- return new MiniMaxProvider();
49
+ return new MiniMaxProvider(undefined, modelOverride);
48
50
  if (process.env.MOONSHOT_API_KEY)
49
- return new MoonshotProvider();
51
+ return new MoonshotProvider(undefined, modelOverride);
50
52
  if (process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY)
51
- return new QwenProvider();
53
+ return new QwenProvider(undefined, modelOverride);
52
54
  // Ollama is always available as fallback if nothing else is configured
53
- return new OllamaProvider();
55
+ return new OllamaProvider(modelOverride);
54
56
  }
55
57
  const factory = providers[name.toLowerCase()];
56
58
  if (!factory) {
57
59
  throw new Error(`Unknown provider: ${name}. Available: ${Object.keys(providers).join(', ')}`);
58
60
  }
59
- return factory();
61
+ return factory(modelOverride);
60
62
  }
61
63
  export function listProviders() {
62
64
  return [
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
  export interface Provider {
5
6
  name: string;
@@ -29,16 +30,41 @@ export interface CognitiveModule {
29
30
  version: string;
30
31
  responsibility: string;
31
32
  excludes: string[];
33
+ constraints?: ModuleConstraints;
34
+ tools?: ToolsPolicy;
35
+ output?: OutputContract;
36
+ failure?: FailureContract;
32
37
  context?: 'fork' | 'main';
33
38
  prompt: string;
34
39
  inputSchema?: object;
35
40
  outputSchema?: object;
41
+ errorSchema?: object;
36
42
  location: string;
43
+ format: 'v1' | 'v2';
44
+ }
45
+ export interface ModuleConstraints {
46
+ no_network?: boolean;
47
+ no_side_effects?: boolean;
48
+ no_file_write?: boolean;
49
+ no_inventing_data?: boolean;
50
+ }
51
+ export interface ToolsPolicy {
52
+ allowed: string[];
53
+ }
54
+ export interface OutputContract {
55
+ mode?: 'json_strict' | 'json_lenient' | 'text';
56
+ require_confidence?: boolean;
57
+ require_rationale?: boolean;
58
+ require_behavior_equivalence?: boolean;
59
+ }
60
+ export interface FailureContract {
61
+ schema?: object;
37
62
  }
38
63
  export interface ModuleResult {
39
64
  output: unknown;
40
65
  confidence: number;
41
66
  rationale: string;
67
+ behaviorEquivalence?: boolean;
42
68
  raw?: string;
43
69
  }
44
70
  export interface CommandContext {
@@ -51,3 +77,10 @@ export interface CommandResult {
51
77
  data?: unknown;
52
78
  error?: string;
53
79
  }
80
+ export interface ModuleInput {
81
+ code?: string;
82
+ query?: string;
83
+ language?: string;
84
+ options?: Record<string, unknown>;
85
+ [key: string]: unknown;
86
+ }
package/dist/types.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognitive-runtime",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Cognitive Runtime - Structured AI Task Execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ import { getProvider, listProviders } from './providers/index.js';
13
13
  import { run, list, pipe, init } from './commands/index.js';
14
14
  import type { CommandContext } from './types.js';
15
15
 
16
- const VERSION = '0.2.1';
16
+ const VERSION = '0.4.0';
17
17
 
18
18
  async function main() {
19
19
  const args = process.argv.slice(2);
@@ -36,6 +36,7 @@ async function main() {
36
36
  args: { type: 'string', short: 'a' },
37
37
  input: { type: 'string', short: 'i' },
38
38
  module: { type: 'string', short: 'm' },
39
+ model: { type: 'string', short: 'M' },
39
40
  provider: { type: 'string', short: 'p' },
40
41
  pretty: { type: 'boolean', default: false },
41
42
  verbose: { type: 'boolean', short: 'V', default: false },
@@ -47,7 +48,7 @@ async function main() {
47
48
  // Get provider
48
49
  let provider;
49
50
  try {
50
- provider = getProvider(values.provider);
51
+ provider = getProvider(values.provider, values.model);
51
52
  } catch (e) {
52
53
  console.error(`Error: ${e instanceof Error ? e.message : e}`);
53
54
  process.exit(1);
@@ -196,7 +197,8 @@ OPTIONS:
196
197
  -a, --args <str> Arguments to pass to module
197
198
  -i, --input <json> JSON input for module
198
199
  -m, --module <name> Module name (for pipe)
199
- -p, --provider <name> LLM provider (gemini, openai, anthropic)
200
+ -M, --model <name> LLM model (e.g., gpt-4o, gemini-2.0-flash)
201
+ -p, --provider <name> LLM provider (gemini, openai, anthropic, deepseek, minimax, moonshot, qwen, ollama)
200
202
  --pretty Pretty-print JSON output
201
203
  -V, --verbose Verbose output
202
204
  --no-validate Skip schema validation
@@ -205,6 +207,7 @@ OPTIONS:
205
207
 
206
208
  EXAMPLES:
207
209
  cog run code-reviewer --args "def foo(): pass"
210
+ cog run code-reviewer --provider openai --model gpt-4o --args "..."
208
211
  cog list
209
212
  echo "review this code" | cog pipe --module code-reviewer
210
213
  cog init my-module
@@ -219,6 +222,7 @@ ENVIRONMENT:
219
222
  MOONSHOT_API_KEY Moonshot (Kimi)
220
223
  DASHSCOPE_API_KEY Alibaba Qwen (通义千问)
221
224
  OLLAMA_HOST Ollama local (default: localhost:11434)
225
+ COG_MODEL Override default model for any provider
222
226
  `);
223
227
  }
224
228
 
@@ -52,8 +52,6 @@ export async function pipe(
52
52
  const result = await runModule(module, ctx.provider, {
53
53
  args: inputData ? undefined : input,
54
54
  input: inputData,
55
- validateInput: !options.noValidate,
56
- validateOutput: !options.noValidate,
57
55
  });
58
56
 
59
57
  // Output JSON to stdout
@@ -47,8 +47,6 @@ export async function run(
47
47
  const result = await runModule(module, ctx.provider, {
48
48
  args: options.args,
49
49
  input: inputData,
50
- validateInput: !options.noValidate,
51
- validateOutput: !options.noValidate,
52
50
  verbose: options.verbose || ctx.verbose,
53
51
  });
54
52
 
package/src/index.ts CHANGED
@@ -12,6 +12,11 @@ export type {
12
12
  Message,
13
13
  CognitiveModule,
14
14
  ModuleResult,
15
+ ModuleInput,
16
+ ModuleConstraints,
17
+ ToolsPolicy,
18
+ OutputContract,
19
+ FailureContract,
15
20
  CommandContext,
16
21
  CommandResult,
17
22
  } from './types.js';
@@ -1,15 +1,86 @@
1
1
  /**
2
2
  * Module Loader - Load and parse Cognitive Modules
3
+ * Supports both v1 (MODULE.md) and v2 (module.yaml + prompt.md) formats
3
4
  */
4
5
 
5
6
  import * as fs from 'node:fs/promises';
6
7
  import * as path from 'node:path';
7
8
  import yaml from 'js-yaml';
8
- import type { CognitiveModule } from '../types.js';
9
+ import type { CognitiveModule, ModuleConstraints, ToolsPolicy, OutputContract, FailureContract } from '../types.js';
9
10
 
10
11
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
11
12
 
12
- export async function loadModule(modulePath: string): Promise<CognitiveModule> {
13
+ /**
14
+ * Detect module format version
15
+ */
16
+ async function detectFormat(modulePath: string): Promise<'v1' | 'v2'> {
17
+ const v2Manifest = path.join(modulePath, 'module.yaml');
18
+ try {
19
+ await fs.access(v2Manifest);
20
+ return 'v2';
21
+ } catch {
22
+ return 'v1';
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Load v2 format module (module.yaml + prompt.md)
28
+ */
29
+ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
30
+ const manifestFile = path.join(modulePath, 'module.yaml');
31
+ const promptFile = path.join(modulePath, 'prompt.md');
32
+ const schemaFile = path.join(modulePath, 'schema.json');
33
+
34
+ // Read module.yaml
35
+ const manifestContent = await fs.readFile(manifestFile, 'utf-8');
36
+ const manifest = yaml.load(manifestContent) as Record<string, unknown>;
37
+
38
+ // Read prompt.md
39
+ let prompt = '';
40
+ try {
41
+ prompt = await fs.readFile(promptFile, 'utf-8');
42
+ } catch {
43
+ // prompt.md is optional, manifest may include inline prompt
44
+ }
45
+
46
+ // Read schema.json
47
+ let inputSchema: object | undefined;
48
+ let outputSchema: object | undefined;
49
+ let errorSchema: object | undefined;
50
+
51
+ try {
52
+ const schemaContent = await fs.readFile(schemaFile, 'utf-8');
53
+ const schema = JSON.parse(schemaContent);
54
+ inputSchema = schema.input;
55
+ outputSchema = schema.output;
56
+ errorSchema = schema.error;
57
+ } catch {
58
+ // Schema file is optional but recommended
59
+ }
60
+
61
+ return {
62
+ name: manifest.name as string || path.basename(modulePath),
63
+ version: manifest.version as string || '1.0.0',
64
+ responsibility: manifest.responsibility as string || '',
65
+ excludes: (manifest.excludes as string[]) || [],
66
+ constraints: manifest.constraints as ModuleConstraints | undefined,
67
+ tools: manifest.tools as ToolsPolicy | undefined,
68
+ output: manifest.output as OutputContract | undefined,
69
+ failure: manifest.failure as FailureContract | undefined,
70
+ context: manifest.context as 'fork' | 'main' | undefined,
71
+ prompt,
72
+ inputSchema,
73
+ outputSchema,
74
+ errorSchema,
75
+ location: modulePath,
76
+ format: 'v2',
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Load v1 format module (MODULE.md with frontmatter)
82
+ */
83
+ async function loadModuleV1(modulePath: string): Promise<CognitiveModule> {
13
84
  const moduleFile = path.join(modulePath, 'MODULE.md');
14
85
  const schemaFile = path.join(modulePath, 'schema.json');
15
86
 
@@ -38,29 +109,69 @@ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
38
109
  // Schema file is optional
39
110
  }
40
111
 
112
+ // Extract constraints from v1 format
113
+ const constraints: ModuleConstraints = {};
114
+ const v1Constraints = frontmatter.constraints as Record<string, boolean> | undefined;
115
+ if (v1Constraints) {
116
+ constraints.no_network = v1Constraints.no_network;
117
+ constraints.no_side_effects = v1Constraints.no_side_effects;
118
+ constraints.no_inventing_data = v1Constraints.no_inventing_data;
119
+ }
120
+
41
121
  return {
42
122
  name: frontmatter.name as string || path.basename(modulePath),
43
123
  version: frontmatter.version as string || '1.0.0',
44
124
  responsibility: frontmatter.responsibility as string || '',
45
125
  excludes: (frontmatter.excludes as string[]) || [],
126
+ constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
46
127
  context: frontmatter.context as 'fork' | 'main' | undefined,
47
128
  prompt,
48
129
  inputSchema,
49
130
  outputSchema,
50
131
  location: modulePath,
132
+ format: 'v1',
51
133
  };
52
134
  }
53
135
 
136
+ /**
137
+ * Load a Cognitive Module (auto-detects format)
138
+ */
139
+ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
140
+ const format = await detectFormat(modulePath);
141
+
142
+ if (format === 'v2') {
143
+ return loadModuleV2(modulePath);
144
+ } else {
145
+ return loadModuleV1(modulePath);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Check if a directory contains a valid module
151
+ */
152
+ async function isValidModule(modulePath: string): Promise<boolean> {
153
+ const v2Manifest = path.join(modulePath, 'module.yaml');
154
+ const v1Module = path.join(modulePath, 'MODULE.md');
155
+
156
+ try {
157
+ await fs.access(v2Manifest);
158
+ return true;
159
+ } catch {
160
+ try {
161
+ await fs.access(v1Module);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+ }
168
+
54
169
  export async function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null> {
55
170
  for (const basePath of searchPaths) {
56
171
  const modulePath = path.join(basePath, name);
57
- const moduleFile = path.join(modulePath, 'MODULE.md');
58
172
 
59
- try {
60
- await fs.access(moduleFile);
173
+ if (await isValidModule(modulePath)) {
61
174
  return await loadModule(modulePath);
62
- } catch {
63
- // Module not found in this path, continue
64
175
  }
65
176
  }
66
177
 
@@ -77,14 +188,14 @@ export async function listModules(searchPaths: string[]): Promise<CognitiveModul
77
188
  for (const entry of entries) {
78
189
  if (entry.isDirectory()) {
79
190
  const modulePath = path.join(basePath, entry.name);
80
- const moduleFile = path.join(modulePath, 'MODULE.md');
81
191
 
82
- try {
83
- await fs.access(moduleFile);
84
- const module = await loadModule(modulePath);
85
- modules.push(module);
86
- } catch {
87
- // Not a valid module, skip
192
+ if (await isValidModule(modulePath)) {
193
+ try {
194
+ const module = await loadModule(modulePath);
195
+ modules.push(module);
196
+ } catch {
197
+ // Skip invalid modules
198
+ }
88
199
  }
89
200
  }
90
201
  }
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
5
 
5
- import type { Provider, CognitiveModule, ModuleResult, Message } from '../types.js';
6
+ import type { Provider, CognitiveModule, ModuleResult, Message, ModuleInput } from '../types.js';
6
7
 
7
8
  export interface RunOptions {
9
+ // Clean input (v2 style)
10
+ input?: ModuleInput;
11
+
12
+ // Legacy CLI args (v1 compatibility) - mapped to input.code or input.query
8
13
  args?: string;
9
- input?: Record<string, unknown>;
10
- validateInput?: boolean;
11
- validateOutput?: boolean;
14
+
15
+ // Runtime options
12
16
  verbose?: boolean;
13
17
  }
14
18
 
@@ -19,44 +23,69 @@ export async function runModule(
19
23
  ): Promise<ModuleResult> {
20
24
  const { args, input, verbose = false } = options;
21
25
 
22
- // Build input data
23
- let inputData: Record<string, unknown> = input || {};
24
- if (args) {
25
- inputData = { $ARGUMENTS: args, query: args };
26
+ // Build clean input data (v2 style: no $ARGUMENTS pollution)
27
+ const inputData: ModuleInput = input || {};
28
+
29
+ // Map legacy --args to clean input
30
+ if (args && !inputData.code && !inputData.query) {
31
+ // Determine if args looks like code or natural language
32
+ if (looksLikeCode(args)) {
33
+ inputData.code = args;
34
+ } else {
35
+ inputData.query = args;
36
+ }
26
37
  }
27
38
 
28
- // Build prompt
39
+ // Build prompt with clean substitution
29
40
  const prompt = buildPrompt(module, inputData);
30
41
 
31
42
  if (verbose) {
43
+ console.error('--- Module ---');
44
+ console.error(`Name: ${module.name} (${module.format})`);
45
+ console.error(`Responsibility: ${module.responsibility}`);
46
+ console.error('--- Input ---');
47
+ console.error(JSON.stringify(inputData, null, 2));
32
48
  console.error('--- Prompt ---');
33
49
  console.error(prompt);
34
- console.error('--- End Prompt ---');
50
+ console.error('--- End ---');
51
+ }
52
+
53
+ // Build system message based on module config
54
+ const systemParts: string[] = [
55
+ `You are executing the "${module.name}" Cognitive Module.`,
56
+ '',
57
+ `RESPONSIBILITY: ${module.responsibility}`,
58
+ ];
59
+
60
+ if (module.excludes.length > 0) {
61
+ systemParts.push('', 'YOU MUST NOT:');
62
+ module.excludes.forEach(e => systemParts.push(`- ${e}`));
63
+ }
64
+
65
+ if (module.constraints) {
66
+ systemParts.push('', 'CONSTRAINTS:');
67
+ if (module.constraints.no_network) systemParts.push('- No network access');
68
+ if (module.constraints.no_side_effects) systemParts.push('- No side effects');
69
+ if (module.constraints.no_file_write) systemParts.push('- No file writes');
70
+ if (module.constraints.no_inventing_data) systemParts.push('- Do not invent data');
71
+ }
72
+
73
+ if (module.output?.require_behavior_equivalence) {
74
+ systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
75
+ systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
76
+ systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
77
+ }
78
+
79
+ systemParts.push('', 'OUTPUT FORMAT:');
80
+ systemParts.push('- Respond with ONLY valid JSON');
81
+ systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
82
+ if (module.output?.require_behavior_equivalence) {
83
+ systemParts.push('- Include "behavior_equivalence" (boolean) field');
35
84
  }
36
85
 
37
- // Build messages
38
86
  const messages: Message[] = [
39
- {
40
- role: 'system',
41
- content: `You are executing the "${module.name}" Cognitive Module.
42
-
43
- RESPONSIBILITY: ${module.responsibility}
44
-
45
- YOU MUST NOT:
46
- ${module.excludes.map(e => `- ${e}`).join('\n')}
47
-
48
- REQUIRED OUTPUT FORMAT:
49
- You MUST respond with a valid JSON object. Include these fields:
50
- - All fields required by the output schema
51
- - "confidence": a number between 0 and 1
52
- - "rationale": a string explaining your reasoning
53
-
54
- Respond with ONLY valid JSON, no markdown code blocks.`,
55
- },
56
- {
57
- role: 'user',
58
- content: prompt,
59
- },
87
+ { role: 'system', content: systemParts.join('\n') },
88
+ { role: 'user', content: prompt },
60
89
  ];
61
90
 
62
91
  // Invoke provider
@@ -75,7 +104,6 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
75
104
  // Parse response
76
105
  let output: unknown;
77
106
  try {
78
- // Try to extract JSON from markdown code blocks
79
107
  const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
80
108
  const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
81
109
  output = JSON.parse(jsonStr.trim());
@@ -83,39 +111,73 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
83
111
  throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
84
112
  }
85
113
 
86
- // Extract confidence and rationale
114
+ // Extract standard fields
87
115
  const outputObj = output as Record<string, unknown>;
88
116
  const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
89
117
  const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
118
+ const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
119
+ ? outputObj.behavior_equivalence
120
+ : undefined;
90
121
 
91
122
  return {
92
123
  output,
93
124
  confidence,
94
125
  rationale,
126
+ behaviorEquivalence,
95
127
  raw: result.content,
96
128
  };
97
129
  }
98
130
 
99
- function buildPrompt(module: CognitiveModule, inputData: Record<string, unknown>): string {
131
+ /**
132
+ * Build prompt with clean variable substitution
133
+ */
134
+ function buildPrompt(module: CognitiveModule, input: ModuleInput): string {
100
135
  let prompt = module.prompt;
101
136
 
102
- // Substitute $ARGUMENTS
103
- const argsValue = String(inputData.$ARGUMENTS || inputData.query || '');
137
+ // v2 style: substitute ${variable} placeholders
138
+ for (const [key, value] of Object.entries(input)) {
139
+ const strValue = typeof value === 'string' ? value : JSON.stringify(value);
140
+ prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
141
+ }
142
+
143
+ // v1 compatibility: substitute $ARGUMENTS
144
+ const argsValue = input.code || input.query || '';
104
145
  prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
105
146
 
106
- // Substitute $N placeholders
107
- const argsList = argsValue.split(/\s+/);
108
- argsList.forEach((arg, i) => {
109
- prompt = prompt.replace(new RegExp(`\\$${i}`, 'g'), arg);
110
- prompt = prompt.replace(new RegExp(`\\$ARGUMENTS\\[${i}\\]`, 'g'), arg);
111
- });
147
+ // Substitute $N placeholders (v1 compatibility)
148
+ if (typeof argsValue === 'string') {
149
+ const argsList = argsValue.split(/\s+/);
150
+ argsList.forEach((arg, i) => {
151
+ prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
152
+ });
153
+ }
112
154
 
113
- // Substitute other input fields
114
- for (const [key, value] of Object.entries(inputData)) {
115
- if (key !== '$ARGUMENTS' && key !== 'query') {
116
- prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
155
+ // Append input summary if not already in prompt
156
+ if (!prompt.includes(argsValue) && argsValue) {
157
+ prompt += '\n\n## Input\n\n';
158
+ if (input.code) {
159
+ prompt += '```\n' + input.code + '\n```\n';
160
+ }
161
+ if (input.query) {
162
+ prompt += input.query + '\n';
163
+ }
164
+ if (input.language) {
165
+ prompt += `\nLanguage: ${input.language}\n`;
117
166
  }
118
167
  }
119
168
 
120
169
  return prompt;
121
170
  }
171
+
172
+ /**
173
+ * Heuristic to detect if input looks like code
174
+ */
175
+ function looksLikeCode(str: string): boolean {
176
+ const codeIndicators = [
177
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
178
+ /[{};()]/,
179
+ /=>/,
180
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
181
+ ];
182
+ return codeIndicators.some(re => re.test(str));
183
+ }
@@ -22,33 +22,38 @@ export { MoonshotProvider } from './moonshot.js';
22
22
  export { QwenProvider } from './qwen.js';
23
23
  export { OllamaProvider } from './ollama.js';
24
24
 
25
- const providers: Record<string, () => Provider> = {
26
- gemini: () => new GeminiProvider(),
27
- openai: () => new OpenAIProvider(),
28
- anthropic: () => new AnthropicProvider(),
29
- minimax: () => new MiniMaxProvider(),
30
- deepseek: () => new DeepSeekProvider(),
31
- moonshot: () => new MoonshotProvider(),
32
- kimi: () => new MoonshotProvider(), // Alias
33
- qwen: () => new QwenProvider(),
34
- tongyi: () => new QwenProvider(), // Alias
35
- dashscope: () => new QwenProvider(), // Alias
36
- ollama: () => new OllamaProvider(),
37
- local: () => new OllamaProvider(), // Alias
25
+ type ProviderFactory = (model?: string) => Provider;
26
+
27
+ const providers: Record<string, ProviderFactory> = {
28
+ gemini: (model) => new GeminiProvider(undefined, model),
29
+ openai: (model) => new OpenAIProvider(undefined, model),
30
+ anthropic: (model) => new AnthropicProvider(undefined, model),
31
+ minimax: (model) => new MiniMaxProvider(undefined, model),
32
+ deepseek: (model) => new DeepSeekProvider(undefined, model),
33
+ moonshot: (model) => new MoonshotProvider(undefined, model),
34
+ kimi: (model) => new MoonshotProvider(undefined, model), // Alias
35
+ qwen: (model) => new QwenProvider(undefined, model),
36
+ tongyi: (model) => new QwenProvider(undefined, model), // Alias
37
+ dashscope: (model) => new QwenProvider(undefined, model), // Alias
38
+ ollama: (model) => new OllamaProvider(model),
39
+ local: (model) => new OllamaProvider(model), // Alias
38
40
  };
39
41
 
40
- export function getProvider(name?: string): Provider {
42
+ export function getProvider(name?: string, model?: string): Provider {
43
+ // Check for model override from environment
44
+ const modelOverride = model || process.env.COG_MODEL;
45
+
41
46
  // Auto-detect if not specified
42
47
  if (!name) {
43
- if (process.env.GEMINI_API_KEY) return new GeminiProvider();
44
- if (process.env.OPENAI_API_KEY) return new OpenAIProvider();
45
- if (process.env.ANTHROPIC_API_KEY) return new AnthropicProvider();
46
- if (process.env.DEEPSEEK_API_KEY) return new DeepSeekProvider();
47
- if (process.env.MINIMAX_API_KEY) return new MiniMaxProvider();
48
- if (process.env.MOONSHOT_API_KEY) return new MoonshotProvider();
49
- if (process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY) return new QwenProvider();
48
+ if (process.env.GEMINI_API_KEY) return new GeminiProvider(undefined, modelOverride);
49
+ if (process.env.OPENAI_API_KEY) return new OpenAIProvider(undefined, modelOverride);
50
+ if (process.env.ANTHROPIC_API_KEY) return new AnthropicProvider(undefined, modelOverride);
51
+ if (process.env.DEEPSEEK_API_KEY) return new DeepSeekProvider(undefined, modelOverride);
52
+ if (process.env.MINIMAX_API_KEY) return new MiniMaxProvider(undefined, modelOverride);
53
+ if (process.env.MOONSHOT_API_KEY) return new MoonshotProvider(undefined, modelOverride);
54
+ if (process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY) return new QwenProvider(undefined, modelOverride);
50
55
  // Ollama is always available as fallback if nothing else is configured
51
- return new OllamaProvider();
56
+ return new OllamaProvider(modelOverride);
52
57
  }
53
58
 
54
59
  const factory = providers[name.toLowerCase()];
@@ -56,7 +61,7 @@ export function getProvider(name?: string): Provider {
56
61
  throw new Error(`Unknown provider: ${name}. Available: ${Object.keys(providers).join(', ')}`);
57
62
  }
58
63
 
59
- return factory();
64
+ return factory(modelOverride);
60
65
  }
61
66
 
62
67
  export function listProviders(): Array<{ name: string; configured: boolean; model: string }> {
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
 
5
6
  // Provider interface - all LLM providers implement this
@@ -30,23 +31,69 @@ export interface InvokeResult {
30
31
  };
31
32
  }
32
33
 
33
- // Module types
34
+ // Module types (v2)
34
35
  export interface CognitiveModule {
36
+ // Core identity
35
37
  name: string;
36
38
  version: string;
37
39
  responsibility: string;
40
+
41
+ // Constraints
38
42
  excludes: string[];
43
+ constraints?: ModuleConstraints;
44
+
45
+ // Tools policy
46
+ tools?: ToolsPolicy;
47
+
48
+ // Output contract
49
+ output?: OutputContract;
50
+
51
+ // Failure contract
52
+ failure?: FailureContract;
53
+
54
+ // Execution context
39
55
  context?: 'fork' | 'main';
56
+
57
+ // Prompt (from prompt.md or MODULE.md body)
40
58
  prompt: string;
59
+
60
+ // Schemas
41
61
  inputSchema?: object;
42
62
  outputSchema?: object;
63
+ errorSchema?: object;
64
+
65
+ // Metadata
43
66
  location: string;
67
+ format: 'v1' | 'v2'; // v1 = MODULE.md, v2 = module.yaml + prompt.md
68
+ }
69
+
70
+ export interface ModuleConstraints {
71
+ no_network?: boolean;
72
+ no_side_effects?: boolean;
73
+ no_file_write?: boolean;
74
+ no_inventing_data?: boolean;
75
+ }
76
+
77
+ export interface ToolsPolicy {
78
+ allowed: string[];
79
+ }
80
+
81
+ export interface OutputContract {
82
+ mode?: 'json_strict' | 'json_lenient' | 'text';
83
+ require_confidence?: boolean;
84
+ require_rationale?: boolean;
85
+ require_behavior_equivalence?: boolean;
86
+ }
87
+
88
+ export interface FailureContract {
89
+ schema?: object;
44
90
  }
45
91
 
46
92
  export interface ModuleResult {
47
93
  output: unknown;
48
94
  confidence: number;
49
95
  rationale: string;
96
+ behaviorEquivalence?: boolean;
50
97
  raw?: string;
51
98
  }
52
99
 
@@ -62,3 +109,12 @@ export interface CommandResult {
62
109
  data?: unknown;
63
110
  error?: string;
64
111
  }
112
+
113
+ // Module input (clean, no CLI pollution)
114
+ export interface ModuleInput {
115
+ code?: string;
116
+ query?: string;
117
+ language?: string;
118
+ options?: Record<string, unknown>;
119
+ [key: string]: unknown;
120
+ }