cognitive-runtime 0.3.0 → 0.5.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/README.md +39 -0
- package/dist/cli.js +1 -1
- package/dist/commands/pipe.js +8 -6
- package/dist/commands/run.js +23 -6
- package/dist/index.d.ts +1 -1
- package/dist/modules/loader.d.ts +4 -0
- package/dist/modules/loader.js +121 -15
- package/dist/modules/runner.d.ts +4 -4
- package/dist/modules/runner.js +186 -50
- package/dist/types.d.ts +83 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/pipe.ts +8 -6
- package/src/commands/run.ts +21 -6
- package/src/index.ts +5 -0
- package/src/modules/loader.ts +127 -14
- package/src/modules/runner.ts +211 -54
- package/src/types.ts +121 -1
package/README.md
CHANGED
|
@@ -65,6 +65,45 @@ echo "review this code" | cog pipe --module code-reviewer
|
|
|
65
65
|
cog doctor
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
## Module Formats
|
|
69
|
+
|
|
70
|
+
### v2 (Recommended)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
my-module/
|
|
74
|
+
├── module.yaml # Machine-readable manifest
|
|
75
|
+
├── prompt.md # Human-readable prompt
|
|
76
|
+
├── schema.json # IO contract
|
|
77
|
+
└── tests/
|
|
78
|
+
├── case1.input.json
|
|
79
|
+
└── case1.expected.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**module.yaml**:
|
|
83
|
+
```yaml
|
|
84
|
+
name: my-module
|
|
85
|
+
version: 2.0.0
|
|
86
|
+
responsibility: What this module does
|
|
87
|
+
constraints:
|
|
88
|
+
no_network: true
|
|
89
|
+
no_side_effects: true
|
|
90
|
+
output:
|
|
91
|
+
mode: json_strict
|
|
92
|
+
require_confidence: true
|
|
93
|
+
require_rationale: true
|
|
94
|
+
require_behavior_equivalence: true
|
|
95
|
+
tools:
|
|
96
|
+
allowed: []
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### v1 (Legacy, still supported)
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
my-module/
|
|
103
|
+
├── MODULE.md # Frontmatter + prompt combined
|
|
104
|
+
└── schema.json
|
|
105
|
+
```
|
|
106
|
+
|
|
68
107
|
## Providers
|
|
69
108
|
|
|
70
109
|
| Provider | Environment Variable | Default Model |
|
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.
|
|
13
|
+
const VERSION = '0.5.0';
|
|
14
14
|
async function main() {
|
|
15
15
|
const args = process.argv.slice(2);
|
|
16
16
|
const command = args[0];
|
package/dist/commands/pipe.js
CHANGED
|
@@ -36,19 +36,21 @@ 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
|
-
// Output
|
|
43
|
-
console.log(JSON.stringify(result
|
|
40
|
+
// Output envelope format to stdout
|
|
41
|
+
console.log(JSON.stringify(result));
|
|
44
42
|
return {
|
|
45
|
-
success:
|
|
43
|
+
success: result.ok,
|
|
46
44
|
data: result,
|
|
47
45
|
};
|
|
48
46
|
}
|
|
49
47
|
catch (e) {
|
|
50
48
|
const error = e instanceof Error ? e.message : String(e);
|
|
51
|
-
|
|
49
|
+
// Output error in envelope format
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
ok: false,
|
|
52
|
+
error: { code: 'RUNTIME_ERROR', message: error }
|
|
53
|
+
}));
|
|
52
54
|
return {
|
|
53
55
|
success: false,
|
|
54
56
|
error,
|
package/dist/commands/run.js
CHANGED
|
@@ -30,14 +30,31 @@ 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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
// Return envelope format or extracted data
|
|
36
|
+
if (options.pretty) {
|
|
37
|
+
return {
|
|
38
|
+
success: result.ok,
|
|
39
|
+
data: result,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// For non-pretty mode, return data (success) or error (failure)
|
|
44
|
+
if (result.ok) {
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
data: result.data,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: `${result.error?.code}: ${result.error?.message}`,
|
|
54
|
+
data: result.partial_data,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
catch (e) {
|
|
43
60
|
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';
|
package/dist/modules/loader.d.ts
CHANGED
|
@@ -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[]>;
|
package/dist/modules/loader.js
CHANGED
|
@@ -1,11 +1,80 @@
|
|
|
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
|
-
|
|
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
|
+
policies: manifest.policies,
|
|
61
|
+
tools: manifest.tools,
|
|
62
|
+
output: manifest.output,
|
|
63
|
+
failure: manifest.failure,
|
|
64
|
+
runtimeRequirements: manifest.runtime_requirements,
|
|
65
|
+
context: manifest.context,
|
|
66
|
+
prompt,
|
|
67
|
+
inputSchema,
|
|
68
|
+
outputSchema,
|
|
69
|
+
errorSchema,
|
|
70
|
+
location: modulePath,
|
|
71
|
+
format: 'v2',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load v1 format module (MODULE.md with frontmatter)
|
|
76
|
+
*/
|
|
77
|
+
async function loadModuleV1(modulePath) {
|
|
9
78
|
const moduleFile = path.join(modulePath, 'MODULE.md');
|
|
10
79
|
const schemaFile = path.join(modulePath, 'schema.json');
|
|
11
80
|
// Read MODULE.md
|
|
@@ -29,29 +98,66 @@ export async function loadModule(modulePath) {
|
|
|
29
98
|
catch {
|
|
30
99
|
// Schema file is optional
|
|
31
100
|
}
|
|
101
|
+
// Extract constraints from v1 format
|
|
102
|
+
const constraints = {};
|
|
103
|
+
const v1Constraints = frontmatter.constraints;
|
|
104
|
+
if (v1Constraints) {
|
|
105
|
+
constraints.no_network = v1Constraints.no_network;
|
|
106
|
+
constraints.no_side_effects = v1Constraints.no_side_effects;
|
|
107
|
+
constraints.no_inventing_data = v1Constraints.no_inventing_data;
|
|
108
|
+
}
|
|
32
109
|
return {
|
|
33
110
|
name: frontmatter.name || path.basename(modulePath),
|
|
34
111
|
version: frontmatter.version || '1.0.0',
|
|
35
112
|
responsibility: frontmatter.responsibility || '',
|
|
36
113
|
excludes: frontmatter.excludes || [],
|
|
114
|
+
constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
|
|
37
115
|
context: frontmatter.context,
|
|
38
116
|
prompt,
|
|
39
117
|
inputSchema,
|
|
40
118
|
outputSchema,
|
|
41
119
|
location: modulePath,
|
|
120
|
+
format: 'v1',
|
|
42
121
|
};
|
|
43
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Load a Cognitive Module (auto-detects format)
|
|
125
|
+
*/
|
|
126
|
+
export async function loadModule(modulePath) {
|
|
127
|
+
const format = await detectFormat(modulePath);
|
|
128
|
+
if (format === 'v2') {
|
|
129
|
+
return loadModuleV2(modulePath);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return loadModuleV1(modulePath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if a directory contains a valid module
|
|
137
|
+
*/
|
|
138
|
+
async function isValidModule(modulePath) {
|
|
139
|
+
const v2Manifest = path.join(modulePath, 'module.yaml');
|
|
140
|
+
const v1Module = path.join(modulePath, 'MODULE.md');
|
|
141
|
+
try {
|
|
142
|
+
await fs.access(v2Manifest);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
try {
|
|
147
|
+
await fs.access(v1Module);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
44
155
|
export async function findModule(name, searchPaths) {
|
|
45
156
|
for (const basePath of searchPaths) {
|
|
46
157
|
const modulePath = path.join(basePath, name);
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
await fs.access(moduleFile);
|
|
158
|
+
if (await isValidModule(modulePath)) {
|
|
50
159
|
return await loadModule(modulePath);
|
|
51
160
|
}
|
|
52
|
-
catch {
|
|
53
|
-
// Module not found in this path, continue
|
|
54
|
-
}
|
|
55
161
|
}
|
|
56
162
|
return null;
|
|
57
163
|
}
|
|
@@ -63,14 +169,14 @@ export async function listModules(searchPaths) {
|
|
|
63
169
|
for (const entry of entries) {
|
|
64
170
|
if (entry.isDirectory()) {
|
|
65
171
|
const modulePath = path.join(basePath, entry.name);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
172
|
+
if (await isValidModule(modulePath)) {
|
|
173
|
+
try {
|
|
174
|
+
const module = await loadModule(modulePath);
|
|
175
|
+
modules.push(module);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Skip invalid modules
|
|
179
|
+
}
|
|
74
180
|
}
|
|
75
181
|
}
|
|
76
182
|
}
|
package/dist/modules/runner.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Runner - Execute Cognitive Modules
|
|
3
|
+
* v2.1: Envelope format support, clean input mapping
|
|
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;
|
|
10
|
+
useEnvelope?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
|
package/dist/modules/runner.js
CHANGED
|
@@ -1,43 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Runner - Execute Cognitive Modules
|
|
3
|
+
* v2.1: Envelope format support, clean input mapping
|
|
3
4
|
*/
|
|
4
5
|
export async function runModule(module, provider, options = {}) {
|
|
5
|
-
const { args, input, verbose = false } = options;
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { args, input, verbose = false, useEnvelope } = options;
|
|
7
|
+
// Determine if we should use envelope format
|
|
8
|
+
const shouldUseEnvelope = useEnvelope ?? (module.output?.envelope === true || module.format === 'v2');
|
|
9
|
+
// Build clean input data (v2 style: no $ARGUMENTS pollution)
|
|
10
|
+
const inputData = input || {};
|
|
11
|
+
// Map legacy --args to clean input
|
|
12
|
+
if (args && !inputData.code && !inputData.query) {
|
|
13
|
+
// Determine if args looks like code or natural language
|
|
14
|
+
if (looksLikeCode(args)) {
|
|
15
|
+
inputData.code = args;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
inputData.query = args;
|
|
19
|
+
}
|
|
10
20
|
}
|
|
11
|
-
// Build prompt
|
|
21
|
+
// Build prompt with clean substitution
|
|
12
22
|
const prompt = buildPrompt(module, inputData);
|
|
13
23
|
if (verbose) {
|
|
24
|
+
console.error('--- Module ---');
|
|
25
|
+
console.error(`Name: ${module.name} (${module.format})`);
|
|
26
|
+
console.error(`Responsibility: ${module.responsibility}`);
|
|
27
|
+
console.error(`Envelope: ${shouldUseEnvelope}`);
|
|
28
|
+
console.error('--- Input ---');
|
|
29
|
+
console.error(JSON.stringify(inputData, null, 2));
|
|
14
30
|
console.error('--- Prompt ---');
|
|
15
31
|
console.error(prompt);
|
|
16
|
-
console.error('--- End
|
|
32
|
+
console.error('--- End ---');
|
|
33
|
+
}
|
|
34
|
+
// Build system message based on module config
|
|
35
|
+
const systemParts = [
|
|
36
|
+
`You are executing the "${module.name}" Cognitive Module.`,
|
|
37
|
+
'',
|
|
38
|
+
`RESPONSIBILITY: ${module.responsibility}`,
|
|
39
|
+
];
|
|
40
|
+
if (module.excludes.length > 0) {
|
|
41
|
+
systemParts.push('', 'YOU MUST NOT:');
|
|
42
|
+
module.excludes.forEach(e => systemParts.push(`- ${e}`));
|
|
43
|
+
}
|
|
44
|
+
if (module.constraints) {
|
|
45
|
+
systemParts.push('', 'CONSTRAINTS:');
|
|
46
|
+
if (module.constraints.no_network)
|
|
47
|
+
systemParts.push('- No network access');
|
|
48
|
+
if (module.constraints.no_side_effects)
|
|
49
|
+
systemParts.push('- No side effects');
|
|
50
|
+
if (module.constraints.no_file_write)
|
|
51
|
+
systemParts.push('- No file writes');
|
|
52
|
+
if (module.constraints.no_inventing_data)
|
|
53
|
+
systemParts.push('- Do not invent data');
|
|
54
|
+
}
|
|
55
|
+
if (module.output?.require_behavior_equivalence) {
|
|
56
|
+
systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
|
|
57
|
+
systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
|
|
58
|
+
systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
|
|
59
|
+
const maxConfidence = module.constraints?.behavior_equivalence_false_max_confidence ?? 0.7;
|
|
60
|
+
systemParts.push(`- If behavior_equivalence=false, confidence MUST be <= ${maxConfidence}`);
|
|
61
|
+
}
|
|
62
|
+
// Add envelope format instructions
|
|
63
|
+
if (shouldUseEnvelope) {
|
|
64
|
+
systemParts.push('', 'RESPONSE FORMAT (Envelope):');
|
|
65
|
+
systemParts.push('- Wrap your response in the envelope format');
|
|
66
|
+
systemParts.push('- Success: { "ok": true, "data": { ...your output... } }');
|
|
67
|
+
systemParts.push('- Error: { "ok": false, "error": { "code": "ERROR_CODE", "message": "..." } }');
|
|
68
|
+
systemParts.push('- Include "confidence" (0-1) and "rationale" in data');
|
|
69
|
+
if (module.output?.require_behavior_equivalence) {
|
|
70
|
+
systemParts.push('- Include "behavior_equivalence" (boolean) in data');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
systemParts.push('', 'OUTPUT FORMAT:');
|
|
75
|
+
systemParts.push('- Respond with ONLY valid JSON');
|
|
76
|
+
systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
|
|
77
|
+
if (module.output?.require_behavior_equivalence) {
|
|
78
|
+
systemParts.push('- Include "behavior_equivalence" (boolean) field');
|
|
79
|
+
}
|
|
17
80
|
}
|
|
18
|
-
// Build messages
|
|
19
81
|
const messages = [
|
|
20
|
-
{
|
|
21
|
-
|
|
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
|
-
},
|
|
82
|
+
{ role: 'system', content: systemParts.join('\n') },
|
|
83
|
+
{ role: 'user', content: prompt },
|
|
41
84
|
];
|
|
42
85
|
// Invoke provider
|
|
43
86
|
const result = await provider.invoke({
|
|
@@ -51,43 +94,136 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
|
|
|
51
94
|
console.error('--- End Response ---');
|
|
52
95
|
}
|
|
53
96
|
// Parse response
|
|
54
|
-
let
|
|
97
|
+
let parsed;
|
|
55
98
|
try {
|
|
56
|
-
// Try to extract JSON from markdown code blocks
|
|
57
99
|
const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
58
100
|
const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
|
|
59
|
-
|
|
101
|
+
parsed = JSON.parse(jsonStr.trim());
|
|
60
102
|
}
|
|
61
103
|
catch {
|
|
62
104
|
throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
|
|
63
105
|
}
|
|
64
|
-
//
|
|
106
|
+
// Handle envelope format
|
|
107
|
+
if (shouldUseEnvelope && isEnvelopeResponse(parsed)) {
|
|
108
|
+
return parseEnvelopeResponse(parsed, result.content);
|
|
109
|
+
}
|
|
110
|
+
// Handle legacy format (non-envelope)
|
|
111
|
+
return parseLegacyResponse(parsed, result.content);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if response is in envelope format
|
|
115
|
+
*/
|
|
116
|
+
function isEnvelopeResponse(obj) {
|
|
117
|
+
if (typeof obj !== 'object' || obj === null)
|
|
118
|
+
return false;
|
|
119
|
+
const o = obj;
|
|
120
|
+
return typeof o.ok === 'boolean';
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Parse envelope format response
|
|
124
|
+
*/
|
|
125
|
+
function parseEnvelopeResponse(response, raw) {
|
|
126
|
+
if (response.ok) {
|
|
127
|
+
const data = response.data;
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
data: {
|
|
131
|
+
...data,
|
|
132
|
+
confidence: typeof data.confidence === 'number' ? data.confidence : 0.5,
|
|
133
|
+
rationale: typeof data.rationale === 'string' ? data.rationale : '',
|
|
134
|
+
behavior_equivalence: data.behavior_equivalence,
|
|
135
|
+
},
|
|
136
|
+
raw,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
error: response.error,
|
|
143
|
+
partial_data: response.partial_data,
|
|
144
|
+
raw,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse legacy (non-envelope) format response
|
|
150
|
+
*/
|
|
151
|
+
function parseLegacyResponse(output, raw) {
|
|
65
152
|
const outputObj = output;
|
|
66
153
|
const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
|
|
67
154
|
const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
|
|
155
|
+
const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
|
|
156
|
+
? outputObj.behavior_equivalence
|
|
157
|
+
: undefined;
|
|
158
|
+
// Check if this is an error response (has error.code)
|
|
159
|
+
if (outputObj.error && typeof outputObj.error === 'object') {
|
|
160
|
+
const errorObj = outputObj.error;
|
|
161
|
+
if (typeof errorObj.code === 'string') {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: {
|
|
165
|
+
code: errorObj.code,
|
|
166
|
+
message: typeof errorObj.message === 'string' ? errorObj.message : 'Unknown error',
|
|
167
|
+
},
|
|
168
|
+
raw,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
68
172
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
173
|
+
ok: true,
|
|
174
|
+
data: {
|
|
175
|
+
...outputObj,
|
|
176
|
+
confidence,
|
|
177
|
+
rationale,
|
|
178
|
+
behavior_equivalence: behaviorEquivalence,
|
|
179
|
+
},
|
|
180
|
+
raw,
|
|
73
181
|
};
|
|
74
182
|
}
|
|
75
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Build prompt with clean variable substitution
|
|
185
|
+
*/
|
|
186
|
+
function buildPrompt(module, input) {
|
|
76
187
|
let prompt = module.prompt;
|
|
77
|
-
//
|
|
78
|
-
const
|
|
188
|
+
// v2 style: substitute ${variable} placeholders
|
|
189
|
+
for (const [key, value] of Object.entries(input)) {
|
|
190
|
+
const strValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
191
|
+
prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
|
|
192
|
+
}
|
|
193
|
+
// v1 compatibility: substitute $ARGUMENTS
|
|
194
|
+
const argsValue = input.code || input.query || '';
|
|
79
195
|
prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
|
|
80
|
-
// Substitute $N placeholders
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
196
|
+
// Substitute $N placeholders (v1 compatibility)
|
|
197
|
+
if (typeof argsValue === 'string') {
|
|
198
|
+
const argsList = argsValue.split(/\s+/);
|
|
199
|
+
argsList.forEach((arg, i) => {
|
|
200
|
+
prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// Append input summary if not already in prompt
|
|
204
|
+
if (!prompt.includes(argsValue) && argsValue) {
|
|
205
|
+
prompt += '\n\n## Input\n\n';
|
|
206
|
+
if (input.code) {
|
|
207
|
+
prompt += '```\n' + input.code + '\n```\n';
|
|
208
|
+
}
|
|
209
|
+
if (input.query) {
|
|
210
|
+
prompt += input.query + '\n';
|
|
211
|
+
}
|
|
212
|
+
if (input.language) {
|
|
213
|
+
prompt += `\nLanguage: ${input.language}\n`;
|
|
90
214
|
}
|
|
91
215
|
}
|
|
92
216
|
return prompt;
|
|
93
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Heuristic to detect if input looks like code
|
|
220
|
+
*/
|
|
221
|
+
function looksLikeCode(str) {
|
|
222
|
+
const codeIndicators = [
|
|
223
|
+
/^(def|function|class|const|let|var|import|export|public|private)\s/,
|
|
224
|
+
/[{};()]/,
|
|
225
|
+
/=>/,
|
|
226
|
+
/\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
|
|
227
|
+
];
|
|
228
|
+
return codeIndicators.some(re => re.test(str));
|
|
229
|
+
}
|