@causyne/agent 1.10.19 → 1.10.21
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/docs/ARCHITECTURE.md +20 -0
- package/docs/THIRD_PARTY_NOTICES.md +44 -0
- package/native-skills/media/creative-studio-pipeline/SKILL.md +52 -0
- package/native-skills/media/photo-studio-automation/SKILL.md +53 -0
- package/native-skills/media/video-timeline-assembly/SKILL.md +52 -0
- package/package.json +1 -1
- package/src/agentLoop.js +4 -0
- package/src/appChannel.js +7 -3
- package/src/cliMode.js +33 -67
- package/src/commands.js +56 -8
- package/src/compiledSkills.js +15 -0
- package/src/computerUse/visionGrounding.js +249 -0
- package/src/computerUse/visionGrounding.ts +331 -0
- package/src/config.js +80 -11
- package/src/creative/apps.js +69 -0
- package/src/creative/apps.ts +58 -0
- package/src/creative/blenderBatch.js +233 -0
- package/src/creative/blenderBatch.ts +217 -0
- package/src/creative/pipeline.js +56 -0
- package/src/creative/pipeline.ts +82 -0
- package/src/creative/studioScripts.js +374 -0
- package/src/creative/studioScripts.ts +378 -0
- package/src/decisionPolicy.js +2 -0
- package/src/decisionPolicy.ts +1 -0
- package/src/dynamicCatalogue.js +270 -0
- package/src/dynamicCatalogue.ts +283 -0
- package/src/entrypoint.js +2 -1
- package/src/modelCatalogue.d.ts +5 -0
- package/src/modelCatalogue.js +49 -18
- package/src/providers/anthropic.js +4 -0
- package/src/sandbox.js +8 -1
- package/src/toolPacks.js +6 -3
- package/src/tools/computerUse.js +139 -0
- package/src/tools/computerUse.ts +147 -0
- package/src/tools/creativeApps.js +106 -0
- package/src/tools/creativeApps.ts +99 -0
- package/src/tools/desktopAct.d.ts +10 -0
- package/src/tools/registry.js +5 -1
- package/src/tools/uiForge.js +132 -0
- package/src/tools/uiForge.ts +145 -0
- package/src/tui/banner.js +3 -1
- package/src/tui/banner.ts +3 -1
- package/src/tui/dialogs.js +10 -0
- package/src/tui/home.js +29 -72
- package/src/tui/setup.js +104 -0
- package/src/tui/setup.ts +98 -0
- package/src/uiEngine/uiForge.js +211 -0
- package/src/uiEngine/uiForge.ts +258 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Sovereign Dynamic Model Architecture & Live Discovery
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS EXISTS:
|
|
4
|
+
// AI providers release and update models continuously. Hardcoded static
|
|
5
|
+
// catalogues freeze models at build time. This module gives Causyne a dual-layer
|
|
6
|
+
// model architecture:
|
|
7
|
+
// 1. Curated baseline: Instant offline availability with zero latency and verified pricing/capability tiers.
|
|
8
|
+
// 2. Dynamic discovery: Live discovery via provider APIs (Ollama, OpenAI, Gemini, Anthropic, DeepSeek, etc.)
|
|
9
|
+
// with resilient local caching in ~/.causyne/models_cache.json.
|
|
10
|
+
//
|
|
11
|
+
// ZERO-MALWARE: Pure TypeScript/ESM. Strictly respects timeouts, no secret leakage, no unprompted background calls.
|
|
12
|
+
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { causyneHome } from './utils/paths.js';
|
|
16
|
+
import { MODEL_CATALOGUE, DEFAULT_MODEL_BY_PROVIDER } from './modelCatalogue.js';
|
|
17
|
+
|
|
18
|
+
export interface DynamicCatalogueCache {
|
|
19
|
+
version: 1;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
providers: Record<string, {
|
|
22
|
+
timestamp: number;
|
|
23
|
+
models: string[];
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
28
|
+
const REQUEST_TIMEOUT_MS = 3500; // Fast timeout to never block user interaction
|
|
29
|
+
|
|
30
|
+
export function cacheFilePath(): string {
|
|
31
|
+
return path.join(causyneHome(), 'models_cache.json');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeProviderKey(provider: string): string {
|
|
35
|
+
const p = (provider || '').toLowerCase().trim();
|
|
36
|
+
if (p === 'dashscope' || p === 'dashscope-byok' || p === 'alibaba') return 'dashscope-byok';
|
|
37
|
+
if (p === 'ollama') return 'ollama';
|
|
38
|
+
if (p === 'openai') return 'openai';
|
|
39
|
+
if (p === 'anthropic' || p === 'claude') return 'anthropic';
|
|
40
|
+
if (p === 'gemini' || p === 'google') return 'gemini';
|
|
41
|
+
if (p === 'deepseek') return 'deepseek';
|
|
42
|
+
if (p === 'xai' || p === 'grok') return 'xai';
|
|
43
|
+
if (p === 'moonshot' || p === 'kimi') return 'moonshot';
|
|
44
|
+
if (p === 'muse' || p === 'meta') return 'muse';
|
|
45
|
+
if (p === 'openai-compatible' || p === 'custom') return 'openai-compatible';
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Get the static curated models for a provider id. */
|
|
50
|
+
export function getCuratedModels(provider: string): string[] {
|
|
51
|
+
const norm = normalizeProviderKey(provider);
|
|
52
|
+
const mapping: Record<string, keyof typeof MODEL_CATALOGUE> = {
|
|
53
|
+
'deepseek': 'DeepSeek',
|
|
54
|
+
'qwen': 'Qwen',
|
|
55
|
+
'dashscope-byok': 'Alibaba (DashScope)',
|
|
56
|
+
'dashscope': 'Alibaba (DashScope)',
|
|
57
|
+
'anthropic': 'Anthropic',
|
|
58
|
+
'openai': 'OpenAI',
|
|
59
|
+
'gemini': 'Gemini',
|
|
60
|
+
'muse': 'Meta (Muse)',
|
|
61
|
+
'ollama': 'Ollama (Local)',
|
|
62
|
+
'moonshot': 'Moonshot (Kimi)',
|
|
63
|
+
'xai': 'xAI (Grok)'
|
|
64
|
+
};
|
|
65
|
+
const key = mapping[norm];
|
|
66
|
+
if (key && MODEL_CATALOGUE[key]) {
|
|
67
|
+
return [...MODEL_CATALOGUE[key]];
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Read cache safely from ~/.causyne/models_cache.json */
|
|
73
|
+
export function readModelCache(): DynamicCatalogueCache | null {
|
|
74
|
+
try {
|
|
75
|
+
const file = cacheFilePath();
|
|
76
|
+
if (!fs.existsSync(file)) return null;
|
|
77
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
78
|
+
const parsed = JSON.parse(content) as DynamicCatalogueCache;
|
|
79
|
+
if (parsed && parsed.version === 1 && typeof parsed.providers === 'object') {
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Graceful fallback on corrupt or inaccessible cache
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Save cache safely to ~/.causyne/models_cache.json */
|
|
89
|
+
export function writeModelCache(cache: DynamicCatalogueCache): void {
|
|
90
|
+
try {
|
|
91
|
+
const dir = causyneHome();
|
|
92
|
+
if (!fs.existsSync(dir)) {
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
|
+
}
|
|
95
|
+
const file = cacheFilePath();
|
|
96
|
+
fs.writeFileSync(file, JSON.stringify(cache, null, 2), { mode: 0o600 });
|
|
97
|
+
} catch {
|
|
98
|
+
// Cache failures must never crash the CLI
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Get cached models for a provider if not expired */
|
|
103
|
+
export function getCachedModels(provider: string): string[] | null {
|
|
104
|
+
const cache = readModelCache();
|
|
105
|
+
if (!cache) return null;
|
|
106
|
+
const norm = normalizeProviderKey(provider);
|
|
107
|
+
const entry = cache.providers[norm];
|
|
108
|
+
if (!entry) return null;
|
|
109
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
110
|
+
return entry.models;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Save models for a provider into the local cache */
|
|
114
|
+
export function saveCachedModels(provider: string, models: string[]): void {
|
|
115
|
+
const norm = normalizeProviderKey(provider);
|
|
116
|
+
const cache: DynamicCatalogueCache = readModelCache() || {
|
|
117
|
+
version: 1,
|
|
118
|
+
updatedAt: new Date().toISOString(),
|
|
119
|
+
providers: {}
|
|
120
|
+
};
|
|
121
|
+
cache.providers[norm] = {
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
models: [...new Set(models)]
|
|
124
|
+
};
|
|
125
|
+
cache.updatedAt = new Date().toISOString();
|
|
126
|
+
writeModelCache(cache);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Live query provider API to discover available models */
|
|
130
|
+
export async function fetchLiveModels(
|
|
131
|
+
provider: string,
|
|
132
|
+
config: { apiKey?: string; baseUrl?: string } = {}
|
|
133
|
+
): Promise<string[]> {
|
|
134
|
+
const norm = normalizeProviderKey(provider);
|
|
135
|
+
const { apiKey, baseUrl } = config;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
if (norm === 'ollama') {
|
|
139
|
+
const host = (baseUrl || 'http://127.0.0.1:11434').replace(/\/+$/, '');
|
|
140
|
+
const res = await fetch(`${host}/api/tags`, {
|
|
141
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) return [];
|
|
144
|
+
const data = await res.json() as { models?: Array<{ name?: string }> };
|
|
145
|
+
if (Array.isArray(data.models)) {
|
|
146
|
+
return data.models
|
|
147
|
+
.map((m) => (m.name || '').replace(/:latest$/, ''))
|
|
148
|
+
.filter(Boolean);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (norm === 'openai') {
|
|
153
|
+
if (!apiKey) return [];
|
|
154
|
+
const host = (baseUrl || 'https://api.openai.com/v1').replace(/\/+$/, '');
|
|
155
|
+
const res = await fetch(`${host}/models`, {
|
|
156
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
157
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) return [];
|
|
160
|
+
const data = await res.json() as { data?: Array<{ id?: string }> };
|
|
161
|
+
if (Array.isArray(data.data)) {
|
|
162
|
+
return data.data
|
|
163
|
+
.map((m) => m.id || '')
|
|
164
|
+
.filter((id) => /^(gpt|o1|o3|chatgpt)/i.test(id))
|
|
165
|
+
.sort();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (norm === 'gemini') {
|
|
170
|
+
if (!apiKey) return [];
|
|
171
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`, {
|
|
172
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) return [];
|
|
175
|
+
const data = await res.json() as { models?: Array<{ name?: string; supportedGenerationMethods?: string[] }> };
|
|
176
|
+
if (Array.isArray(data.models)) {
|
|
177
|
+
return data.models
|
|
178
|
+
.filter((m) => m.supportedGenerationMethods?.includes('generateContent'))
|
|
179
|
+
.map((m) => (m.name || '').replace(/^models\//, ''))
|
|
180
|
+
.filter(Boolean)
|
|
181
|
+
.sort();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (norm === 'anthropic') {
|
|
186
|
+
if (!apiKey) return [];
|
|
187
|
+
const res = await fetch('https://api.anthropic.com/v1/models', {
|
|
188
|
+
headers: {
|
|
189
|
+
'x-api-key': apiKey,
|
|
190
|
+
'anthropic-version': '2023-06-01'
|
|
191
|
+
},
|
|
192
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) return [];
|
|
195
|
+
const data = await res.json() as { data?: Array<{ id?: string }> };
|
|
196
|
+
if (Array.isArray(data.data)) {
|
|
197
|
+
return data.data
|
|
198
|
+
.map((m) => m.id || '')
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
.sort();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (norm === 'deepseek') {
|
|
205
|
+
if (!apiKey) return [];
|
|
206
|
+
const res = await fetch('https://api.deepseek.com/models', {
|
|
207
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
208
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
209
|
+
});
|
|
210
|
+
if (!res.ok) return [];
|
|
211
|
+
const data = await res.json() as { data?: Array<{ id?: string }> };
|
|
212
|
+
if (Array.isArray(data.data)) {
|
|
213
|
+
return data.data.map((m) => m.id || '').filter(Boolean).sort();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (norm === 'openai-compatible') {
|
|
218
|
+
if (!baseUrl) return [];
|
|
219
|
+
const host = baseUrl.replace(/\/+$/, '');
|
|
220
|
+
const headers: Record<string, string> = {};
|
|
221
|
+
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
222
|
+
const res = await fetch(`${host}/models`, {
|
|
223
|
+
headers,
|
|
224
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
225
|
+
});
|
|
226
|
+
if (!res.ok) return [];
|
|
227
|
+
const data = await res.json() as { data?: Array<{ id?: string }> };
|
|
228
|
+
if (Array.isArray(data.data)) {
|
|
229
|
+
return data.data.map((m) => m.id || '').filter(Boolean).sort();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Network errors or timeouts silently fall back
|
|
234
|
+
}
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface ModelResolutionResult {
|
|
239
|
+
models: string[];
|
|
240
|
+
source: 'live' | 'cache' | 'catalogue';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolve available models using dual-layer architecture:
|
|
245
|
+
* 1. Checks live query if forceRefresh is true.
|
|
246
|
+
* 2. Checks local cache if available and fresh.
|
|
247
|
+
* 3. Falls back to static curated catalogue merged with any cached items.
|
|
248
|
+
*/
|
|
249
|
+
export async function resolveAvailableModels(
|
|
250
|
+
provider: string,
|
|
251
|
+
config: { apiKey?: string; baseUrl?: string } = {},
|
|
252
|
+
forceRefresh = false
|
|
253
|
+
): Promise<ModelResolutionResult> {
|
|
254
|
+
const norm = normalizeProviderKey(provider);
|
|
255
|
+
const curated = getCuratedModels(norm);
|
|
256
|
+
|
|
257
|
+
if (forceRefresh) {
|
|
258
|
+
const live = await fetchLiveModels(norm, config);
|
|
259
|
+
if (live.length > 0) {
|
|
260
|
+
const combined = [...new Set([...live, ...curated])];
|
|
261
|
+
saveCachedModels(norm, combined);
|
|
262
|
+
return { models: combined, source: 'live' };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const cached = getCachedModels(norm);
|
|
267
|
+
if (cached && cached.length > 0) {
|
|
268
|
+
const combined = [...new Set([...cached, ...curated])];
|
|
269
|
+
return { models: combined, source: 'cache' };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Attempt lazy background discovery if credentials exist
|
|
273
|
+
if (config.apiKey || norm === 'ollama') {
|
|
274
|
+
const live = await fetchLiveModels(norm, config);
|
|
275
|
+
if (live.length > 0) {
|
|
276
|
+
const combined = [...new Set([...live, ...curated])];
|
|
277
|
+
saveCachedModels(norm, combined);
|
|
278
|
+
return { models: combined, source: 'live' };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { models: curated, source: 'catalogue' };
|
|
283
|
+
}
|
package/src/entrypoint.js
CHANGED
|
@@ -90,7 +90,8 @@ program
|
|
|
90
90
|
|
|
91
91
|
program
|
|
92
92
|
.command('config')
|
|
93
|
-
.
|
|
93
|
+
.alias('setup')
|
|
94
|
+
.description('Guided provider, endpoint, model and autonomy setup')
|
|
94
95
|
.option('-p, --provider <name>', 'Set LLM provider (openai, anthropic, gemini, muse, ollama, dashscope, deepseek)')
|
|
95
96
|
.option('-k, --apikey <key>', 'Set API key')
|
|
96
97
|
.option('-m, --model <name>', 'Set model name')
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const MODEL_CATALOGUE: Record<string, string[]>;
|
|
2
|
+
export const PROVIDER_LABELS: string[];
|
|
3
|
+
export const DEFAULT_MODEL_BY_PROVIDER: Record<string, string>;
|
|
4
|
+
export const UTILITY_MODEL_BY_PROVIDER: Record<string, string>;
|
|
5
|
+
export function allCatalogueModels(): string[];
|
package/src/modelCatalogue.js
CHANGED
|
@@ -34,22 +34,52 @@
|
|
|
34
34
|
* The label keys are a stable configuration format, not display sugar.
|
|
35
35
|
*/
|
|
36
36
|
export const MODEL_CATALOGUE = {
|
|
37
|
-
'DeepSeek': ['deepseek-v4-pro', 'deepseek-v4-flash'],
|
|
38
|
-
'Qwen': [
|
|
37
|
+
'DeepSeek': ['deepseek-chat', 'deepseek-reasoner', 'deepseek-v4-pro', 'deepseek-v4-flash'],
|
|
38
|
+
'Qwen': [
|
|
39
|
+
'qwen2.5-coder-32b-instruct',
|
|
40
|
+
'qwen2.5-72b-instruct',
|
|
41
|
+
'qwen-max',
|
|
42
|
+
'qwen-plus',
|
|
43
|
+
'qwen-turbo',
|
|
44
|
+
'qwen3.7-max',
|
|
45
|
+
'qwen3.7-plus',
|
|
46
|
+
'qwen3.6-flash'
|
|
47
|
+
],
|
|
39
48
|
// Anthropic ids use DASHES and carry no date suffix. A dotted id here is a
|
|
40
49
|
// bug, not a variant — see src/modelIds.js.
|
|
41
50
|
'Anthropic': [
|
|
51
|
+
'claude-3-7-sonnet',
|
|
52
|
+
'claude-3-5-sonnet',
|
|
53
|
+
'claude-3-5-haiku',
|
|
54
|
+
'claude-3-opus',
|
|
42
55
|
'claude-fable-5-1',
|
|
43
56
|
'claude-opus-5',
|
|
44
57
|
'claude-opus-4-8',
|
|
45
58
|
'claude-sonnet-5',
|
|
46
59
|
'claude-haiku-4-5'
|
|
47
60
|
],
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
'OpenAI': [
|
|
62
|
+
'gpt-4o',
|
|
63
|
+
'gpt-4o-mini',
|
|
64
|
+
'o3-mini',
|
|
65
|
+
'o1',
|
|
66
|
+
'gpt-6-astra',
|
|
67
|
+
'gpt-5.6-sol',
|
|
68
|
+
'gpt-5.6-terra',
|
|
69
|
+
'gpt-5.6-luna',
|
|
70
|
+
'gpt-5.4-mini'
|
|
71
|
+
],
|
|
72
|
+
'Gemini': [
|
|
73
|
+
'gemini-2.5-pro',
|
|
74
|
+
'gemini-2.5-flash',
|
|
75
|
+
'gemini-2.0-flash',
|
|
76
|
+
'gemini-1.5-pro',
|
|
77
|
+
'gemini-1.5-flash',
|
|
78
|
+
'gemini-3.6-flash',
|
|
79
|
+
'gemini-3.5-flash',
|
|
80
|
+
'gemini-3.1-pro',
|
|
81
|
+
'gemini-3.1-flash-lite'
|
|
82
|
+
],
|
|
53
83
|
// Standard first. The -contributor variants are cheaper because Meta trains
|
|
54
84
|
// on the user's prompts and completions, so they must be a choice made
|
|
55
85
|
// deliberately, never the one at the top of the list.
|
|
@@ -60,14 +90,15 @@ export const MODEL_CATALOGUE = {
|
|
|
60
90
|
'muse-spark-1.3-contributor',
|
|
61
91
|
'muse-spark-1.2-contributor'
|
|
62
92
|
],
|
|
63
|
-
'Ollama (Local)': ['llama3', 'mistral', 'gemma2', 'custom'],
|
|
93
|
+
'Ollama (Local)': ['llama3.3', 'qwen2.5-coder', 'deepseek-r1', 'llama3', 'mistral', 'gemma2', 'custom'],
|
|
64
94
|
'Alibaba (DashScope)': [
|
|
95
|
+
'qwen-max', 'qwen-plus', 'qwen-turbo', 'qwen2.5-coder-32b-instruct',
|
|
65
96
|
'qwen3.7-max', 'qwen3.7-plus', 'kimi-k2.7-code', 'kimi-k2.6',
|
|
66
97
|
'kimi-k2.5', 'kimi-k2-thinking', 'Moonshot-Kimi-K2-Instruct',
|
|
67
98
|
'deepseek-v4-flash', 'deepseek-v4-pro'
|
|
68
99
|
],
|
|
69
100
|
'Moonshot (Kimi)': ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6'],
|
|
70
|
-
'xAI (Grok)': ['grok-4.5', 'grok-latest']
|
|
101
|
+
'xAI (Grok)': ['grok-3', 'grok-3-mini', 'grok-2-1212', 'grok-4.5', 'grok-latest']
|
|
71
102
|
};
|
|
72
103
|
|
|
73
104
|
/** Provider labels in picker order. */
|
|
@@ -75,17 +106,17 @@ export const PROVIDER_LABELS = Object.keys(MODEL_CATALOGUE);
|
|
|
75
106
|
|
|
76
107
|
|
|
77
108
|
export const DEFAULT_MODEL_BY_PROVIDER = {
|
|
78
|
-
deepseek: 'deepseek-
|
|
79
|
-
qwen: '
|
|
80
|
-
'dashscope-byok': '
|
|
81
|
-
dashscope: '
|
|
109
|
+
deepseek: 'deepseek-chat',
|
|
110
|
+
qwen: 'qwen2.5-coder-32b-instruct',
|
|
111
|
+
'dashscope-byok': 'qwen-plus',
|
|
112
|
+
dashscope: 'qwen-plus',
|
|
82
113
|
muse: 'muse-spark-1.3',
|
|
83
|
-
anthropic: 'claude-
|
|
84
|
-
openai: 'gpt-
|
|
85
|
-
gemini: 'gemini-
|
|
114
|
+
anthropic: 'claude-3-7-sonnet',
|
|
115
|
+
openai: 'gpt-4o',
|
|
116
|
+
gemini: 'gemini-2.5-pro',
|
|
86
117
|
moonshot: 'kimi-k3',
|
|
87
|
-
xai: 'grok-
|
|
88
|
-
ollama: 'llama3',
|
|
118
|
+
xai: 'grok-3',
|
|
119
|
+
ollama: 'llama3.3',
|
|
89
120
|
'openai-compatible': ''
|
|
90
121
|
};
|
|
91
122
|
|
|
@@ -8,6 +8,10 @@ import { LLMProvider } from './base.js';
|
|
|
8
8
|
// full capability, none of the token cost. Older models fall back to the
|
|
9
9
|
// client-side progressive disclosure in src/toolPacks.js.
|
|
10
10
|
const TOOL_SEARCH_MODELS = [
|
|
11
|
+
'claude-3-7-sonnet',
|
|
12
|
+
'claude-3-5-sonnet',
|
|
13
|
+
'claude-3-5-haiku',
|
|
14
|
+
'claude-3-opus',
|
|
11
15
|
'claude-fable-5',
|
|
12
16
|
'claude-mythos-5',
|
|
13
17
|
'claude-opus-5',
|
package/src/sandbox.js
CHANGED
|
@@ -298,6 +298,10 @@ export class Sandbox {
|
|
|
298
298
|
return { allowed: true, risk: 'safe' };
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
if (toolName === 'creative_apps') {
|
|
302
|
+
return { allowed: true, risk: args.action === 'status' ? 'safe' : 'dangerous' };
|
|
303
|
+
}
|
|
304
|
+
|
|
301
305
|
if (toolName === 'game_asset_fetch' && args.action === 'download_asset') {
|
|
302
306
|
if (!args.url || this.isInternalUrl(args.url)) {
|
|
303
307
|
return { allowed: false, reason: 'Cannot download from an internal or invalid URL', risk: 'forbidden' };
|
|
@@ -353,9 +357,12 @@ export class Sandbox {
|
|
|
353
357
|
|
|
354
358
|
// Mode C. Section 4 says gated, not walled — the capability is real, the
|
|
355
359
|
// human just says yes first.
|
|
356
|
-
if (toolName === 'desktop_act') {
|
|
360
|
+
if (toolName === 'desktop_act' || toolName === 'computer_use') {
|
|
357
361
|
return { allowed: true, risk: 'extreme' };
|
|
358
362
|
}
|
|
363
|
+
if (toolName === 'ui_forge') {
|
|
364
|
+
return { allowed: true, risk: 'safe' };
|
|
365
|
+
}
|
|
359
366
|
if (toolName === 'fs_write' || toolName === 'fs_move' || toolName === 'fs_edit' || toolName === 'MultiEdit') {
|
|
360
367
|
return { allowed: true, risk: 'moderate' };
|
|
361
368
|
}
|
package/src/toolPacks.js
CHANGED
|
@@ -76,7 +76,8 @@ export const PACKS = {
|
|
|
76
76
|
'screenshot',
|
|
77
77
|
'vision_inspect_ui',
|
|
78
78
|
'view_image',
|
|
79
|
-
'astryx_oracle'
|
|
79
|
+
'astryx_oracle',
|
|
80
|
+
'ui_forge'
|
|
80
81
|
],
|
|
81
82
|
media: [
|
|
82
83
|
'generate_media_via_api',
|
|
@@ -84,6 +85,7 @@ export const PACKS = {
|
|
|
84
85
|
'check_system_specs',
|
|
85
86
|
'manage_local_dependencies',
|
|
86
87
|
'blender_act',
|
|
88
|
+
'creative_apps',
|
|
87
89
|
'resolve_act',
|
|
88
90
|
'palmier_act'
|
|
89
91
|
],
|
|
@@ -95,6 +97,7 @@ export const PACKS = {
|
|
|
95
97
|
// Channel C. Ships beside its observer on purpose: acting on a screen you
|
|
96
98
|
// have not read is how an agent clicks the wrong button confidently.
|
|
97
99
|
'desktop_act',
|
|
100
|
+
'computer_use',
|
|
98
101
|
'process_list',
|
|
99
102
|
'proc_term',
|
|
100
103
|
'archive',
|
|
@@ -361,11 +364,11 @@ When the user asks you to build, create, test, expand, or develop ANY game, you
|
|
|
361
364
|
|
|
362
365
|
const GAME_INTENT = /\b(game|games|gamedev|permainan|godot|unity|unreal|rpg|platformer|roguelike|arcade|sprite|spritesheet|tilemap|voxel|minecraft|aetheria|voxellia|nes|snes|gameplay|player character|level design|boss fight)\b|\.(tscn|gd)\b/i;
|
|
363
366
|
|
|
364
|
-
const WEB_INTENT = /\b(web|webpage|website|situs|frontend|front-end|landing page|homepage|dashboard|portfolio|html|css|scss|tailwind|bootstrap|react|vue|svelte|angular|next\.?js|nuxt|astro|ui|ux|user interface|web app|webapp|spa|dom|responsive)\b/i;
|
|
367
|
+
const WEB_INTENT = /\b(web|webpage|website|situs|frontend|front-end|landing page|homepage|dashboard|portfolio|html|css|scss|tailwind|bootstrap|react|vue|svelte|angular|next\.?js|nuxt|astro|ui|ux|user interface|web app|webapp|spa|dom|responsive|ui forge|component forge|component generation|ui component)\b/i;
|
|
365
368
|
|
|
366
369
|
const MEDIA_INTENT = /\b(image|images|picture|photo|gambar|render|3d model|blender|animation|animate|video|movie|film|klip|audio|sound|sfx|music|musik|song|lagu|voice|suara|tts|text.to.speech|narration|podcast|thumbnail|poster|illustration|logo)\b/i;
|
|
367
370
|
|
|
368
|
-
const SYSTEM_INTENT = /\b(process|processes|pid|kill|port|zip|unzip|tar|archive|compress|extract|clipboard|copy to clipboard|print|printer|cetak|open in browser|launch app)\b/i;
|
|
371
|
+
const SYSTEM_INTENT = /\b(process|processes|pid|kill|port|zip|unzip|tar|archive|compress|extract|clipboard|copy to clipboard|print|printer|cetak|open in browser|launch app|computer use|vision grounding|desktop vision|gui agent|coordinate click|desktop control)\b/i;
|
|
369
372
|
|
|
370
373
|
const ADVANCED_INTENT = /\b(pdf|forge a tool|custom tool|new tool|plugin|plugins|custom plugin|subagent|sub-agent|parallel agent|swarm|ast|symbols|refactor across)\b/i;
|
|
371
374
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* Causyne Vision Desktop & Coordinate Grounding Tool Wrapper.
|
|
6
|
+
*/
|
|
7
|
+
import { parseVisionGroundingPrediction, visionGroundingToDesktopAct, groundBoxCenter } from '../computerUse/visionGrounding.js';
|
|
8
|
+
import { desktopAct } from './desktopAct.js';
|
|
9
|
+
import { auditLog } from '../auditLog.js';
|
|
10
|
+
export const computerUse = {
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'computer_use',
|
|
13
|
+
description: 'Ground and execute graphical computer-use actions (click coordinates, type text, send key presses, drag, wait) derived from vision-language models and coordinate grounding predictions. Automatically translates normalized bounding boxes and coordinates against current screen dimensions with safety boundary checks. Requires explicit approval.',
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
action: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
enum: ['execute_prediction', 'click', 'type', 'key', 'drag', 'wait'],
|
|
20
|
+
description: 'Type of computer-use action to perform.'
|
|
21
|
+
},
|
|
22
|
+
prediction: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Raw prediction text from a vision-language model or coordinate grounding engine, e.g. "Thought: Click search bar. Action: click(start_box=\'(500, 200)\')".'
|
|
25
|
+
},
|
|
26
|
+
coordinates: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: { type: 'number' },
|
|
29
|
+
description: 'Coordinates as [x, y] or bounding box [x1, y1, x2, y2] normalized (0-1000) or in screen pixels.'
|
|
30
|
+
},
|
|
31
|
+
text: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Text to type into the active or targeted element.'
|
|
34
|
+
},
|
|
35
|
+
key: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Key or hotkey to press, e.g. "return", "tab", "escape".'
|
|
38
|
+
},
|
|
39
|
+
seconds: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: 'Seconds to wait (0.1 to 10).'
|
|
42
|
+
},
|
|
43
|
+
app: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Application to focus before executing actions, e.g. "Safari".'
|
|
46
|
+
},
|
|
47
|
+
screenContext: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
width: { type: 'number' },
|
|
51
|
+
height: { type: 'number' },
|
|
52
|
+
scaleFactor: { type: 'number' }
|
|
53
|
+
},
|
|
54
|
+
description: 'Current display dimensions and scale factor for coordinate grounding.'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async execute(args = {}, context = {}) {
|
|
60
|
+
const screen = args.screenContext || { width: 1920, height: 1080, scaleFactor: 1 };
|
|
61
|
+
const app = typeof args.app === 'string' ? args.app.trim() : '';
|
|
62
|
+
let desktopActions = [];
|
|
63
|
+
// Mode 1: Parse full model prediction from vision grounding engine
|
|
64
|
+
if (args.prediction || args.action === 'execute_prediction') {
|
|
65
|
+
const pred = args.prediction || '';
|
|
66
|
+
if (!pred.trim()) {
|
|
67
|
+
return { success: false, error: 'No prediction string provided for execute_prediction.' };
|
|
68
|
+
}
|
|
69
|
+
const parsed = parseVisionGroundingPrediction(pred, screen);
|
|
70
|
+
if (parsed.actions.length === 0) {
|
|
71
|
+
return { success: false, error: 'Could not parse any executable GUI actions from prediction string.' };
|
|
72
|
+
}
|
|
73
|
+
desktopActions = visionGroundingToDesktopAct(parsed.actions);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Mode 2: Direct action request
|
|
77
|
+
const actionType = args.action || 'click';
|
|
78
|
+
if (actionType === 'click') {
|
|
79
|
+
const coords = args.coordinates || [];
|
|
80
|
+
const point = groundBoxCenter(coords, screen);
|
|
81
|
+
if (!point)
|
|
82
|
+
return { success: false, error: 'Click requires valid coordinates [x, y].' };
|
|
83
|
+
desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
|
|
84
|
+
}
|
|
85
|
+
else if (actionType === 'type') {
|
|
86
|
+
if (args.coordinates && args.coordinates.length >= 2) {
|
|
87
|
+
const point = groundBoxCenter(args.coordinates, screen);
|
|
88
|
+
if (point)
|
|
89
|
+
desktopActions.push({ verb: 'clickxy', x: point.x, y: point.y });
|
|
90
|
+
}
|
|
91
|
+
if (!args.text)
|
|
92
|
+
return { success: false, error: 'Type action requires "text" parameter.' };
|
|
93
|
+
desktopActions.push({ verb: 'type', text: args.text });
|
|
94
|
+
}
|
|
95
|
+
else if (actionType === 'key') {
|
|
96
|
+
if (!args.key)
|
|
97
|
+
return { success: false, error: 'Key action requires "key" parameter.' };
|
|
98
|
+
const keyMap = {
|
|
99
|
+
return: 36, enter: 36, tab: 48, space: 49, backspace: 51, escape: 53, esc: 53
|
|
100
|
+
};
|
|
101
|
+
const keyCode = keyMap[args.key.toLowerCase()];
|
|
102
|
+
if (keyCode !== undefined) {
|
|
103
|
+
desktopActions.push({ verb: 'key', keyCode });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
desktopActions.push({ verb: 'type', text: args.key });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (actionType === 'wait') {
|
|
110
|
+
desktopActions.push({ verb: 'wait', seconds: Math.min(10, Math.max(0.1, args.seconds || 1)) });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
return { success: false, error: `Unsupported computer_use action: ${actionType}` };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (desktopActions.length === 0) {
|
|
117
|
+
return { success: false, error: 'No actions to execute.' };
|
|
118
|
+
}
|
|
119
|
+
if (desktopActions.length > 12) {
|
|
120
|
+
return { success: false, error: `Maximum 12 actions allowed per call; got ${desktopActions.length}.` };
|
|
121
|
+
}
|
|
122
|
+
auditLog.log({
|
|
123
|
+
event: 'computer_use_actuation',
|
|
124
|
+
app,
|
|
125
|
+
actionCount: desktopActions.length,
|
|
126
|
+
verbs: desktopActions.map((a) => a.verb)
|
|
127
|
+
});
|
|
128
|
+
const execResult = await desktopAct.execute({ app, actions: desktopActions }, context);
|
|
129
|
+
const finalRes = {
|
|
130
|
+
success: Boolean(execResult.success),
|
|
131
|
+
actions: desktopActions
|
|
132
|
+
};
|
|
133
|
+
if (execResult.output)
|
|
134
|
+
finalRes.output = execResult.output;
|
|
135
|
+
if (execResult.error)
|
|
136
|
+
finalRes.error = execResult.error;
|
|
137
|
+
return finalRes;
|
|
138
|
+
}
|
|
139
|
+
};
|