feat: add /config chat config updates

This commit is contained in:
Peter Steinberger
2026-01-10 03:00:24 +01:00
parent 63b0a16357
commit 8b579c91a5
13 changed files with 421 additions and 108 deletions

View File

@@ -0,0 +1,88 @@
type PathNode = Record<string, unknown>;
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
export function parseConfigPath(raw: string): {
ok: boolean;
path?: string[];
error?: string;
} {
const trimmed = raw.trim();
if (!trimmed) {
return { ok: false, error: "Invalid path. Use dot notation (e.g. foo.bar)." };
}
const parts = trimmed.split(".").map((part) => part.trim());
if (parts.some((part) => !part)) {
return { ok: false, error: "Invalid path. Use dot notation (e.g. foo.bar)." };
}
if (parts.some((part) => BLOCKED_KEYS.has(part))) {
return { ok: false, error: "Invalid path segment." };
}
return { ok: true, path: parts };
}
export function setConfigValueAtPath(
root: PathNode,
path: string[],
value: unknown,
): void {
let cursor: PathNode = root;
for (let idx = 0; idx < path.length - 1; idx += 1) {
const key = path[idx];
const next = cursor[key];
if (!isPlainObject(next)) {
cursor[key] = {};
}
cursor = cursor[key] as PathNode;
}
cursor[path[path.length - 1]] = value;
}
export function unsetConfigValueAtPath(
root: PathNode,
path: string[],
): boolean {
const stack: Array<{ node: PathNode; key: string }> = [];
let cursor: PathNode = root;
for (let idx = 0; idx < path.length - 1; idx += 1) {
const key = path[idx];
const next = cursor[key];
if (!isPlainObject(next)) return false;
stack.push({ node: cursor, key });
cursor = next;
}
const leafKey = path[path.length - 1];
if (!(leafKey in cursor)) return false;
delete cursor[leafKey];
for (let idx = stack.length - 1; idx >= 0; idx -= 1) {
const { node, key } = stack[idx];
const child = node[key];
if (isPlainObject(child) && Object.keys(child).length === 0) {
delete node[key];
} else {
break;
}
}
return true;
}
export function getConfigValueAtPath(
root: PathNode,
path: string[],
): unknown {
let cursor: unknown = root;
for (const key of path) {
if (!isPlainObject(cursor)) return undefined;
cursor = cursor[key];
}
return cursor;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === "[object Object]"
);
}