Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
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
aisbf
Commits
ce6a2513
Commit
ce6a2513
authored
May 14, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update to latest revision
parent
926cfaac
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
341 additions
and
7 deletions
+341
-7
coderai_broker.py
aisbf/coderai_broker.py
+8
-2
config.py
aisbf/config.py
+21
-1
settings.py
aisbf/routes/dashboard/settings.py
+4
-2
studio.py
aisbf/studio.py
+36
-1
aisbf-oauth2-extension.zip
static/aisbf-oauth2-extension.zip
+0
-0
studio.js
static/dashboard/studio.js
+17
-1
test_dashboard_settings_feature_controls.py
tests/routes/test_dashboard_settings_feature_controls.py
+212
-0
test_studio_sidebar_and_coderai_catalog.py
tests/test_studio_sidebar_and_coderai_catalog.py
+43
-0
No files found.
aisbf/coderai_broker.py
View file @
ce6a2513
...
@@ -480,6 +480,12 @@ class CoderAIBroker:
...
@@ -480,6 +480,12 @@ class CoderAIBroker:
async
def
send_request
(
self
,
provider_id
:
str
,
op
:
str
,
payload
:
Dict
[
str
,
Any
],
timeout
:
float
=
300.0
,
client_id
:
Optional
[
str
]
=
None
,
owner_user_id
:
Optional
[
int
]
=
None
,
extra
:
Optional
[
Dict
[
str
,
Any
]]
=
None
)
->
Dict
[
str
,
Any
]:
async
def
send_request
(
self
,
provider_id
:
str
,
op
:
str
,
payload
:
Dict
[
str
,
Any
],
timeout
:
float
=
300.0
,
client_id
:
Optional
[
str
]
=
None
,
owner_user_id
:
Optional
[
int
]
=
None
,
extra
:
Optional
[
Dict
[
str
,
Any
]]
=
None
)
->
Dict
[
str
,
Any
]:
snapshot
=
await
self
.
get_session_snapshot
(
provider_id
,
client_id
)
snapshot
=
await
self
.
get_session_snapshot
(
provider_id
,
client_id
)
if
(
not
snapshot
or
not
snapshot
.
get
(
"connected"
))
and
client_id
:
fallback_snapshot
=
await
self
.
get_session_snapshot
(
client_id
,
client_id
)
fallback_metadata
=
(
fallback_snapshot
or
{})
.
get
(
"metadata"
)
or
{}
if
fallback_snapshot
and
fallback_snapshot
.
get
(
"connected"
)
and
fallback_metadata
.
get
(
"owner_user_id"
)
==
owner_user_id
:
snapshot
=
fallback_snapshot
provider_id
=
snapshot
.
get
(
"provider_id"
)
or
provider_id
if
not
snapshot
or
not
snapshot
.
get
(
"connected"
):
if
not
snapshot
or
not
snapshot
.
get
(
"connected"
):
raise
RuntimeError
(
f
"No active CoderAI broker session for provider '{provider_id}'"
)
raise
RuntimeError
(
f
"No active CoderAI broker session for provider '{provider_id}'"
)
if
owner_user_id
!=
((
snapshot
.
get
(
'metadata'
)
or
{})
.
get
(
'owner_user_id'
)):
if
owner_user_id
!=
((
snapshot
.
get
(
'metadata'
)
or
{})
.
get
(
'owner_user_id'
)):
...
@@ -493,7 +499,7 @@ class CoderAIBroker:
...
@@ -493,7 +499,7 @@ class CoderAIBroker:
"v"
:
1
,
"v"
:
1
,
"op"
:
op
,
"op"
:
op
,
"request_id"
:
request_id
,
"request_id"
:
request_id
,
"provider_id"
:
provider_id
,
"provider_id"
:
snapshot
.
get
(
"provider_id"
)
or
provider_id
,
"client_id"
:
snapshot
.
get
(
"client_id"
)
or
client_id
,
"client_id"
:
snapshot
.
get
(
"client_id"
)
or
client_id
,
"payload"
:
payload
,
"payload"
:
payload
,
"reply_key"
:
self
.
_reply_key
(
request_id
),
"reply_key"
:
self
.
_reply_key
(
request_id
),
...
@@ -509,7 +515,7 @@ class CoderAIBroker:
...
@@ -509,7 +515,7 @@ class CoderAIBroker:
stream_queue
=
stream_queue
,
stream_queue
=
stream_queue
,
request_snapshot
=
{
request_snapshot
=
{
"session_id"
:
snapshot
.
get
(
"session_id"
),
"session_id"
:
snapshot
.
get
(
"session_id"
),
"provider_id"
:
provider_id
,
"provider_id"
:
snapshot
.
get
(
"provider_id"
)
or
provider_id
,
"client_id"
:
snapshot
.
get
(
"client_id"
)
or
client_id
,
"client_id"
:
snapshot
.
get
(
"client_id"
)
or
client_id
,
"op"
:
op
,
"op"
:
op
,
},
},
...
...
aisbf/config.py
View file @
ce6a2513
...
@@ -472,6 +472,26 @@ class Config:
...
@@ -472,6 +472,26 @@ class Config:
raise
FileNotFoundError
(
"Could not find configuration files"
)
raise
FileNotFoundError
(
"Could not find configuration files"
)
def
_get_aisbf_config_path
(
self
)
->
Path
:
"""Resolve the active aisbf.json path consistently across startup and reloads."""
candidates
=
[]
if
self
.
_custom_config_dir
and
self
.
_custom_config_dir
.
exists
():
candidates
.
append
(
self
.
_custom_config_dir
/
'aisbf.json'
)
candidates
+=
[
Path
.
home
()
/
'.aisbf'
/
'aisbf.json'
,
Path
.
home
()
/
'.local'
/
'share'
/
'aisbf'
/
'aisbf.json'
,
Path
(
'/usr/local/share/aisbf/aisbf.json'
),
Path
(
'/usr/share/aisbf/aisbf.json'
),
Path
(
__file__
)
.
parent
.
parent
/
'config'
/
'aisbf.json'
,
]
for
candidate
in
candidates
:
if
candidate
.
exists
():
return
candidate
return
candidates
[
-
1
]
def
_ensure_config_directory
(
self
):
def
_ensure_config_directory
(
self
):
"""Ensure ~/.aisbf/ directory exists and copy default config files if needed"""
"""Ensure ~/.aisbf/ directory exists and copy default config files if needed"""
config_dir
=
Path
.
home
()
/
'.aisbf'
config_dir
=
Path
.
home
()
/
'.aisbf'
...
@@ -930,7 +950,7 @@ class Config:
...
@@ -930,7 +950,7 @@ class Config:
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
logger
.
info
(
f
"=== Config._load_aisbf_config START ==="
)
logger
.
info
(
f
"=== Config._load_aisbf_config START ==="
)
aisbf_path
=
Path
.
home
()
/
'.aisbf'
/
'aisbf.json'
aisbf_path
=
self
.
_get_aisbf_config_path
()
logger
.
info
(
f
"Looking for AISBF config in: {aisbf_path}"
)
logger
.
info
(
f
"Looking for AISBF config in: {aisbf_path}"
)
if
not
aisbf_path
.
exists
():
if
not
aisbf_path
.
exists
():
...
...
aisbf/routes/dashboard/settings.py
View file @
ce6a2513
...
@@ -761,7 +761,9 @@ async def dashboard_settings_save(
...
@@ -761,7 +761,9 @@ async def dashboard_settings_save(
'requests_per_hour'
:
max
(
0
,
client_rl_general_rph
)
'requests_per_hour'
:
max
(
0
,
client_rl_general_rph
)
}
}
# Save config
# Save config back to the same resolved path we loaded from
config_path
=
get_aisbf_config_path
()
if
not
config_path
.
exists
():
config_path
=
Path
.
home
()
/
'.aisbf'
/
'aisbf.json'
config_path
=
Path
.
home
()
/
'.aisbf'
/
'aisbf.json'
config_path
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
config_path
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
with
open
(
config_path
,
'w'
)
as
f
:
with
open
(
config_path
,
'w'
)
as
f
:
...
...
aisbf/studio.py
View file @
ce6a2513
...
@@ -19,11 +19,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -19,11 +19,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from
__future__
import
annotations
from
__future__
import
annotations
import
asyncio
from
dataclasses
import
dataclass
from
dataclasses
import
dataclass
from
pathlib
import
Path
from
pathlib
import
Path
import
json
import
json
from
typing
import
Any
,
Dict
,
Iterable
,
List
,
Optional
from
typing
import
Any
,
Dict
,
Iterable
,
List
,
Optional
from
aisbf.app.model_cache
import
get_provider_models
from
aisbf.studio_adapters
import
effective_studio_adapter
,
infer_studio_adapter_profile
from
aisbf.studio_adapters
import
effective_studio_adapter
,
infer_studio_adapter_profile
...
@@ -593,8 +595,41 @@ def build_studio_catalog(
...
@@ -593,8 +595,41 @@ def build_studio_catalog(
rotations
=
getattr
(
config
,
"rotations"
,
None
)
or
{}
rotations
=
getattr
(
config
,
"rotations"
,
None
)
or
{}
autoselects
=
getattr
(
config
,
"autoselect"
,
None
)
or
{}
autoselects
=
getattr
(
config
,
"autoselect"
,
None
)
or
{}
provider_entries
=
_build_provider_entries
(
scope
,
owner_id
,
providers
)
missing_provider_ids
=
{
provider_id
for
provider_id
,
provider_config
in
(
providers
or
{})
.
items
()
if
not
_provider_models_from_config
(
provider_config
)
}
if
missing_provider_ids
and
config
is
not
None
:
for
provider_id
in
missing_provider_ids
:
provider_config
=
providers
.
get
(
provider_id
)
if
provider_config
is
None
:
continue
try
:
live_models
=
asyncio
.
run
(
get_provider_models
(
provider_id
,
provider_config
,
config
,
user_id
=
owner_id
if
scope
==
"user"
else
None
))
except
RuntimeError
:
live_models
=
[]
except
Exception
:
live_models
=
[]
if
not
live_models
:
continue
live_model_names
=
{
(
model
.
get
(
"name"
)
or
model
.
get
(
"model_name"
)
or
model
.
get
(
"id"
)
or
""
)
.
split
(
"/"
,
1
)[
-
1
]
for
model
in
live_models
if
isinstance
(
model
,
dict
)
}
provider_entries
=
[
entry
for
entry
in
provider_entries
if
not
(
entry
.
get
(
"kind"
)
==
"provider_model"
and
entry
.
get
(
"source_id"
)
==
provider_id
and
entry
.
get
(
"target_id"
)
in
live_model_names
)
]
hydrated_provider
=
provider_config
if
isinstance
(
provider_config
,
dict
)
else
provider_config
.
model_dump
()
hydrated_provider
=
dict
(
hydrated_provider
)
hydrated_provider
[
"models"
]
=
live_models
provider_entries
.
extend
(
_build_provider_entries
(
scope
,
owner_id
,
{
provider_id
:
hydrated_provider
}))
entries
=
[
entries
=
[
*
_build_provider_entries
(
scope
,
owner_id
,
providers
)
,
*
provider_entries
,
*
_build_rotation_entries
(
scope
,
owner_id
,
rotations
),
*
_build_rotation_entries
(
scope
,
owner_id
,
rotations
),
*
_build_autoselect_entries
(
scope
,
owner_id
,
autoselects
),
*
_build_autoselect_entries
(
scope
,
owner_id
,
autoselects
),
]
]
...
...
static/aisbf-oauth2-extension.zip
View file @
ce6a2513
No preview for this file type
static/dashboard/studio.js
View file @
ce6a2513
...
@@ -1609,8 +1609,24 @@ const BLABEL = {text:'LLM',vision:'VLM',image:'IMG',video:'VID',audio:'STT',
...
@@ -1609,8 +1609,24 @@ const BLABEL = {text:'LLM',vision:'VLM',image:'IMG',video:'VID',audio:'STT',
function
renderSidebar
()
{
function
renderSidebar
()
{
const
el
=
$
(
'model-list'
);
const
el
=
$
(
'model-list'
);
const
activeEl
=
document
.
activeElement
;
const
activeIsBindingSearch
=
activeEl
&&
activeEl
.
classList
&&
activeEl
.
classList
.
contains
(
'binding-role-search'
);
const
activeValue
=
activeIsBindingSearch
?
activeEl
.
value
:
''
;
const
activeSelectionStart
=
activeIsBindingSearch
&&
typeof
activeEl
.
selectionStart
===
'number'
?
activeEl
.
selectionStart
:
null
;
const
activeSelectionEnd
=
activeIsBindingSearch
&&
typeof
activeEl
.
selectionEnd
===
'number'
?
activeEl
.
selectionEnd
:
null
;
const
restoreKey
=
activeIsBindingSearch
?
activeEl
.
getAttribute
(
'data-search-key'
)
:
null
;
if
(
!
functionBindingDefs
.
length
)
{
el
.
innerHTML
=
'<div class="muted small" style="padding:.5rem .6rem">No Studio bindings</div>'
;
return
;
}
if
(
!
functionBindingDefs
.
length
)
{
el
.
innerHTML
=
'<div class="muted small" style="padding:.5rem .6rem">No Studio bindings</div>'
;
return
;
}
el
.
innerHTML
=
`<div class="binding-list">
${
functionBindingDefs
.
map
(
renderBindingCard
).
join
(
''
)}
</div>`
;
el
.
innerHTML
=
`<div class="binding-list">
${
functionBindingDefs
.
map
(
renderBindingCard
).
join
(
''
)}
</div>`
;
if
(
restoreKey
)
{
const
nextEl
=
el
.
querySelector
(
`.binding-role-search[data-search-key="
${
CSS
.
escape
(
restoreKey
)}
"]`
);
if
(
nextEl
)
{
nextEl
.
focus
();
if
(
nextEl
.
value
!==
activeValue
)
nextEl
.
value
=
activeValue
;
if
(
activeSelectionStart
!==
null
&&
activeSelectionEnd
!==
null
&&
typeof
nextEl
.
setSelectionRange
===
'function'
)
{
nextEl
.
setSelectionRange
(
activeSelectionStart
,
activeSelectionEnd
);
}
}
}
}
}
function
renderBindingCard
(
def
)
{
function
renderBindingCard
(
def
)
{
...
@@ -1657,7 +1673,7 @@ function renderBindingRole(def, role) {
...
@@ -1657,7 +1673,7 @@ function renderBindingRole(def, role) {
<div class="binding-role-state">
${
assignedModel
?
'Bound'
:
(
role
.
optional
?
'Optional'
:
'Missing'
)}
</div>
<div class="binding-role-state">
${
assignedModel
?
'Bound'
:
(
role
.
optional
?
'Optional'
:
'Missing'
)}
</div>
</div>
</div>
<div class="binding-role-meta">
${
currentMeta
}
</div>
<div class="binding-role-meta">
${
currentMeta
}
</div>
<input class="fi binding-role-search" type="search" value="
${
escapeHtml
(
query
)}
" placeholder="Search provider/model, rotation, autoselect" oninput="updateBindingSearch('
${
def
.
id
}
','
${
role
.
key
}
', this.value)">
<input class="fi binding-role-search" type="search"
data-search-key="
${
escapeHtml
(
searchKey
)}
"
value="
${
escapeHtml
(
query
)}
" placeholder="Search provider/model, rotation, autoselect" oninput="updateBindingSearch('
${
def
.
id
}
','
${
role
.
key
}
', this.value)">
<div class="binding-role-results">
${
results
}
</div>
<div class="binding-role-results">
${
results
}
</div>
${
assignedModel
?
`<button class="btn btn-ghost btn-sm binding-role-clear" onclick="clearBindingRole('
${
def
.
id
}
','
${
role
.
key
}
');return false;">Clear</button>`
:
''
}
${
assignedModel
?
`<button class="btn btn-ghost btn-sm binding-role-clear" onclick="clearBindingRole('
${
def
.
id
}
','
${
role
.
key
}
');return false;">Clear</button>`
:
''
}
</div>`
;
</div>`
;
...
...
tests/routes/test_dashboard_settings_feature_controls.py
View file @
ce6a2513
import
json
import
json
import
os
from
base64
import
b64encode
from
base64
import
b64encode
from
pathlib
import
Path
from
pathlib
import
Path
...
@@ -6,6 +7,7 @@ from fastapi.testclient import TestClient
...
@@ -6,6 +7,7 @@ from fastapi.testclient import TestClient
from
itsdangerous
import
TimestampSigner
from
itsdangerous
import
TimestampSigner
from
aisbf.routes.dashboard
import
settings
as
dashboard_settings
from
aisbf.routes.dashboard
import
settings
as
dashboard_settings
from
aisbf.config
import
Config
import
sys
import
sys
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
2
]))
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
2
]))
...
@@ -192,3 +194,213 @@ def test_dashboard_settings_save_persists_feature_controls(tmp_path, monkeypatch
...
@@ -192,3 +194,213 @@ def test_dashboard_settings_save_persists_feature_controls(tmp_path, monkeypatch
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"persist_prompt_text"
][
"mode"
]
==
"enabled"
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"persist_prompt_text"
][
"mode"
]
==
"enabled"
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"redact_before_persist"
][
"mode"
]
==
"disabled"
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"redact_before_persist"
][
"mode"
]
==
"disabled"
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"risk_threshold"
]
==
"medium"
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"risk_threshold"
]
==
"medium"
def
test_dashboard_settings_save_writes_back_to_resolved_config_path
(
tmp_path
,
monkeypatch
):
resolved_cfg_path
=
tmp_path
/
"custom-location.json"
resolved_cfg_path
.
write_text
(
json
.
dumps
({
"server"
:
{
"host"
:
"127.0.0.1"
,
"port"
:
17765
,
"protocol"
:
"http"
},
"auth"
:
{
"enabled"
:
False
,
"tokens"
:
[]},
"dashboard"
:
{
"username"
:
"admin"
,
"password"
:
"hash"
},
"internal_model"
:
{
"condensation_model_id"
:
"internal-condense"
,
"autoselect_model_id"
:
"internal-autoselect"
,
"semantic_vectorization"
:
"sentence-transformers/all-MiniLM-L6-v2"
,
},
"database"
:
{
"type"
:
"sqlite"
,
"sqlite_path"
:
"~/.aisbf/aisbf.db"
,
"mysql_host"
:
"localhost"
,
"mysql_port"
:
3306
,
"mysql_user"
:
"aisbf"
,
"mysql_password"
:
""
,
"mysql_database"
:
"aisbf"
},
"cache"
:
{
"type"
:
"file"
,
"redis_host"
:
"localhost"
,
"redis_port"
:
6379
,
"redis_db"
:
0
,
"redis_password"
:
""
,
"redis_key_prefix"
:
"aisbf:"
},
"response_cache"
:
{
"enabled"
:
True
,
"backend"
:
"memory"
,
"ttl"
:
600
,
"max_memory_cache"
:
1000
,
"redis_host"
:
"localhost"
,
"redis_port"
:
6379
,
"redis_db"
:
0
,
"redis_password"
:
""
,
"redis_key_prefix"
:
"aisbf:response:"
,
"sqlite_path"
:
"~/.aisbf/response_cache.db"
,
"mysql_host"
:
"localhost"
,
"mysql_port"
:
3306
,
"mysql_user"
:
"aisbf"
,
"mysql_password"
:
""
,
"mysql_database"
:
"aisbf_response_cache"
},
"mcp"
:
{
"enabled"
:
False
,
"autoselect_tokens"
:
[],
"fullconfig_tokens"
:
[]},
"tor"
:
{
"enabled"
:
False
,
"control_port"
:
9051
,
"control_host"
:
"127.0.0.1"
,
"control_password"
:
None
,
"hidden_service_dir"
:
None
,
"hidden_service_port"
:
80
,
"socks_port"
:
9050
,
"socks_host"
:
"127.0.0.1"
},
"signup"
:
{
"enabled"
:
False
,
"require_email_verification"
:
False
,
"verification_token_expiry_hours"
:
24
},
"smtp"
:
{
"enabled"
:
False
,
"host"
:
""
,
"port"
:
587
,
"username"
:
""
,
"password"
:
""
,
"use_tls"
:
True
,
"use_ssl"
:
False
,
"from_email"
:
""
,
"from_name"
:
"AISBF"
},
"oauth2"
:
{
"google"
:
{
"enabled"
:
False
,
"client_id"
:
""
,
"client_secret"
:
""
},
"github"
:
{
"enabled"
:
False
,
"client_id"
:
""
,
"client_secret"
:
""
}},
"batching"
:
{
"enabled"
:
False
,
"window_ms"
:
100
,
"max_batch_size"
:
8
,
"provider_settings"
:
{
"openai"
:
{
"enabled"
:
False
,
"max_batch_size"
:
10
},
"anthropic"
:
{
"enabled"
:
False
,
"max_batch_size"
:
5
}}},
"adaptive_rate_limiting"
:
{
"enabled"
:
False
,
"initial_rate_limit"
:
0
,
"learning_rate"
:
0.1
,
"headroom_percent"
:
10
,
"recovery_rate"
:
0.05
,
"max_rate_limit"
:
60
,
"min_rate_limit"
:
0.1
,
"backoff_base"
:
2
,
"jitter_factor"
:
0.25
,
"history_window"
:
3600
,
"consecutive_successes_for_recovery"
:
10
},
"client_rate_limiting"
:
{
"enabled"
:
False
,
"api"
:
{
"requests_per_minute"
:
60
,
"requests_per_hour"
:
1000
},
"general"
:
{
"requests_per_minute"
:
120
,
"requests_per_hour"
:
3000
}},
}))
home_cfg_path
=
Path
.
home
()
/
".aisbf"
/
"aisbf.json"
original_home
=
home_cfg_path
.
read_text
()
if
home_cfg_path
.
exists
()
else
None
monkeypatch
.
setattr
(
dashboard_settings
,
"get_aisbf_config_path"
,
lambda
:
resolved_cfg_path
)
monkeypatch
.
setattr
(
dashboard_settings
,
"_reload_global_config"
,
lambda
:
None
)
class
TemplateStub
:
def
TemplateResponse
(
self
,
*
args
,
**
kwargs
):
from
starlette.responses
import
Response
return
Response
(
status_code
=
200
)
monkeypatch
.
setattr
(
dashboard_settings
,
"_templates"
,
TemplateStub
())
client
=
TestClient
(
app
)
_login_as_admin
(
client
)
response
=
client
.
post
(
"/dashboard/settings"
,
data
=
{
"host"
:
"127.0.0.1"
,
"port"
:
17765
,
"protocol"
:
"http"
,
"auth_enabled"
:
""
,
"auth_tokens"
:
""
,
"dashboard_username"
:
"admin"
,
"condensation_model_id"
:
"internal-condense"
,
"autoselect_model_id"
:
"internal-autoselect"
,
"autoselect_max_tokens"
:
8000
,
"condensation_max_tokens"
:
1000
,
"autoselect_max_new_tokens"
:
100
,
"nsfw_classifier"
:
"michelleli99/NSFW_text_classifier"
,
"privacy_classifier"
:
"iiiorg/piiranha-v1-detect-personal-information"
,
"semantic_vectorization"
:
"sentence-transformers/all-MiniLM-L6-v2"
,
"feature_nsfw_classification_mode"
:
"enabled"
,
"feature_privacy_classification_mode"
:
"disabled"
,
"feature_context_condensation_mode"
:
"enabled"
,
"feature_response_cache_mode"
:
"disabled"
,
"feature_prompt_batching_mode"
:
"enabled"
,
"feature_prompt_security_mode"
:
"enabled"
,
"feature_context_lens_mode"
:
"enabled"
,
"feature_block_high_risk_prompts_mode"
:
"disabled"
,
"feature_persist_prompt_text_mode"
:
"enabled"
,
"feature_redact_before_persist_mode"
:
"disabled"
,
"feature_risk_threshold"
:
"medium"
,
"batching_window_ms"
:
100
,
"batching_max_batch_size"
:
8
,
"batching_openai_max_batch_size"
:
10
,
"batching_anthropic_max_batch_size"
:
5
,
"adaptive_initial_rate_limit"
:
0
,
"adaptive_learning_rate"
:
0.1
,
"adaptive_headroom_percent"
:
10
,
"adaptive_recovery_rate"
:
0.05
,
"adaptive_max_rate_limit"
:
60
,
"adaptive_min_rate_limit"
:
0.1
,
"adaptive_backoff_base"
:
2
,
"adaptive_jitter_factor"
:
0.25
,
"adaptive_history_window"
:
3600
,
"adaptive_consecutive_successes"
:
10
,
"active_tab"
:
"classification"
,
"database_type"
:
"sqlite"
,
"sqlite_path"
:
"~/.aisbf/aisbf.db"
,
"mysql_host"
:
"localhost"
,
"mysql_port"
:
3306
,
"mysql_user"
:
"aisbf"
,
"mysql_password"
:
""
,
"mysql_database"
:
"aisbf"
,
"cache_type"
:
"file"
,
"redis_host"
:
"localhost"
,
"redis_port"
:
6379
,
"redis_db"
:
0
,
"redis_password"
:
""
,
"redis_key_prefix"
:
"aisbf:"
,
"response_cache_backend"
:
"memory"
,
"response_cache_ttl"
:
600
,
"response_cache_max_memory"
:
1000
,
"response_cache_redis_host"
:
"localhost"
,
"response_cache_redis_port"
:
6379
,
"response_cache_redis_db"
:
0
,
"response_cache_redis_password"
:
""
,
"response_cache_redis_key_prefix"
:
"aisbf:response:"
,
"response_cache_sqlite_path"
:
"~/.aisbf/response_cache.db"
,
"response_cache_mysql_host"
:
"localhost"
,
"response_cache_mysql_port"
:
3306
,
"response_cache_mysql_user"
:
"aisbf"
,
"response_cache_mysql_password"
:
""
,
"response_cache_mysql_database"
:
"aisbf_response_cache"
,
"autoselect_tokens"
:
""
,
"fullconfig_tokens"
:
""
,
"tor_control_port"
:
9051
,
"tor_control_host"
:
"127.0.0.1"
,
"tor_control_password"
:
""
,
"tor_hidden_service_dir"
:
""
,
"tor_hidden_service_port"
:
80
,
"tor_socks_port"
:
9050
,
"tor_socks_host"
:
"127.0.0.1"
,
"verification_token_expiry"
:
24
,
"smtp_host"
:
""
,
"smtp_port"
:
587
,
"smtp_username"
:
""
,
"smtp_password"
:
""
,
"smtp_from_email"
:
""
,
"smtp_from_name"
:
"AISBF"
,
"oauth2_google_client_id"
:
""
,
"oauth2_google_client_secret"
:
""
,
"oauth2_github_client_id"
:
""
,
"oauth2_github_client_secret"
:
""
,
"dashboard_email"
:
""
,
"new_admin_password"
:
""
,
"confirm_admin_password"
:
""
,
"client_rl_api_rpm"
:
60
,
"client_rl_api_rph"
:
1000
,
"client_rl_general_rpm"
:
120
,
"client_rl_general_rph"
:
3000
,
},
follow_redirects
=
False
,
)
assert
response
.
status_code
==
200
saved
=
json
.
loads
(
resolved_cfg_path
.
read_text
())
assert
saved
[
"feature_controls"
][
"prompt_security"
][
"security_scan"
][
"mode"
]
==
"enabled"
if
original_home
is
None
:
if
home_cfg_path
.
exists
():
home_cfg_path
.
unlink
()
else
:
home_cfg_path
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
home_cfg_path
.
write_text
(
original_home
)
def
test_config_reload_reads_feature_controls_from_resolved_config_path
(
tmp_path
):
custom_dir
=
tmp_path
/
"custom-config"
custom_dir
.
mkdir
()
previous_config_dir
=
os
.
environ
.
get
(
"AISBF_CONFIG_DIR"
)
(
custom_dir
/
"providers.json"
)
.
write_text
(
json
.
dumps
({
"providers"
:
{}}))
(
custom_dir
/
"rotations.json"
)
.
write_text
(
json
.
dumps
({
"rotations"
:
{}}))
(
custom_dir
/
"autoselect.json"
)
.
write_text
(
json
.
dumps
({}))
(
custom_dir
/
"aisbf.json"
)
.
write_text
(
json
.
dumps
({
"server"
:
{
"host"
:
"127.0.0.1"
,
"port"
:
17765
,
"protocol"
:
"http"
},
"auth"
:
{
"enabled"
:
False
,
"tokens"
:
[]},
"dashboard"
:
{
"username"
:
"admin"
,
"password"
:
"hash"
},
"internal_model"
:
{
"condensation_model_id"
:
"internal-condense"
,
"autoselect_model_id"
:
"internal-autoselect"
,
"semantic_vectorization"
:
"sentence-transformers/all-MiniLM-L6-v2"
},
"feature_controls"
:
{
"nsfw_classification"
:
{
"mode"
:
"enabled"
},
"privacy_classification"
:
{
"mode"
:
"disabled"
},
"context_condensation"
:
{
"mode"
:
"enabled"
},
"response_cache"
:
{
"mode"
:
"enabled"
},
"prompt_batching"
:
{
"mode"
:
"enabled"
},
"prompt_security"
:
{
"security_scan"
:
{
"mode"
:
"enabled"
},
"context_lens"
:
{
"mode"
:
"enabled"
},
"block_high_risk_prompts"
:
{
"mode"
:
"disabled"
},
"persist_prompt_text"
:
{
"mode"
:
"enabled"
},
"redact_before_persist"
:
{
"mode"
:
"disabled"
},
"risk_threshold"
:
"medium"
}
},
"response_cache"
:
{
"enabled"
:
False
,
"backend"
:
"memory"
,
"ttl"
:
600
,
"max_memory_cache"
:
1000
},
"batching"
:
{
"enabled"
:
False
,
"window_ms"
:
100
,
"max_batch_size"
:
8
,
"provider_settings"
:
{}},
"adaptive_rate_limiting"
:
{
"enabled"
:
False
},
"client_rate_limiting"
:
{
"enabled"
:
False
,
"api"
:
{
"requests_per_minute"
:
60
,
"requests_per_hour"
:
1000
},
"general"
:
{
"requests_per_minute"
:
120
,
"requests_per_hour"
:
3000
}}
}))
os
.
environ
[
"AISBF_CONFIG_DIR"
]
=
str
(
custom_dir
)
try
:
cfg
=
Config
()
assert
cfg
.
resolve_feature_enabled
(
"nsfw_classification"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"privacy_classification"
)
is
False
assert
cfg
.
resolve_feature_enabled
(
"context_condensation"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"response_cache"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"prompt_batching"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"prompt_security"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"context_lens"
)
is
True
assert
cfg
.
resolve_feature_enabled
(
"block_high_risk_prompts"
)
is
False
finally
:
if
previous_config_dir
is
None
:
os
.
environ
.
pop
(
"AISBF_CONFIG_DIR"
,
None
)
else
:
os
.
environ
[
"AISBF_CONFIG_DIR"
]
=
previous_config_dir
tests/test_studio_sidebar_and_coderai_catalog.py
0 → 100644
View file @
ce6a2513
import
json
from
aisbf.coderai_broker
import
CoderAIBroker
def
test_coderai_broker_send_request_falls_back_to_client_id_named_provider_snapshot
(
tmp_path
):
broker
=
CoderAIBroker
()
broker
.
_state_path
=
tmp_path
/
"coderai_broker_sessions.json"
session_key
=
broker
.
_session_meta_key
(
"zeiss-nvidia"
,
"zeiss-nvidia"
)
broker
.
_cache
.
broker_set
(
session_key
,
{
"session_id"
:
"sess-1"
,
"provider_id"
:
"actual-coderai-provider"
,
"client_id"
:
"zeiss-nvidia"
,
"closed"
:
False
,
"metadata"
:
{
"owner_user_id"
:
None
},
},
ttl
=
120
,
)
import
asyncio
async
def
_run
():
task
=
asyncio
.
create_task
(
broker
.
send_request
(
"zeiss-nvidia"
,
"models.list"
,
{},
client_id
=
"zeiss-nvidia"
,
owner_user_id
=
None
,
timeout
=
0.01
)
)
await
asyncio
.
sleep
(
0
)
pending
=
next
(
iter
(
broker
.
_pending
.
values
()))
assert
pending
.
request_snapshot
[
"provider_id"
]
==
"actual-coderai-provider"
task
.
cancel
()
try
:
await
task
except
BaseException
:
pass
asyncio
.
run
(
_run
())
def
test_studio_sidebar_search_input_declares_stable_search_key
():
studio_js
=
open
(
"/working/aisbf/static/dashboard/studio.js"
,
"r"
,
encoding
=
"utf-8"
)
.
read
()
assert
"data-search-key"
in
studio_js
assert
"setSelectionRange"
in
studio_js
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