fix(codex-bridge): strip a stray ide/ prefix from stored model names
A hand- or agent-edited jev-settings.json could store a model name that already carries the gateway's own "ide/" prefix. The bridge adds "ide/" again when it routes, producing "ide/ide/<model>" — an unknown model whose context window the bridge reads as the 32768 default and then silently drops the request, answering "." with a wrong "model seçildi" instead of the chosen model. jev_settings.read()/_policy() now normalize every stored model name (fallbackModel, watchModels, exemptModels, prefilter.target, autoRoute.model/targets) by stripping a leading "ide/". No real provider/model id begins with "ide/", so valid settings are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
86a6621936
commit
65ce4be58b
2 files changed
+60
No files matched your search
@@ -59,6 +59,42 @@ def _https(value):
|
|||||||
return url.scheme == 'https' and bool(url.netloc) and not url.username and not url.password
|
return url.scheme == 'https' and bool(url.netloc) and not url.username and not url.password
|
||||||
|
|
||||||
|
|
||||||
|
IDE_PREFIX = 'ide/'
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_ide(value):
|
||||||
|
"""A stored model name must never carry the gateway's own 'ide/' prefix; the bridge adds it when
|
||||||
|
it routes. A doubled 'ide/ide/...' makes the bridge treat the name as an unknown model, read its
|
||||||
|
context window as the 32768 default and silently drop the request. No real provider/model id
|
||||||
|
begins with 'ide/', so stripping a leading 'ide/' here can only undo an accidental prefix."""
|
||||||
|
if isinstance(value, str):
|
||||||
|
while value.startswith(IDE_PREFIX):
|
||||||
|
value = value[len(IDE_PREFIX):]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_models(settings):
|
||||||
|
"""Strip a stray 'ide/' prefix from every stored model name so routing can never double it."""
|
||||||
|
if 'fallbackModel' in settings:
|
||||||
|
settings['fallbackModel'] = _strip_ide(settings['fallbackModel'])
|
||||||
|
for key in ('watchModels', 'exemptModels'):
|
||||||
|
values = settings.get(key)
|
||||||
|
if isinstance(values, list):
|
||||||
|
settings[key] = [_strip_ide(x) for x in values]
|
||||||
|
prefilter = settings.get('prefilter')
|
||||||
|
if isinstance(prefilter, dict) and 'target' in prefilter:
|
||||||
|
prefilter['target'] = _strip_ide(prefilter['target'])
|
||||||
|
auto = settings.get('autoRoute')
|
||||||
|
if isinstance(auto, dict):
|
||||||
|
if 'model' in auto:
|
||||||
|
auto['model'] = _strip_ide(auto['model'])
|
||||||
|
targets = auto.get('targets')
|
||||||
|
if isinstance(targets, dict):
|
||||||
|
for key in list(targets):
|
||||||
|
targets[key] = _strip_ide(targets[key])
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def validate(settings):
|
def validate(settings):
|
||||||
"""Raise ValueError when a value could route to the wrong place or wait without bound."""
|
"""Raise ValueError when a value could route to the wrong place or wait without bound."""
|
||||||
# A corrupted file must not silently change routing or contact another host.
|
# A corrupted file must not silently change routing or contact another host.
|
||||||
@@ -115,6 +151,7 @@ def _policy(settings):
|
|||||||
for key in POLICY_KEYS:
|
for key in POLICY_KEYS:
|
||||||
if isinstance(overrides.get(key), dict):
|
if isinstance(overrides.get(key), dict):
|
||||||
merged[key] = _fill(overrides[key], settings[key])
|
merged[key] = _fill(overrides[key], settings[key])
|
||||||
|
_normalize_models(merged)
|
||||||
return validate(merged)
|
return validate(merged)
|
||||||
except (OSError, ValueError, KeyError, TypeError, AttributeError):
|
except (OSError, ValueError, KeyError, TypeError, AttributeError):
|
||||||
return settings
|
return settings
|
||||||
@@ -129,6 +166,7 @@ def read(overlay=True):
|
|||||||
settings = data['settings']
|
settings = data['settings']
|
||||||
for section in SECTIONS:
|
for section in SECTIONS:
|
||||||
settings[section] = _fill(settings[section], DEFAULTS[section]) if section in settings else copy.deepcopy(DEFAULTS[section])
|
settings[section] = _fill(settings[section], DEFAULTS[section]) if section in settings else copy.deepcopy(DEFAULTS[section])
|
||||||
|
_normalize_models(settings)
|
||||||
validate(settings)
|
validate(settings)
|
||||||
return (_policy(settings) if overlay else settings), data.get('protectedKey')
|
return (_policy(settings) if overlay else settings), data.get('protectedKey')
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,28 @@ class Settings(unittest.TestCase):
|
|||||||
self.save(rejectionRules=[{'from': 'A', 'to': 'B'}, {'from': 'B', 'to': 'A'}])
|
self.save(rejectionRules=[{'from': 'A', 'to': 'B'}, {'from': 'B', 'to': 'A'}])
|
||||||
self.assertEqual(jev_denetim.sonrakiler('A'), ['B'])
|
self.assertEqual(jev_denetim.sonrakiler('A'), ['B'])
|
||||||
|
|
||||||
|
def test_stored_ide_prefix_on_model_names_is_stripped(self):
|
||||||
|
# A hand- or agent-edited settings file may store a model name carrying the gateway's own
|
||||||
|
# 'ide/' prefix. The bridge adds 'ide/' again when it routes, so a stored prefix would make
|
||||||
|
# 'ide/ide/...', an unknown model whose context window the bridge mis-reads before silently
|
||||||
|
# dropping the request. read() must strip the stray prefix so routing stays correct.
|
||||||
|
self.save(
|
||||||
|
fallbackModel='ide/abliteration-abliterated-model-large-v2',
|
||||||
|
exemptModels=['ide/claude-claude-opus-5-5', 'agnes-agnes-1.5-flash'],
|
||||||
|
prefilter={**self.settings['prefilter'], 'target': 'ide/ide/abliteration-abliterated-model-large-v2'},
|
||||||
|
)
|
||||||
|
settings, _ = jev_settings.read()
|
||||||
|
self.assertEqual(settings['fallbackModel'], 'abliteration-abliterated-model-large-v2')
|
||||||
|
self.assertEqual(settings['prefilter']['target'], 'abliteration-abliterated-model-large-v2')
|
||||||
|
self.assertEqual(settings['exemptModels'], ['claude-claude-opus-5-5', 'agnes-agnes-1.5-flash'])
|
||||||
|
self.assertTrue(jev_denetim.muaf_mi('claude-claude-opus-5-5'))
|
||||||
|
|
||||||
|
def test_strip_ide_leaves_normal_names_and_removes_only_prefix(self):
|
||||||
|
self.assertEqual(jev_settings._strip_ide('claude-claude-opus-5-5'), 'claude-claude-opus-5-5')
|
||||||
|
self.assertEqual(jev_settings._strip_ide('ide/x'), 'x')
|
||||||
|
self.assertEqual(jev_settings._strip_ide('ide/ide/x'), 'x')
|
||||||
|
self.assertEqual(jev_settings._strip_ide('provider-ide/x'), 'provider-ide/x')
|
||||||
|
|
||||||
def test_prompts_model_criteria_and_thresholds_are_applied(self):
|
def test_prompts_model_criteria_and_thresholds_are_applied(self):
|
||||||
self.save(model='jev-custom', questions={'ret': 'question A', 'sart': 'question B'}, thresholds={'ret': .9, 'sart': .8}, criteria={'ret': {'true': 'bad', 'false': 'good'}, 'sart': {'true': '', 'false': ''}})
|
self.save(model='jev-custom', questions={'ret': 'question A', 'sart': 'question B'}, thresholds={'ret': .9, 'sart': .8}, criteria={'ret': {'true': 'bad', 'false': 'good'}, 'sart': {'true': '', 'false': ''}})
|
||||||
captured = []
|
captured = []
|
||||||
|
|||||||
Reference in new issue
Block a user