Skip to content

Commit 06f9c4a

Browse files
authored
Merge pull request #5735 from FlowFuse/5733-device-assistant-config
Add assistant config settings to instance and device runtime settings
2 parents da3add9 + 533288f commit 06f9c4a

5 files changed

Lines changed: 149 additions & 2 deletions

File tree

etc/flowforge.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ driver:
7777
# url: https://assistant.service
7878
# token: token_for_service
7979
# requestTimeout: 60000
80+
# mcp
81+
# enabled: true # defaults to true if not set
82+
# completions:
83+
# enabled: true # defaults to true if not set
84+
# modelUrl: https://<example>/model.json # OPTIONAL - assistant will default to forge URL+"assistant/assets/model.json"
85+
# vocabularyUrl: https://<example>/vocabulary.json # OPTIONAL - assistant will default to forge URL+"assistant/assets/vocabulary.json"
8086

8187
#################################################
8288
# File Server config #

forge/routes/api/deviceLive.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,15 @@ module.exports = async function (app) {
287287
}
288288
response.assistant = {
289289
enabled: app.config.assistant?.enabled || false,
290-
requestTimeout: app.config.assistant?.requestTimeout || 60000
290+
requestTimeout: app.config.assistant?.requestTimeout || 60000,
291+
mcp: { enabled: true }, // default to enabled
292+
completions: { enabled: true } // default to enabled
293+
}
294+
if (app.config.assistant?.mcp && typeof app.config.assistant.mcp === 'object') {
295+
response.assistant.mcp = { ...app.config.assistant.mcp }
296+
}
297+
if (app.config.assistant?.completions && typeof app.config.assistant.completions === 'object') {
298+
response.assistant.completions = { ...app.config.assistant.completions }
291299
}
292300

293301
const teamNPMEnabled = app.config.features.enabled('npm') && teamType.getFeatureProperty('npm', false)

forge/routes/api/project.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,15 @@ module.exports = async function (app) {
833833
settings.fileStore = app.config.fileStore ? { ...app.config.fileStore } : null
834834
settings.assistant = {
835835
enabled: app.config.assistant?.enabled || false,
836-
requestTimeout: app.config.assistant?.requestTimeout
836+
requestTimeout: app.config.assistant?.requestTimeout || 60000,
837+
mcp: { enabled: true }, // default to enabled
838+
completions: { enabled: true } // default to enabled
839+
}
840+
if (app.config.assistant?.mcp && typeof app.config.assistant.mcp === 'object') {
841+
settings.assistant.mcp = { ...app.config.assistant.mcp }
842+
}
843+
if (app.config.assistant?.completions && typeof app.config.assistant.completions === 'object') {
844+
settings.assistant.completions = { ...app.config.assistant.completions }
837845
}
838846
settings.teamID = request.project.Team.hashid
839847
settings.storageURL = request.project.storageURL

test/unit/forge/routes/api/device_spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ describe('Device API', async function () {
2222
/**
2323
* Create a device
2424
* @param {Object} options - Options for creating a device
25+
* @param {string} options.name - The name of the device
26+
* @param {string} options.type - The type of the device
27+
* @param {string} options.team - The team hashid to create the device in
28+
* @param {string} options.as - The user session to create the device as
29+
* @param {string} [options.agentVersion] - The agent version to set on the device
30+
* @param {string} [options.application] - The application hashid to assign the device to
31+
* @param {string} [options.instance] - The instance hashid to assign the device to
2532
* @returns {Promise<Object>} - The device object
2633
*/
2734
async function createDevice (options) {
@@ -2296,6 +2303,67 @@ describe('Device API', async function () {
22962303
body.editor.should.have.property('nodeRedVersion', 'next')
22972304
})
22982305
})
2306+
describe('Assistant Settings', function () {
2307+
// these tests are run with a clean app since they change the app config
2308+
beforeEach(async function () {
2309+
// Close down the default app
2310+
if (app) {
2311+
await app.close()
2312+
}
2313+
app = null
2314+
})
2315+
after(async function () {
2316+
// Once all done, create the clean app for later tests
2317+
await app.close()
2318+
await setupApp()
2319+
})
2320+
2321+
it('device downloads settings with assistant disabled', async function () {
2322+
app = await setup({
2323+
assistant: {
2324+
enabled: false,
2325+
mcp: { enabled: false },
2326+
completions: { enabled: false }
2327+
}
2328+
})
2329+
await login('alice', 'aaPassword')
2330+
const device = await createDevice({ name: 'AppDevice1', type: 'AppDevice1_type', team: app.team.hashid, as: TestObjects.tokens.alice })
2331+
const dbDevice = await app.db.models.Device.byId(device.id)
2332+
dbDevice.setApplication(app.application)
2333+
await dbDevice.save()
2334+
2335+
const body = await getLiveSettings(device)
2336+
body.should.have.property('assistant').and.be.an.Object()
2337+
body.assistant.should.have.property('enabled', false)
2338+
body.assistant.should.have.property('mcp').and.be.an.Object()
2339+
body.assistant.mcp.should.have.property('enabled', false)
2340+
body.assistant.should.have.property('completions').and.be.an.Object()
2341+
body.assistant.completions.should.have.property('enabled', false)
2342+
})
2343+
it('device downloads settings including assistant completions settings when enabled', async function () {
2344+
app = await setup({
2345+
assistant: {
2346+
enabled: true,
2347+
requestTimeout: 12345
2348+
// mcp deliberately excluded to check it defaults to enabled
2349+
// completions deliberately excluded to check it defaults to enabled
2350+
}
2351+
})
2352+
await login('alice', 'aaPassword')
2353+
const device = await createDevice({ name: 'AppDevice2', type: 'AppDevice2_type', team: app.team.hashid, as: TestObjects.tokens.alice })
2354+
const dbDevice = await app.db.models.Device.byId(device.id)
2355+
dbDevice.setApplication(app.application)
2356+
await dbDevice.save()
2357+
2358+
const body = await getLiveSettings(device)
2359+
body.should.have.property('assistant').and.be.an.Object()
2360+
body.assistant.should.have.property('enabled', true)
2361+
body.assistant.should.have.property('mcp').and.be.an.Object()
2362+
body.assistant.mcp.should.have.property('enabled', true) // defaults to enabled
2363+
body.assistant.should.have.property('completions').and.be.an.Object()
2364+
body.assistant.completions.should.have.property('enabled', true) // defaults to enabled
2365+
})
2366+
})
22992367

23002368
describe('Device state', function () {
23012369
async function setupProjectWithSnapshot (setActive) {

test/unit/forge/routes/api/project_spec.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,63 @@ describe('Project API', function () {
23012301
data3.should.have.property('settings')
23022302
data3.settings.should.have.property('dashboard2UI')
23032303
})
2304+
describe('Assistant Settings', function () {
2305+
// these tests are run with a clean app since they change the app config
2306+
beforeEach(async function () {
2307+
// Close down the default app
2308+
if (app) {
2309+
await app.close()
2310+
}
2311+
app = null
2312+
})
2313+
after(async function () {
2314+
// Once all done, create the clean app for later tests
2315+
await app.close()
2316+
await setupApp()
2317+
})
2318+
2319+
it('assistant can be disabled', async function () {
2320+
app = await setup({
2321+
assistant: {
2322+
enabled: false,
2323+
mcp: { enabled: false },
2324+
completions: { enabled: false }
2325+
}
2326+
})
2327+
2328+
await login('alice', 'aaPassword')
2329+
TestObjects.tokens.project = (await app.project.refreshAuthTokens()).token
2330+
2331+
const body = await getSettings()
2332+
body.should.have.property('assistant').and.be.an.Object()
2333+
body.assistant.should.have.property('enabled', false)
2334+
body.assistant.should.have.property('mcp').and.be.an.Object()
2335+
body.assistant.mcp.should.have.property('enabled', false)
2336+
body.assistant.should.have.property('completions').and.be.an.Object()
2337+
body.assistant.completions.should.have.property('enabled', false)
2338+
})
2339+
it('instance settings including assistant completions settings by default', async function () {
2340+
app = await setup({
2341+
assistant: {
2342+
enabled: true,
2343+
requestTimeout: 12345
2344+
// mcp deliberately excluded to check it defaults to enabled
2345+
// completions deliberately excluded to check it defaults to enabled
2346+
}
2347+
})
2348+
2349+
await login('alice', 'aaPassword')
2350+
TestObjects.tokens.project = (await app.project.refreshAuthTokens()).token
2351+
2352+
const body = await getSettings(app.project)
2353+
body.should.have.property('assistant').and.be.an.Object()
2354+
body.assistant.should.have.property('enabled', true)
2355+
body.assistant.should.have.property('mcp').and.be.an.Object()
2356+
body.assistant.mcp.should.have.property('enabled', true) // defaults to enabled
2357+
body.assistant.should.have.property('completions').and.be.an.Object()
2358+
body.assistant.completions.should.have.property('enabled', true) // defaults to enabled
2359+
})
2360+
})
23042361
})
23052362

23062363
describe('Project import flows & credentials', function () {

0 commit comments

Comments
 (0)