@pixelbyte-software/pixcode 1.40.5 → 1.40.8
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/assets/{index-DROaSsD_.js → index-C8XyPjlN.js} +149 -149
- package/dist/index.html +1 -1
- package/dist/landing.html +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +87 -4
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/services/live-view.js +124 -2
- package/dist-server/server/services/live-view.js.map +1 -1
- package/dist-server/server/services/managed-runtimes.js +89 -33
- package/dist-server/server/services/managed-runtimes.js.map +1 -1
- package/dist-server/server/services/provider-models.js +16 -3
- package/dist-server/server/services/provider-models.js.map +1 -1
- package/dist-server/shared/modelConstants.js +2 -3
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/live-view-integration.mjs +167 -1
- package/scripts/smoke/orchestration-model-sync.mjs +30 -0
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +108 -4
- package/server/services/live-view.js +128 -2
- package/server/services/managed-runtimes.js +88 -34
- package/server/services/provider-models.js +18 -3
- package/shared/modelConstants.js +2 -3
|
@@ -83,12 +83,21 @@ function selectFrankenPhpAsset(release) {
|
|
|
83
83
|
return candidates[0]?.asset || null;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
function isGitHubUrl(url) {
|
|
87
|
+
try {
|
|
88
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
89
|
+
return hostname === 'github.com' || hostname === 'api.github.com' || hostname.endsWith('.github.com');
|
|
90
|
+
} catch {
|
|
91
|
+
return String(url).includes('github.com');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function fetchJson(url, env = process.env, options = {}) {
|
|
87
96
|
const headers = {
|
|
88
|
-
Accept: 'application/
|
|
97
|
+
Accept: options.accept || 'application/json',
|
|
89
98
|
'User-Agent': 'Pixcode Live View',
|
|
90
99
|
};
|
|
91
|
-
if (env.GITHUB_TOKEN) headers.Authorization = `Bearer ${env.GITHUB_TOKEN}`;
|
|
100
|
+
if (env.GITHUB_TOKEN && isGitHubUrl(url)) headers.Authorization = `Bearer ${env.GITHUB_TOKEN}`;
|
|
92
101
|
|
|
93
102
|
const response = await fetch(url, { headers });
|
|
94
103
|
if (!response.ok) {
|
|
@@ -99,7 +108,7 @@ async function fetchJson(url, env = process.env) {
|
|
|
99
108
|
|
|
100
109
|
async function downloadFile(url, targetFile, env = process.env) {
|
|
101
110
|
const headers = { 'User-Agent': 'Pixcode Live View' };
|
|
102
|
-
if (env.GITHUB_TOKEN && url
|
|
111
|
+
if (env.GITHUB_TOKEN && isGitHubUrl(url)) headers.Authorization = `Bearer ${env.GITHUB_TOKEN}`;
|
|
103
112
|
|
|
104
113
|
const response = await fetch(url, { headers });
|
|
105
114
|
if (!response.ok) {
|
|
@@ -112,50 +121,65 @@ async function downloadFile(url, targetFile, env = process.env) {
|
|
|
112
121
|
function runProcess(command, args, options = {}) {
|
|
113
122
|
return new Promise((resolve, reject) => {
|
|
114
123
|
let stderr = '';
|
|
124
|
+
let settled = false;
|
|
125
|
+
const { timeoutMs, ...spawnOptions } = options;
|
|
115
126
|
const child = spawn(command, args, {
|
|
116
|
-
...
|
|
127
|
+
...spawnOptions,
|
|
117
128
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
118
129
|
windowsHide: true,
|
|
119
130
|
});
|
|
131
|
+
const finish = (callback, value) => {
|
|
132
|
+
if (settled) return;
|
|
133
|
+
settled = true;
|
|
134
|
+
if (timer) clearTimeout(timer);
|
|
135
|
+
callback(value);
|
|
136
|
+
};
|
|
137
|
+
const timer = timeoutMs
|
|
138
|
+
? setTimeout(() => {
|
|
139
|
+
try {
|
|
140
|
+
child.kill();
|
|
141
|
+
} catch {
|
|
142
|
+
// Process may have exited between timeout scheduling and kill.
|
|
143
|
+
}
|
|
144
|
+
finish(reject, new Error(`${command} timed out after ${timeoutMs}ms`));
|
|
145
|
+
}, timeoutMs)
|
|
146
|
+
: null;
|
|
120
147
|
child.stderr.on('data', (chunk) => {
|
|
121
148
|
stderr += chunk.toString();
|
|
122
149
|
});
|
|
123
|
-
child.on('error', reject);
|
|
150
|
+
child.on('error', (error) => finish(reject, error));
|
|
124
151
|
child.on('close', (code) => {
|
|
125
152
|
if (code === 0) {
|
|
126
|
-
resolve
|
|
153
|
+
finish(resolve);
|
|
127
154
|
return;
|
|
128
155
|
}
|
|
129
|
-
reject
|
|
156
|
+
finish(reject, new Error(`${command} exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`));
|
|
130
157
|
});
|
|
131
158
|
});
|
|
132
159
|
}
|
|
133
160
|
|
|
161
|
+
function buildPowerShellExpandArchiveArgs(archivePath, targetDir) {
|
|
162
|
+
return [
|
|
163
|
+
'-NoProfile',
|
|
164
|
+
'-ExecutionPolicy',
|
|
165
|
+
'Bypass',
|
|
166
|
+
'-Command',
|
|
167
|
+
'& { param([string]$archive, [string]$destination) Expand-Archive -Force -LiteralPath $archive -DestinationPath $destination }',
|
|
168
|
+
archivePath,
|
|
169
|
+
targetDir,
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
|
|
134
173
|
async function extractZip(archivePath, targetDir, env = process.env) {
|
|
135
174
|
if (process.platform === 'win32') {
|
|
136
175
|
const shell = env.ComSpec || process.env.ComSpec || 'powershell.exe';
|
|
137
176
|
const isCmd = shell.toLowerCase().endsWith('cmd.exe');
|
|
177
|
+
const expandArgs = buildPowerShellExpandArchiveArgs(archivePath, targetDir);
|
|
138
178
|
if (isCmd) {
|
|
139
|
-
await runProcess('powershell.exe',
|
|
140
|
-
'-NoProfile',
|
|
141
|
-
'-ExecutionPolicy',
|
|
142
|
-
'Bypass',
|
|
143
|
-
'-Command',
|
|
144
|
-
'Expand-Archive -Force -LiteralPath $args[0] -DestinationPath $args[1]',
|
|
145
|
-
archivePath,
|
|
146
|
-
targetDir,
|
|
147
|
-
], { env });
|
|
179
|
+
await runProcess('powershell.exe', expandArgs, { env });
|
|
148
180
|
return;
|
|
149
181
|
}
|
|
150
|
-
await runProcess(shell,
|
|
151
|
-
'-NoProfile',
|
|
152
|
-
'-ExecutionPolicy',
|
|
153
|
-
'Bypass',
|
|
154
|
-
'-Command',
|
|
155
|
-
'Expand-Archive -Force -LiteralPath $args[0] -DestinationPath $args[1]',
|
|
156
|
-
archivePath,
|
|
157
|
-
targetDir,
|
|
158
|
-
], { env });
|
|
182
|
+
await runProcess(shell, expandArgs, { env });
|
|
159
183
|
return;
|
|
160
184
|
}
|
|
161
185
|
|
|
@@ -194,10 +218,33 @@ async function findRuntimeExecutable(searchRoot, binaryName) {
|
|
|
194
218
|
return null;
|
|
195
219
|
}
|
|
196
220
|
|
|
221
|
+
async function copyRuntimeWithSidecars(executablePath, currentDir) {
|
|
222
|
+
const executableDir = path.dirname(executablePath);
|
|
223
|
+
await fs.rm(currentDir, { recursive: true, force: true });
|
|
224
|
+
await fs.mkdir(currentDir, { recursive: true });
|
|
225
|
+
await fs.cp(executableDir, currentDir, { recursive: true, force: true });
|
|
226
|
+
return path.join(currentDir, path.basename(executablePath));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function validateManagedRuntimeExecutable(id, executablePath, env = process.env) {
|
|
230
|
+
if (id !== 'frankenphp') return true;
|
|
231
|
+
try {
|
|
232
|
+
await runProcess(executablePath, ['version'], {
|
|
233
|
+
env,
|
|
234
|
+
timeoutMs: 5000,
|
|
235
|
+
});
|
|
236
|
+
return true;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
197
242
|
async function installFrankenPhp(env = process.env) {
|
|
198
243
|
const releaseApiUrl = env.PIXCODE_FRANKENPHP_RELEASE_API
|
|
199
244
|
|| 'https://api.github.com/repos/php/frankenphp/releases/latest';
|
|
200
|
-
const release = env.PIXCODE_FRANKENPHP_URL
|
|
245
|
+
const release = env.PIXCODE_FRANKENPHP_URL
|
|
246
|
+
? null
|
|
247
|
+
: await fetchJson(releaseApiUrl, env, { accept: 'application/vnd.github+json' });
|
|
201
248
|
const asset = env.PIXCODE_FRANKENPHP_URL
|
|
202
249
|
? {
|
|
203
250
|
name: path.basename(new URL(env.PIXCODE_FRANKENPHP_URL).pathname),
|
|
@@ -237,12 +284,7 @@ async function installFrankenPhp(env = process.env) {
|
|
|
237
284
|
await fs.chmod(executablePath, 0o755).catch(() => undefined);
|
|
238
285
|
}
|
|
239
286
|
|
|
240
|
-
|
|
241
|
-
await fs.mkdir(currentDir, { recursive: true });
|
|
242
|
-
|
|
243
|
-
const finalName = process.platform === 'win32' ? 'frankenphp.exe' : 'frankenphp';
|
|
244
|
-
const finalExecutable = path.join(currentDir, finalName);
|
|
245
|
-
await fs.copyFile(executablePath, finalExecutable);
|
|
287
|
+
const finalExecutable = await copyRuntimeWithSidecars(executablePath, currentDir);
|
|
246
288
|
if (process.platform !== 'win32') {
|
|
247
289
|
await fs.chmod(finalExecutable, 0o755).catch(() => undefined);
|
|
248
290
|
}
|
|
@@ -264,7 +306,7 @@ async function installFrankenPhp(env = process.env) {
|
|
|
264
306
|
async function installNpmRuntime(env = process.env) {
|
|
265
307
|
const registryUrl = env.PIXCODE_NPM_RUNTIME_REGISTRY
|
|
266
308
|
|| 'https://registry.npmjs.org/npm/latest';
|
|
267
|
-
const metadata = await fetchJson(registryUrl, env);
|
|
309
|
+
const metadata = await fetchJson(registryUrl, env, { accept: 'application/json' });
|
|
268
310
|
const tarballUrl = metadata?.dist?.tarball;
|
|
269
311
|
if (!tarballUrl) {
|
|
270
312
|
throw new Error('No npm runtime tarball is available from the npm registry.');
|
|
@@ -371,6 +413,18 @@ export async function getManagedRuntimeStatus(id, options = {}) {
|
|
|
371
413
|
|
|
372
414
|
const manifest = await readManifest(id, env);
|
|
373
415
|
if (manifest) {
|
|
416
|
+
const valid = await validateManagedRuntimeExecutable(id, manifest.executablePath, env);
|
|
417
|
+
if (!valid) {
|
|
418
|
+
return {
|
|
419
|
+
id,
|
|
420
|
+
label: 'Pixcode PHP runtime',
|
|
421
|
+
provider: 'FrankenPHP',
|
|
422
|
+
status: 'missing',
|
|
423
|
+
installable: true,
|
|
424
|
+
reason: 'The existing Pixcode PHP runtime is incomplete or cannot start. Pixcode will reinstall it automatically.',
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
374
428
|
return {
|
|
375
429
|
id,
|
|
376
430
|
label: manifest.label || 'Pixcode PHP runtime',
|
|
@@ -86,6 +86,20 @@ function mergeCatalogs(primary, secondary) {
|
|
|
86
86
|
return Array.from(seen.values());
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function mergeProviderCatalogs(provider, primary, staticCatalog) {
|
|
90
|
+
const normalizedPrimary = normalizeList(primary);
|
|
91
|
+
|
|
92
|
+
// OpenCode Zen free models rotate often. When models.dev succeeds, treat
|
|
93
|
+
// that live catalog as authoritative; otherwise stale static freebies can
|
|
94
|
+
// leak back into the UI and fail later with ProviderModelNotFoundError.
|
|
95
|
+
if (provider === 'opencode') {
|
|
96
|
+
const liveModels = normalizedPrimary.filter((item) => item.source === 'api');
|
|
97
|
+
if (liveModels.length > 0) return liveModels;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return mergeCatalogs(normalizedPrimary, staticCatalog);
|
|
101
|
+
}
|
|
102
|
+
|
|
89
103
|
// ---------------- Per-provider live discovery ----------------
|
|
90
104
|
|
|
91
105
|
async function discoverAnthropic(apiKey, baseUrl) {
|
|
@@ -287,8 +301,9 @@ export async function getProviderModels(provider, opts = {}) {
|
|
|
287
301
|
: false;
|
|
288
302
|
|
|
289
303
|
if (!forceRefresh && cacheFresh && Array.isArray(cached?.models)) {
|
|
304
|
+
const merged = mergeProviderCatalogs(provider, cached.models, staticCatalog);
|
|
290
305
|
return {
|
|
291
|
-
models:
|
|
306
|
+
models: merged,
|
|
292
307
|
fetchedAt: cached.fetchedAt,
|
|
293
308
|
error: cached.error,
|
|
294
309
|
fromCache: true,
|
|
@@ -305,7 +320,7 @@ export async function getProviderModels(provider, opts = {}) {
|
|
|
305
320
|
} catch (err) {
|
|
306
321
|
error = err?.message || String(err);
|
|
307
322
|
}
|
|
308
|
-
const merged =
|
|
323
|
+
const merged = mergeProviderCatalogs(provider, liveModels, staticCatalog);
|
|
309
324
|
const entry = { models: merged, error };
|
|
310
325
|
await saveCacheEntry(provider, entry).catch(() => { /* non-fatal */ });
|
|
311
326
|
return { models: merged, fetchedAt: new Date().toISOString(), error, fromCache: false };
|
|
@@ -367,7 +382,7 @@ export async function getProviderModels(provider, opts = {}) {
|
|
|
367
382
|
}
|
|
368
383
|
}
|
|
369
384
|
|
|
370
|
-
const merged =
|
|
385
|
+
const merged = mergeProviderCatalogs(provider, liveModels, staticCatalog);
|
|
371
386
|
const entry = { models: merged, error };
|
|
372
387
|
await saveCacheEntry(provider, entry).catch(() => { /* non-fatal */ });
|
|
373
388
|
return { models: merged, fetchedAt: new Date().toISOString(), error, fromCache: false };
|
package/shared/modelConstants.js
CHANGED
|
@@ -110,11 +110,10 @@ export const QWEN_MODELS = {
|
|
|
110
110
|
export const OPENCODE_MODELS = {
|
|
111
111
|
OPTIONS: [
|
|
112
112
|
// OpenCode Zen — free tier (no charge, may rate-limit). The "limited
|
|
113
|
-
// time" Zen freebies rotate, so this
|
|
113
|
+
// time" Zen freebies rotate, so keep this fallback conservative and let
|
|
114
|
+
// the live models.dev catalog populate the full current list.
|
|
114
115
|
{ value: "opencode/big-pickle", label: "OpenCode Zen · Big Pickle (Free)", free: true },
|
|
115
116
|
{ value: "opencode/minimax-m2.5-free", label: "OpenCode Zen · MiniMax M2.5 (Free)", free: true },
|
|
116
|
-
{ value: "opencode/hy3-preview-free", label: "OpenCode Zen · Hy3 Preview (Free)", free: true },
|
|
117
|
-
{ value: "opencode/ling-2.6-flash-free", label: "OpenCode Zen · Ling 2.6 Flash (Free)", free: true },
|
|
118
117
|
{ value: "opencode/nemotron-3-super-free", label: "OpenCode Zen · Nemotron 3 Super (Free)", free: true },
|
|
119
118
|
{ value: "opencode/gpt-5-nano", label: "OpenCode Zen · GPT-5 Nano (Free)", free: true },
|
|
120
119
|
|