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. Sets (upserts) a company setting for the selected company.
Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`, 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 ## Path parameters

View File

@@ -5,8 +5,9 @@ const ALLOWED_KEYS = [
'keys.tavily', 'keys.tavily',
'keys.openrouter', 'keys.openrouter',
'keys.serper', 'keys.serper',
'labelprinter.addr', 'systemprompt-prefix',
'labelprinter.type', 'systemprompt-suffix',
'default-model',
]; ];
function requireSelectedCompany(req) { function requireSelectedCompany(req) {
@@ -206,7 +207,8 @@ export function register(app, basePath = '') {
* description: | * description: |
* Sets (upserts) a company setting for the selected company. * Sets (upserts) a company setting for the selected company.
* Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`, * 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: * security:
* - cookieAuth: [] * - cookieAuth: []
* x-markdown: companysetting/set.md * 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. * (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. * @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 = ''; let routesDoc = '';
try { try {
routesDoc = readFileSync(ROUTES_DOC_PATH, 'utf8').trim(); routesDoc = readFileSync(ROUTES_DOC_PATH, 'utf8').trim();
@@ -83,7 +101,7 @@ async function buildSystemPrompt(vfsRoot) {
console.error('conversation: failed to read routes doc:', err); console.error('conversation: failed to read routes doc:', err);
} }
const today = new Date().toISOString().split('T')[0]; 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.', 'You are an intelligent assistant specialized in exploring, querying, and managing data from the Dashback REST API.',
`Today's date: ${today}`, `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.', '- Format responses clearly using markdown tables, bullet lists, or bold key-value pairs where appropriate.',
...(routesDoc ? ['', 'DASHBACK API ROUTES:', routesDoc] : []), ...(routesDoc ? ['', 'DASHBACK API ROUTES:', routesDoc] : []),
].join('\n'); ].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 defaultModel = await resolveDefaultModel(googleAuthId);
const hasCustomKey = await hasConfiguredOpenRouterKey(googleAuthId); const hasCustomKey = await hasConfiguredOpenRouterKey(googleAuthId);
const storedModel = typeof conversation.model === 'string' ? conversation.model.trim() : ''; 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 ? defaultModel
: storedModel; : storedModel;
const apiKey = await resolveOpenRouterKey(googleAuthId); const apiKey = await resolveOpenRouterKey(googleAuthId);
@@ -369,7 +393,7 @@ export class ConversationManager {
} }
const messages = [ const messages = [
{ role: 'system', content: await buildSystemPrompt(vfsRoot) }, { role: 'system', content: await buildSystemPrompt(vfsRoot, googleAuthId) },
...history.map(toLlmMessage), ...history.map(toLlmMessage),
]; ];

View File

@@ -47,13 +47,27 @@ export async function hasConfiguredOpenRouterKey(googleAuthId) {
/** /**
* Resolve the default model for a user/company: * Resolve the default model for a user/company:
* - As soon as the user/company has configured `keys.openrouter`, use `env.openrouterPaidModel`. * - If companysetting `default-model` is set, return it (works in both paid and no-key modes).
* - Otherwise (using the server's free key), use `env.openrouterFreeModel`. * - 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] * @param {number} [googleAuthId]
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
export async function resolveDefaultModel(googleAuthId) { 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); const hasKey = await hasConfiguredOpenRouterKey(googleAuthId);
return hasKey ? env.openrouterPaidModel : env.openrouterFreeModel; return hasKey
? (env.openrouterPaidModel || 'openai/gpt-6-luna:floor')
: (env.openrouterFreeModel || 'openrouter/free');
} }