This commit is contained in:
seb
2026-10-05 19:04:30 +02:00
parent f55990c25e
commit af0ef29e22
4 changed files with 53 additions and 12 deletions

View File

@@ -6,7 +6,8 @@ Upsert company setting
Sets (upserts) a company setting for the selected company.
Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,
`keys.openrouter`, `keys.serper`, `labelprinter.addr`, `labelprinter.type`.
`keys.openrouter`, `keys.serper`, `systemprompt-prefix`,
`systemprompt-suffix`, `default-model`.
## Path parameters

View File

@@ -5,8 +5,9 @@ const ALLOWED_KEYS = [
'keys.tavily',
'keys.openrouter',
'keys.serper',
'labelprinter.addr',
'labelprinter.type',
'systemprompt-prefix',
'systemprompt-suffix',
'default-model',
];
function requireSelectedCompany(req) {
@@ -206,7 +207,8 @@ export function register(app, basePath = '') {
* description: |
* Sets (upserts) a company setting for the selected company.
* Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,
* `keys.openrouter`, `keys.serper`, `labelprinter.addr`, `labelprinter.type`.
* `keys.openrouter`, `keys.serper`, `systemprompt-prefix`,
* `systemprompt-suffix`, `default-model`.
* security:
* - cookieAuth: []
* x-markdown: companysetting/set.md

View File

@@ -73,9 +73,27 @@ function buildSkillIndex(vfsRoot) {
* (docs/api/routes.md), read on each run so it stays current.
*
* @param {string} vfsRoot Resolved VFS root for this user's company, used to list SKILL files.
* @returns {string}
* @param {number} [googleAuthId]
* @returns {Promise<string>}
*/
async function buildSystemPrompt(vfsRoot) {
async function buildSystemPrompt(vfsRoot, googleAuthId) {
let prefix = '';
let suffix = '';
if (googleAuthId) {
try {
const prefixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-prefix', { maskWriteonly: false });
prefix = prefixSetting?.value?.trim() || '';
} catch (err) {
console.error('conversation: failed to read systemprompt-prefix:', err.message);
}
try {
const suffixSetting = await dbCompanySetting.getByKey(googleAuthId, 'systemprompt-suffix', { maskWriteonly: false });
suffix = suffixSetting?.value?.trim() || '';
} catch (err) {
console.error('conversation: failed to read systemprompt-suffix:', err.message);
}
}
let routesDoc = '';
try {
routesDoc = readFileSync(ROUTES_DOC_PATH, 'utf8').trim();
@@ -83,7 +101,7 @@ async function buildSystemPrompt(vfsRoot) {
console.error('conversation: failed to read routes doc:', err);
}
const today = new Date().toISOString().split('T')[0];
return [
const basePrompt = [
'You are an intelligent assistant specialized in exploring, querying, and managing data from the Dashback REST API.',
`Today's date: ${today}`,
'',
@@ -129,6 +147,12 @@ async function buildSystemPrompt(vfsRoot) {
'- Format responses clearly using markdown tables, bullet lists, or bold key-value pairs where appropriate.',
...(routesDoc ? ['', 'DASHBACK API ROUTES:', routesDoc] : []),
].join('\n');
const sections = [];
if (prefix) sections.push(prefix);
sections.push(basePrompt);
if (suffix) sections.push(suffix);
return sections.join('\n\n');
}
/**
@@ -351,7 +375,7 @@ export class ConversationManager {
const defaultModel = await resolveDefaultModel(googleAuthId);
const hasCustomKey = await hasConfiguredOpenRouterKey(googleAuthId);
const storedModel = typeof conversation.model === 'string' ? conversation.model.trim() : '';
const model = (!storedModel || RETIRED_MODELS.has(storedModel) || (storedModel === env.openrouterFreeModel && hasCustomKey))
const model = (!storedModel || RETIRED_MODELS.has(storedModel) || (storedModel === env.openrouterFreeModel && (hasCustomKey || defaultModel !== env.openrouterFreeModel)))
? defaultModel
: storedModel;
const apiKey = await resolveOpenRouterKey(googleAuthId);
@@ -369,7 +393,7 @@ export class ConversationManager {
}
const messages = [
{ role: 'system', content: await buildSystemPrompt(vfsRoot) },
{ role: 'system', content: await buildSystemPrompt(vfsRoot, googleAuthId) },
...history.map(toLlmMessage),
];

View File

@@ -47,13 +47,27 @@ export async function hasConfiguredOpenRouterKey(googleAuthId) {
/**
* Resolve the default model for a user/company:
* - As soon as the user/company has configured `keys.openrouter`, use `env.openrouterPaidModel`.
* - Otherwise (using the server's free key), use `env.openrouterFreeModel`.
* - If companysetting `default-model` is set, return it (works in both paid and no-key modes).
* - Otherwise, if the user/company has configured `keys.openrouter`, use `env.openrouterPaidModel`
* (which defaults to 'openai/gpt-6-luna:floor').
* - Otherwise (no key set / free mode), use `env.openrouterFreeModel`
* (which defaults to 'openrouter/free').
*
* @param {number} [googleAuthId]
* @returns {Promise<string>}
*/
export async function resolveDefaultModel(googleAuthId) {
if (googleAuthId) {
try {
const setting = await dbCompanySetting.getByKey(googleAuthId, 'default-model', { maskWriteonly: false });
const customModel = setting?.value?.trim();
if (customModel) return customModel;
} catch (err) {
console.error('default-model: failed to read company setting:', err.message);
}
}
const hasKey = await hasConfiguredOpenRouterKey(googleAuthId);
return hasKey ? env.openrouterPaidModel : env.openrouterFreeModel;
return hasKey
? (env.openrouterPaidModel || 'openai/gpt-6-luna:floor')
: (env.openrouterFreeModel || 'openrouter/free');
}