Initial commit with Dockerfile and demo code

This commit is contained in:
2026-08-28 18:16:30 +05:30
commit cf2575fd4e
53 changed files with 11602 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
/**
* Runtime copilot settings.
*
* Precedence: saved settings file > environment > built-in defaults.
*
* Settings are persisted to a small JSON file rather than held only in memory, so
* a model chosen while preparing a demo survives a server restart. They are
* deliberately NOT written back into .env - that file holds the API key, and a
* process that rewrites its own secrets file is a bad habit to build.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = path.dirname(fileURLToPath(import.meta.url));
export const SETTINGS_FILE = path.join(HERE, '..', '..', '.copilot-settings.json');
/** Fields a client is allowed to change, with validation for each. */
const FIELDS = {
provider: {
validate: (v) => ['auto', 'openrouter', 'ollama', 'fallback'].includes(v),
error: 'provider must be auto, openrouter, ollama or fallback',
},
openRouterModel: {
validate: (v) => typeof v === 'string' && v.length > 0 && v.length < 200,
error: 'openRouterModel must be a non-empty model id',
},
ollamaModel: {
validate: (v) => typeof v === 'string' && v.length > 0 && v.length < 200,
error: 'ollamaModel must be a non-empty model id',
},
ollamaHost: {
validate: (v) => typeof v === 'string' && v.length > 0 && v.length < 300,
error: 'ollamaHost must be a URL or host:port',
},
temperature: {
validate: (v) => Number.isFinite(v) && v >= 0 && v <= 2,
error: 'temperature must be between 0 and 2',
},
maxTokens: {
validate: (v) => Number.isInteger(v) && v >= 128 && v <= 8000,
error: 'maxTokens must be an integer between 128 and 8000',
},
reasoningEffort: {
validate: (v) => ['none', 'low', 'medium', 'high'].includes(v),
error: 'reasoningEffort must be none, low, medium or high',
},
};
export function defaultsFromEnv(env = process.env) {
return {
// 'auto' resolves by availability at detect() time.
provider: (env.COPILOT_PROVIDER || 'auto').trim().toLowerCase(),
openRouterModel: (env.OPENROUTER_MODEL || 'meta-llama/llama-3.3-70b-instruct').trim(),
ollamaModel: (env.OLLAMA_MODEL || 'qwen3.5-4b-32k:latest').trim(),
ollamaHost: (env.OLLAMA_HOST || 'http://127.0.0.1:11434').trim(),
temperature: 0.3,
maxTokens: 2000,
// Reasoning tokens count against the output budget on most providers, so
// 'low' keeps answers from being swallowed by a reasoning block and keeps
// time-to-first-token usable in a live demo.
reasoningEffort: 'low',
};
}
export function loadSettings(env = process.env) {
const base = defaultsFromEnv(env);
try {
const raw = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
for (const key of Object.keys(FIELDS)) {
if (raw[key] !== undefined && FIELDS[key].validate(raw[key])) base[key] = raw[key];
}
} catch {
/* no saved settings yet, or unreadable - environment defaults stand */
}
return base;
}
/**
* Validate a patch. Returns { ok, errors, clean }.
*
* Unknown keys are rejected rather than ignored, so a typo in a field name fails
* loudly instead of silently doing nothing.
*/
export function validatePatch(patch) {
const errors = [];
const clean = {};
for (const [key, value] of Object.entries(patch || {})) {
const field = FIELDS[key];
if (!field) { errors.push(`unknown setting "${key}"`); continue; }
if (!field.validate(value)) { errors.push(field.error); continue; }
clean[key] = value;
}
return { ok: errors.length === 0, errors, clean };
}
export function saveSettings(settings) {
const out = {};
for (const key of Object.keys(FIELDS)) {
if (settings[key] !== undefined) out[key] = settings[key];
}
fs.writeFileSync(SETTINGS_FILE, `${JSON.stringify(out, null, 2)}\n`, 'utf8');
return out;
}
export function resetSettings() {
try {
fs.unlinkSync(SETTINGS_FILE);
} catch {
/* nothing saved - already at defaults */
}
}