Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
C
coderai
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
coderai
Commits
c51704c4
Commit
c51704c4
authored
May 11, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add AISBF broker settings UI and docs
parent
dfe27069
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
200 additions
and
0 deletions
+200
-0
README.md
README.md
+39
-0
routes.py
codai/admin/routes.py
+38
-0
settings.html
codai/admin/templates/settings.html
+111
-0
test_whisper_server_local_models.py
tests/test_whisper_server_local_models.py
+12
-0
No files found.
README.md
View file @
c51704c4
...
@@ -247,6 +247,45 @@ Config files live in `~/.coderai/` (or `--config` path):
...
@@ -247,6 +247,45 @@ Config files live in `~/.coderai/` (or `--config` path):
└── secret_key # Session signing key (auto-generated)
└── secret_key # Session signing key (auto-generated)
```
```
### AISBF Broker Client
CoderAI includes an AISBF broker websocket client that can register this instance
with a broker and receive brokered requests.
You can configure it either by editing
`config.json`
directly or from the admin
Settings page under
`AISBF Broker`
.
Example broker configuration:
```
json
{
"broker"
:
{
"enabled"
:
true
,
"base_url"
:
"https://broker.example.com"
,
"scope"
:
"user"
,
"username"
:
"alice"
,
"provider_id"
:
"coderai-local"
,
"client_id"
:
"workstation-01"
,
"registration_token"
:
"your-registration-token"
,
"advertised_endpoint"
:
"http://127.0.0.1:8776"
,
"transport"
:
"websocket"
,
"heartbeat_interval_seconds"
:
30
,
"connect_timeout_seconds"
:
10
,
"request_timeout_seconds"
:
30
,
"reconnect_initial_delay_seconds"
:
1
,
"reconnect_max_delay_seconds"
:
60
}
}
```
Broker notes:
-
`base_url`
accepts
`http`
,
`https`
,
`ws`
, or
`wss`
; the websocket route is derived automatically.
-
`scope: "user"`
requires a non-global
`username`
.
-
`scope: "global"`
requires
`username: "global"`
.
-
When
`enabled`
is
`true`
,
`provider_id`
,
`client_id`
, and
`registration_token`
are required.
-
`advertised_endpoint`
is optional and is sent to the broker as the externally reachable endpoint for this instance.
-
Restart CoderAI after changing broker settings so the background broker service reconnects with the new configuration.
### config.json
### config.json
```
json
```
json
...
...
codai/admin/routes.py
View file @
c51704c4
...
@@ -1624,6 +1624,22 @@ async def api_get_settings(username: str = Depends(require_admin)):
...
@@ -1624,6 +1624,22 @@ async def api_get_settings(username: str = Depends(require_admin)):
"directory"
:
c
.
archive
.
directory
,
"directory"
:
c
.
archive
.
directory
,
"retention"
:
c
.
archive
.
retention
,
"retention"
:
c
.
archive
.
retention
,
},
},
"broker"
:
{
"enabled"
:
c
.
broker
.
enabled
,
"base_url"
:
c
.
broker
.
base_url
,
"scope"
:
c
.
broker
.
scope
,
"username"
:
c
.
broker
.
username
,
"provider_id"
:
c
.
broker
.
provider_id
,
"client_id"
:
c
.
broker
.
client_id
,
"registration_token"
:
c
.
broker
.
registration_token
,
"advertised_endpoint"
:
c
.
broker
.
advertised_endpoint
,
"transport"
:
c
.
broker
.
transport
,
"heartbeat_interval_seconds"
:
c
.
broker
.
heartbeat_interval_seconds
,
"connect_timeout_seconds"
:
c
.
broker
.
connect_timeout_seconds
,
"request_timeout_seconds"
:
c
.
broker
.
request_timeout_seconds
,
"reconnect_initial_delay_seconds"
:
c
.
broker
.
reconnect_initial_delay_seconds
,
"reconnect_max_delay_seconds"
:
c
.
broker
.
reconnect_max_delay_seconds
,
},
"system_prompt"
:
c
.
system_prompt
,
"system_prompt"
:
c
.
system_prompt
,
"tools_closer_prompt"
:
c
.
tools_closer_prompt
,
"tools_closer_prompt"
:
c
.
tools_closer_prompt
,
"grammar_guided"
:
c
.
grammar_guided
,
"grammar_guided"
:
c
.
grammar_guided
,
...
@@ -1714,6 +1730,28 @@ async def api_save_settings(request: Request, username: str = Depends(require_ad
...
@@ -1714,6 +1730,28 @@ async def api_save_settings(request: Request, username: str = Depends(require_ad
resolved
=
raw_dir
if
raw_dir
and
_os
.
path
.
isabs
(
raw_dir
)
else
_os
.
path
.
join
(
cfg_dir
,
raw_dir
or
"archive"
)
resolved
=
raw_dir
if
raw_dir
and
_os
.
path
.
isabs
(
raw_dir
)
else
_os
.
path
.
join
(
cfg_dir
,
raw_dir
or
"archive"
)
archive_manager
.
configure
(
c
.
archive
.
enabled
,
resolved
,
c
.
archive
.
retention
)
archive_manager
.
configure
(
c
.
archive
.
enabled
,
resolved
,
c
.
archive
.
retention
)
if
"broker"
in
data
:
bro
=
data
[
"broker"
]
c
.
broker
.
enabled
=
bool
(
bro
.
get
(
"enabled"
,
c
.
broker
.
enabled
))
c
.
broker
.
base_url
=
(
bro
.
get
(
"base_url"
)
or
""
)
.
strip
()
c
.
broker
.
scope
=
(
bro
.
get
(
"scope"
)
or
c
.
broker
.
scope
or
"user"
)
.
strip
()
c
.
broker
.
username
=
(
bro
.
get
(
"username"
)
or
""
)
.
strip
()
c
.
broker
.
provider_id
=
(
bro
.
get
(
"provider_id"
)
or
""
)
.
strip
()
c
.
broker
.
client_id
=
(
bro
.
get
(
"client_id"
)
or
""
)
.
strip
()
c
.
broker
.
registration_token
=
(
bro
.
get
(
"registration_token"
)
or
""
)
.
strip
()
c
.
broker
.
advertised_endpoint
=
(
bro
.
get
(
"advertised_endpoint"
)
or
""
)
.
strip
()
c
.
broker
.
transport
=
(
bro
.
get
(
"transport"
)
or
c
.
broker
.
transport
or
"websocket"
)
.
strip
()
c
.
broker
.
heartbeat_interval_seconds
=
max
(
1
,
int
(
bro
.
get
(
"heartbeat_interval_seconds"
,
c
.
broker
.
heartbeat_interval_seconds
)))
c
.
broker
.
connect_timeout_seconds
=
max
(
1
,
int
(
bro
.
get
(
"connect_timeout_seconds"
,
c
.
broker
.
connect_timeout_seconds
)))
c
.
broker
.
request_timeout_seconds
=
max
(
1
,
int
(
bro
.
get
(
"request_timeout_seconds"
,
c
.
broker
.
request_timeout_seconds
)))
c
.
broker
.
reconnect_initial_delay_seconds
=
max
(
1
,
int
(
bro
.
get
(
"reconnect_initial_delay_seconds"
,
c
.
broker
.
reconnect_initial_delay_seconds
)))
c
.
broker
.
reconnect_max_delay_seconds
=
max
(
c
.
broker
.
reconnect_initial_delay_seconds
,
int
(
bro
.
get
(
"reconnect_max_delay_seconds"
,
c
.
broker
.
reconnect_max_delay_seconds
)),
)
from
codai.broker.config
import
build_broker_runtime_config
request
.
app
.
state
.
broker_runtime
=
build_broker_runtime_config
(
c
.
broker
)
config_manager
.
save_config
()
config_manager
.
save_config
()
return
{
"success"
:
True
}
return
{
"success"
:
True
}
...
...
codai/admin/templates/settings.html
View file @
c51704c4
...
@@ -101,6 +101,81 @@
...
@@ -101,6 +101,81 @@
<span
class=
"form-hint"
>
Archived entries older than this are automatically deleted. Takes effect immediately on save.
</span>
<span
class=
"form-hint"
>
Archived entries older than this are automatically deleted. Takes effect immediately on save.
</span>
</div>
</div>
</div>
</div>
<div
class=
"card mb-0"
style=
"margin-top:1rem"
>
<div
class=
"card-title"
>
AISBF Broker
</div>
<div
class=
"form-row"
>
<label
style=
"display:flex;align-items:center;gap:.5rem;cursor:pointer"
>
<input
type=
"checkbox"
id=
"s-broker-enabled"
onchange=
"toggleBrokerFields()"
>
<span
style=
"font-size:13px;font-weight:500"
>
Enable broker client
</span>
</label>
<span
class=
"form-hint"
>
Registers this CoderAI instance with an AISBF broker so it can receive brokered requests.
</span>
</div>
<div
id=
"broker-fields"
style=
"display:none"
>
<div
class=
"form-row"
>
<label
class=
"form-label"
>
Broker base URL
</label>
<input
type=
"text"
id=
"s-broker-base-url"
class=
"form-input"
placeholder=
"https://broker.example.com"
>
<span
class=
"form-hint"
>
Supports `http`, `https`, `ws`, or `wss`. The websocket endpoint is derived automatically.
</span>
</div>
<div
style=
"display:grid;grid-template-columns:180px 1fr;gap:1rem;align-items:start"
>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Scope
</label>
<select
id=
"s-broker-scope"
class=
"form-input"
>
<option
value=
"user"
>
user
</option>
<option
value=
"global"
>
global
</option>
</select>
</div>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Username
</label>
<input
type=
"text"
id=
"s-broker-username"
class=
"form-input"
placeholder=
"alice or global"
>
<span
class=
"form-hint"
>
Use `global` when scope is `global`; otherwise provide the AISBF username.
</span>
</div>
</div>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start"
>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Provider ID
</label>
<input
type=
"text"
id=
"s-broker-provider-id"
class=
"form-input"
placeholder=
"coderai-local"
>
</div>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Client ID
</label>
<input
type=
"text"
id=
"s-broker-client-id"
class=
"form-input"
placeholder=
"workstation-01"
>
</div>
</div>
<div
class=
"form-row"
>
<label
class=
"form-label"
>
Registration token
</label>
<input
type=
"password"
id=
"s-broker-registration-token"
class=
"form-input"
placeholder=
"Broker registration token"
>
</div>
<div
class=
"form-row"
>
<label
class=
"form-label"
>
Advertised endpoint
</label>
<input
type=
"text"
id=
"s-broker-advertised-endpoint"
class=
"form-input"
placeholder=
"http://127.0.0.1:8776"
>
<span
class=
"form-hint"
>
Optional external URL advertised to the broker for this instance.
</span>
</div>
<div
style=
"display:grid;grid-template-columns:repeat(3, minmax(0, 1fr));gap:1rem;align-items:start"
>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Heartbeat seconds
</label>
<input
type=
"number"
id=
"s-broker-heartbeat"
class=
"form-input"
min=
"1"
placeholder=
"30"
>
</div>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Connect timeout
</label>
<input
type=
"number"
id=
"s-broker-connect-timeout"
class=
"form-input"
min=
"1"
placeholder=
"10"
>
</div>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Request timeout
</label>
<input
type=
"number"
id=
"s-broker-request-timeout"
class=
"form-input"
min=
"1"
placeholder=
"30"
>
</div>
</div>
<div
style=
"display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:1rem;align-items:start"
>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Reconnect initial delay
</label>
<input
type=
"number"
id=
"s-broker-reconnect-initial"
class=
"form-input"
min=
"1"
placeholder=
"1"
>
</div>
<div
class=
"form-row"
style=
"margin:0"
>
<label
class=
"form-label"
>
Reconnect max delay
</label>
<input
type=
"number"
id=
"s-broker-reconnect-max"
class=
"form-input"
min=
"1"
placeholder=
"60"
>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}
{% block scripts %}
{% block scripts %}
...
@@ -110,6 +185,11 @@ function toggleHttps(){
...
@@ -110,6 +185,11 @@ function toggleHttps(){
document
.
getElementById
(
's-https'
).
checked
?
'block'
:
'none'
;
document
.
getElementById
(
's-https'
).
checked
?
'block'
:
'none'
;
}
}
function
toggleBrokerFields
(){
document
.
getElementById
(
'broker-fields'
).
style
.
display
=
document
.
getElementById
(
's-broker-enabled'
).
checked
?
'block'
:
'none'
;
}
function
showAlert
(
type
,
msg
){
function
showAlert
(
type
,
msg
){
const
el
=
document
.
getElementById
(
'settings-alert'
);
const
el
=
document
.
getElementById
(
'settings-alert'
);
el
.
className
=
'alert alert-'
+
(
type
===
'error'
?
'error'
:
'info'
);
el
.
className
=
'alert alert-'
+
(
type
===
'error'
?
'error'
:
'info'
);
...
@@ -143,6 +223,21 @@ async function loadSettings(){
...
@@ -143,6 +223,21 @@ async function loadSettings(){
if
(
hint
&&
as
.
default_directory
)
hint
.
textContent
=
`(default:
${
as
.
default_directory
}
)`
;
if
(
hint
&&
as
.
default_directory
)
hint
.
textContent
=
`(default:
${
as
.
default_directory
}
)`
;
document
.
getElementById
(
's-arc-dir'
).
placeholder
=
as
.
default_directory
||
'(default)'
;
document
.
getElementById
(
's-arc-dir'
).
placeholder
=
as
.
default_directory
||
'(default)'
;
}
catch
(
_
){}
}
catch
(
_
){}
const
broker
=
d
.
broker
||
{};
document
.
getElementById
(
's-broker-enabled'
).
checked
=
!!
broker
.
enabled
;
document
.
getElementById
(
's-broker-base-url'
).
value
=
broker
.
base_url
??
''
;
document
.
getElementById
(
's-broker-scope'
).
value
=
broker
.
scope
??
'user'
;
document
.
getElementById
(
's-broker-username'
).
value
=
broker
.
username
??
''
;
document
.
getElementById
(
's-broker-provider-id'
).
value
=
broker
.
provider_id
??
''
;
document
.
getElementById
(
's-broker-client-id'
).
value
=
broker
.
client_id
??
''
;
document
.
getElementById
(
's-broker-registration-token'
).
value
=
broker
.
registration_token
??
''
;
document
.
getElementById
(
's-broker-advertised-endpoint'
).
value
=
broker
.
advertised_endpoint
??
''
;
document
.
getElementById
(
's-broker-heartbeat'
).
value
=
broker
.
heartbeat_interval_seconds
??
30
;
document
.
getElementById
(
's-broker-connect-timeout'
).
value
=
broker
.
connect_timeout_seconds
??
10
;
document
.
getElementById
(
's-broker-request-timeout'
).
value
=
broker
.
request_timeout_seconds
??
30
;
document
.
getElementById
(
's-broker-reconnect-initial'
).
value
=
broker
.
reconnect_initial_delay_seconds
??
1
;
document
.
getElementById
(
's-broker-reconnect-max'
).
value
=
broker
.
reconnect_max_delay_seconds
??
60
;
toggleBrokerFields
();
}
catch
(
e
){
showAlert
(
'error'
,
'Failed to load settings: '
+
e
.
message
);
}
}
catch
(
e
){
showAlert
(
'error'
,
'Failed to load settings: '
+
e
.
message
);
}
}
}
...
@@ -169,6 +264,22 @@ async function saveSettings(){
...
@@ -169,6 +264,22 @@ async function saveSettings(){
directory
:
document
.
getElementById
(
's-arc-dir'
).
value
.
trim
(),
directory
:
document
.
getElementById
(
's-arc-dir'
).
value
.
trim
(),
retention
:
document
.
getElementById
(
's-arc-retention'
).
value
,
retention
:
document
.
getElementById
(
's-arc-retention'
).
value
,
},
},
broker
:{
enabled
:
document
.
getElementById
(
's-broker-enabled'
).
checked
,
base_url
:
document
.
getElementById
(
's-broker-base-url'
).
value
.
trim
(),
scope
:
document
.
getElementById
(
's-broker-scope'
).
value
,
username
:
document
.
getElementById
(
's-broker-username'
).
value
.
trim
(),
provider_id
:
document
.
getElementById
(
's-broker-provider-id'
).
value
.
trim
(),
client_id
:
document
.
getElementById
(
's-broker-client-id'
).
value
.
trim
(),
registration_token
:
document
.
getElementById
(
's-broker-registration-token'
).
value
.
trim
(),
advertised_endpoint
:
document
.
getElementById
(
's-broker-advertised-endpoint'
).
value
.
trim
(),
heartbeat_interval_seconds
:
parseInt
(
document
.
getElementById
(
's-broker-heartbeat'
).
value
)
||
30
,
connect_timeout_seconds
:
parseInt
(
document
.
getElementById
(
's-broker-connect-timeout'
).
value
)
||
10
,
request_timeout_seconds
:
parseInt
(
document
.
getElementById
(
's-broker-request-timeout'
).
value
)
||
30
,
reconnect_initial_delay_seconds
:
parseInt
(
document
.
getElementById
(
's-broker-reconnect-initial'
).
value
)
||
1
,
reconnect_max_delay_seconds
:
parseInt
(
document
.
getElementById
(
's-broker-reconnect-max'
).
value
)
||
60
,
transport
:
'websocket'
,
},
};
};
try
{
try
{
const
r
=
await
fetch
(
ROOT_PATH
+
'/admin/api/settings'
,{
const
r
=
await
fetch
(
ROOT_PATH
+
'/admin/api/settings'
,{
...
...
tests/test_whisper_server_local_models.py
View file @
c51704c4
...
@@ -841,3 +841,15 @@ def test_settings_template_keeps_queue_size_control():
...
@@ -841,3 +841,15 @@ def test_settings_template_keeps_queue_size_control():
assert
"Request queue max size"
in
template
assert
"Request queue max size"
in
template
assert
"s-queue-max"
in
template
assert
"s-queue-max"
in
template
def
test_settings_template_includes_broker_controls
():
template
=
Path
(
"codai/admin/templates/settings.html"
)
.
read_text
()
assert
"AISBF Broker"
in
template
assert
"s-broker-enabled"
in
template
assert
"s-broker-base-url"
in
template
assert
"s-broker-provider-id"
in
template
assert
"s-broker-client-id"
in
template
assert
"s-broker-registration-token"
in
template
assert
"toggleBrokerFields()"
in
template
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment