@animus-labs/cortex 0.2.0 → 0.2.3
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/cortex-agent.d.ts +1 -0
- package/dist/cortex-agent.d.ts.map +1 -1
- package/dist/cortex-agent.js +34 -10
- package/dist/cortex-agent.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/provider-manager.d.ts +42 -2
- package/dist/provider-manager.d.ts.map +1 -1
- package/dist/provider-manager.js +195 -33
- package/dist/provider-manager.js.map +1 -1
- package/dist/provider-registry.d.ts +7 -9
- package/dist/provider-registry.d.ts.map +1 -1
- package/dist/provider-registry.js +11 -19
- package/dist/provider-registry.js.map +1 -1
- package/dist/tools/bash/index.d.ts +2 -0
- package/dist/tools/bash/index.d.ts.map +1 -1
- package/dist/tools/bash/index.js +3 -0
- package/dist/tools/bash/index.js.map +1 -1
- package/dist/tools/bash/safety.d.ts +2 -4
- package/dist/tools/bash/safety.d.ts.map +1 -1
- package/dist/tools/bash/safety.js +103 -20
- package/dist/tools/bash/safety.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +4 -0
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +13 -0
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utility-model-inference.d.ts +5 -0
- package/dist/utility-model-inference.d.ts.map +1 -0
- package/dist/utility-model-inference.js +174 -0
- package/dist/utility-model-inference.js.map +1 -0
- package/package.json +1 -1
- package/src/cortex-agent.ts +36 -10
- package/src/index.ts +5 -0
- package/src/provider-manager.ts +299 -39
- package/src/provider-registry.ts +12 -19
- package/src/tools/bash/index.ts +5 -0
- package/src/tools/bash/safety.ts +113 -23
- package/src/tools/edit.ts +6 -0
- package/src/tools/write.ts +14 -0
- package/src/types.ts +6 -0
- package/src/utility-model-inference.ts +203 -0
package/src/tools/edit.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { ToolContentDetails } from '../types.js';
|
|
|
19
19
|
import { computeDiff, type DiffHunk } from './write.js';
|
|
20
20
|
import type { CortexToolRuntime } from './runtime.js';
|
|
21
21
|
import { attachRuntimeAwareTool } from './runtime.js';
|
|
22
|
+
import { isCriticalPathOrDescendant } from './bash/safety.js';
|
|
22
23
|
import {
|
|
23
24
|
findMatch,
|
|
24
25
|
findNearestMatch,
|
|
@@ -196,6 +197,11 @@ export function createEditTool(config: EditToolConfig): {
|
|
|
196
197
|
const newString = params.new_string;
|
|
197
198
|
const replaceAll = params.replace_all ?? false;
|
|
198
199
|
|
|
200
|
+
if (isCriticalPathOrDescendant(filePath)) {
|
|
201
|
+
return noChange(filePath, oldString, newString, replaceAll,
|
|
202
|
+
`Refusing to edit critical system path: ${filePath}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
199
205
|
// Check identical strings (no lock needed)
|
|
200
206
|
if (oldString === newString) {
|
|
201
207
|
return noChange(filePath, oldString, newString, replaceAll,
|
package/src/tools/write.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { ReadRegistry } from './shared/read-registry.js';
|
|
|
18
18
|
import type { ToolContentDetails } from '../types.js';
|
|
19
19
|
import type { CortexToolRuntime } from './runtime.js';
|
|
20
20
|
import { attachRuntimeAwareTool } from './runtime.js';
|
|
21
|
+
import { isCriticalPathOrDescendant } from './bash/safety.js';
|
|
21
22
|
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
// Schema
|
|
@@ -148,6 +149,19 @@ export function createWriteTool(config: WriteToolConfig): {
|
|
|
148
149
|
const filePath = path.resolve(params.file_path);
|
|
149
150
|
const newContent = params.content;
|
|
150
151
|
|
|
152
|
+
if (isCriticalPathOrDescendant(filePath)) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: 'text', text: `Refusing to write to critical system path: ${filePath}` }],
|
|
155
|
+
details: {
|
|
156
|
+
filePath,
|
|
157
|
+
isCreate: false,
|
|
158
|
+
bytesWritten: 0,
|
|
159
|
+
diff: null,
|
|
160
|
+
originalContent: null,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
// Check if file exists (before acquiring lock)
|
|
152
166
|
let fileExists = false;
|
|
153
167
|
try {
|
package/src/types.ts
CHANGED
|
@@ -230,6 +230,12 @@ export interface CortexAgentConfig {
|
|
|
230
230
|
shellPath?: string;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Whether the consumer is currently auto-approving tool calls.
|
|
235
|
+
* Used by built-in tool safety gates that need stricter checks in auto mode.
|
|
236
|
+
*/
|
|
237
|
+
isAutoApprove?: () => boolean;
|
|
238
|
+
|
|
233
239
|
/**
|
|
234
240
|
* Disable specific built-in tools by name.
|
|
235
241
|
* Built-in tools (Read, Write, Edit, Glob, Grep, Bash, WebFetch, TaskOutput)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
type RawModel = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
const MIN_UTILITY_CONTEXT_WINDOW = 32_000;
|
|
4
|
+
|
|
5
|
+
const SPECIAL_PURPOSE_MODEL_PATTERN = /(?:embedding|embed|rerank|moderation|whisper|tts|audio|speech|image-generation|vision|live|deep-research|safety|safeguard|guard|search|transcrib)/i;
|
|
6
|
+
|
|
7
|
+
const UTILITY_TERMS: Array<{ pattern: RegExp; score: number }> = [
|
|
8
|
+
{ pattern: /(?:^|[\s._/-])flash[\s._/-]?lite(?:$|[\s._/-])/, score: 110 },
|
|
9
|
+
{ pattern: /(?:^|[\s._/-])nano(?:$|[\s._/-])/, score: 100 },
|
|
10
|
+
{ pattern: /(?:^|[\s._/-])mini(?:$|[\s._/-])/, score: 95 },
|
|
11
|
+
{ pattern: /(?:^|[\s._/-])haiku(?:$|[\s._/-])/, score: 95 },
|
|
12
|
+
{ pattern: /(?:^|[\s._/-])small(?:$|[\s._/-])/, score: 85 },
|
|
13
|
+
{ pattern: /(?:^|[\s._/-])fast(?:$|[\s._/-])/, score: 80 },
|
|
14
|
+
{ pattern: /(?:^|[\s._/-])spark(?:$|[\s._/-])/, score: 80 },
|
|
15
|
+
{ pattern: /(?:^|[\s._/-])instant(?:$|[\s._/-])/, score: 75 },
|
|
16
|
+
{ pattern: /(?:^|[\s._/-])lite(?:$|[\s._/-])/, score: 70 },
|
|
17
|
+
{ pattern: /(?:^|[\s._/-])flash(?:$|[\s._/-])/, score: 65 },
|
|
18
|
+
{ pattern: /(?:^|[\s._/-])(?:7|8)b(?:$|[\s._/-])/, score: 65 },
|
|
19
|
+
{ pattern: /(?:^|[\s._/-])(?:12|20)b(?:$|[\s._/-])/, score: 45 },
|
|
20
|
+
{ pattern: /(?:^|[\s._/-])32b(?:$|[\s._/-])/, score: 25 },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
interface UtilityCandidate {
|
|
24
|
+
model: RawModel;
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
utilityScore: number;
|
|
28
|
+
recencyScore: number;
|
|
29
|
+
costScore: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function inferUtilityModel(models: readonly RawModel[] | null | undefined): RawModel | null {
|
|
33
|
+
if (!Array.isArray(models)) return null;
|
|
34
|
+
|
|
35
|
+
const capable = models
|
|
36
|
+
.map(toUtilityCandidate)
|
|
37
|
+
.filter((candidate): candidate is UtilityCandidate => candidate !== null);
|
|
38
|
+
|
|
39
|
+
const utilityCandidates = capable.filter(candidate => candidate.utilityScore > 0);
|
|
40
|
+
if (utilityCandidates.length > 0) {
|
|
41
|
+
return [...utilityCandidates].sort(compareUtilityCandidates)[0]!.model;
|
|
42
|
+
}
|
|
43
|
+
if (capable.length === 0) return null;
|
|
44
|
+
|
|
45
|
+
return [...capable].sort(compareFallbackCandidates)[0]!.model;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function inferUtilityModelId(models: readonly RawModel[] | null | undefined): string | null {
|
|
49
|
+
const model = inferUtilityModel(models);
|
|
50
|
+
const id = model?.['id'];
|
|
51
|
+
if (typeof id === 'string') return id;
|
|
52
|
+
const name = model?.['name'];
|
|
53
|
+
return typeof name === 'string' ? name : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toUtilityCandidate(model: RawModel): UtilityCandidate | null {
|
|
57
|
+
const id = getString(model['id']) ?? getString(model['name']);
|
|
58
|
+
if (!id) return null;
|
|
59
|
+
|
|
60
|
+
const name = getString(model['name']) ?? id;
|
|
61
|
+
const searchable = `${id} ${name}`.toLowerCase();
|
|
62
|
+
if (SPECIAL_PURPOSE_MODEL_PATTERN.test(searchable)) return null;
|
|
63
|
+
|
|
64
|
+
if (!supportsText(model)) return null;
|
|
65
|
+
|
|
66
|
+
const contextWindow = getNumber(model['contextWindow']) ?? 0;
|
|
67
|
+
if (contextWindow > 0 && contextWindow < MIN_UTILITY_CONTEXT_WINDOW) return null;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
model,
|
|
71
|
+
id,
|
|
72
|
+
name,
|
|
73
|
+
utilityScore: inferUtilityScore(searchable),
|
|
74
|
+
recencyScore: inferRecencyScore(searchable),
|
|
75
|
+
costScore: inferCostScore(model),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compareUtilityCandidates(a: UtilityCandidate, b: UtilityCandidate): number {
|
|
80
|
+
if (b.recencyScore !== a.recencyScore) return b.recencyScore - a.recencyScore;
|
|
81
|
+
if (a.costScore !== b.costScore) return a.costScore - b.costScore;
|
|
82
|
+
if (b.utilityScore !== a.utilityScore) return b.utilityScore - a.utilityScore;
|
|
83
|
+
return a.id.localeCompare(b.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compareFallbackCandidates(a: UtilityCandidate, b: UtilityCandidate): number {
|
|
87
|
+
if (a.costScore !== b.costScore) return a.costScore - b.costScore;
|
|
88
|
+
if (b.recencyScore !== a.recencyScore) return b.recencyScore - a.recencyScore;
|
|
89
|
+
return a.id.localeCompare(b.id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function supportsText(model: RawModel): boolean {
|
|
93
|
+
const input = model['input'];
|
|
94
|
+
if (!Array.isArray(input)) return true;
|
|
95
|
+
return input.includes('text');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function inferUtilityScore(searchable: string): number {
|
|
99
|
+
let score = 0;
|
|
100
|
+
for (const term of UTILITY_TERMS) {
|
|
101
|
+
if (term.pattern.test(searchable)) {
|
|
102
|
+
score = Math.max(score, term.score);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return score;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function inferRecencyScore(searchable: string): number {
|
|
109
|
+
const dateScore = inferDateScore(searchable);
|
|
110
|
+
const versionScore = inferVersionScore(searchable);
|
|
111
|
+
return Math.max(dateScore, versionScore);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function inferDateScore(searchable: string): number {
|
|
115
|
+
let score = 0;
|
|
116
|
+
|
|
117
|
+
for (const match of searchable.matchAll(/20\d{6}/g)) {
|
|
118
|
+
const value = Number(match[0]);
|
|
119
|
+
if (isValidDateScore(value)) {
|
|
120
|
+
score = Math.max(score, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const match of searchable.matchAll(/(20\d{2})[-_/](0[1-9]|1[0-2])/g)) {
|
|
125
|
+
score = Math.max(score, Number(match[1]) * 10_000 + Number(match[2]) * 100);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const match of searchable.matchAll(/(0[1-9]|1[0-2])[-_/](20\d{2})/g)) {
|
|
129
|
+
score = Math.max(score, Number(match[2]) * 10_000 + Number(match[1]) * 100);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const match of searchable.matchAll(/(?:^|[^\d])(\d{4})(?:$|[^\da-z])/g)) {
|
|
133
|
+
const raw = match[1]!;
|
|
134
|
+
const year = Number(raw.slice(0, 2));
|
|
135
|
+
const month = Number(raw.slice(2, 4));
|
|
136
|
+
if (year >= 20 && year <= 40 && month >= 1 && month <= 12) {
|
|
137
|
+
score = Math.max(score, 20_000_000 + year * 10_000 + month * 100);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return score;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function inferVersionScore(searchable: string): number {
|
|
145
|
+
const scrubbed = searchable
|
|
146
|
+
.replace(/20\d{6}/g, ' ')
|
|
147
|
+
.replace(/(20\d{2})[-_/](0[1-9]|1[0-2])/g, ' ')
|
|
148
|
+
.replace(/(0[1-9]|1[0-2])[-_/](20\d{2})/g, ' ')
|
|
149
|
+
.replace(/(?:^|[^\d])(\d{4})(?:$|[^\da-z])/g, ' ')
|
|
150
|
+
.replace(/\b\d+(?:\.\d+)?b\b/g, ' ');
|
|
151
|
+
|
|
152
|
+
let score = 0;
|
|
153
|
+
for (const match of scrubbed.matchAll(/\d+(?:\.\d+)+/g)) {
|
|
154
|
+
score = Math.max(score, scoreVersionParts(match[0]!.split('.').map(Number)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tokens = scrubbed.split(/[^a-z0-9]+/).filter(Boolean);
|
|
158
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
159
|
+
if (!/^\d+$/.test(tokens[i]!)) continue;
|
|
160
|
+
const parts: number[] = [];
|
|
161
|
+
for (let j = i; j < tokens.length && /^\d+$/.test(tokens[j]!) && parts.length < 4; j++) {
|
|
162
|
+
parts.push(Number(tokens[j]));
|
|
163
|
+
}
|
|
164
|
+
if (parts.length >= 2) {
|
|
165
|
+
score = Math.max(score, scoreVersionParts(parts));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return score;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function scoreVersionParts(parts: number[]): number {
|
|
173
|
+
const weights = [1_000_000, 10_000, 100, 1];
|
|
174
|
+
return parts.slice(0, weights.length).reduce((score, part, index) => (
|
|
175
|
+
Number.isFinite(part) ? score + part * weights[index]! : score
|
|
176
|
+
), 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function inferCostScore(model: RawModel): number {
|
|
180
|
+
const cost = model['cost'] ?? model['pricing'];
|
|
181
|
+
if (!cost || typeof cost !== 'object') return Number.MAX_SAFE_INTEGER;
|
|
182
|
+
|
|
183
|
+
const rawCost = cost as RawModel;
|
|
184
|
+
const input = getNumber(rawCost['input']) ?? 0;
|
|
185
|
+
const output = getNumber(rawCost['output']) ?? 0;
|
|
186
|
+
if (input === 0 && output === 0) return Number.MAX_SAFE_INTEGER - 1;
|
|
187
|
+
return input + output * 3;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isValidDateScore(value: number): boolean {
|
|
191
|
+
const year = Math.floor(value / 10_000);
|
|
192
|
+
const month = Math.floor((value % 10_000) / 100);
|
|
193
|
+
const day = value % 100;
|
|
194
|
+
return year >= 2020 && year <= 2040 && month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getString(value: unknown): string | null {
|
|
198
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getNumber(value: unknown): number | null {
|
|
202
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
203
|
+
}
|