| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- """Prepare narrowly scoped repairs for Hermes' multi-Profile session routing.
- The installer owns backup/activation/rollback. This module never writes files.
- Unsupported source layouts fail before anything is changed.
- """
- import ast
- from pathlib import Path
- def _function(source, name):
- matches = [node for node in ast.parse(source).body
- if isinstance(node, ast.FunctionDef) and node.name == name]
- if len(matches) != 1:
- raise ValueError(f'Hermes 接口 {name} 不兼容,未修改核心文件')
- node = matches[0]
- lines = source.splitlines(keepends=True)
- return node, ''.join(lines[node.lineno - 1:node.end_lineno])
- def _replace_function(source, name, transform):
- node, body = _function(source, name)
- lines = source.splitlines(keepends=True)
- return ''.join(lines[:node.lineno - 1]) + transform(body) + ''.join(lines[node.end_lineno:])
- def _config_first(body):
- marker = '# Yaoyao: each Profile owns its configured model.'
- if marker in body:
- return body
- old = ' if env := _env_model_seed():\n return env\n m = _load_cfg().get("model", "")\n'
- empty_model = ' if isinstance(m, dict):\n return str(m.get("default", "") or "").strip()\n'
- anchor = ' # No env seed / config preference:'
- if body.count(old) != 1 or body.count(anchor) != 1 or body.count(empty_model) != 1:
- raise ValueError('Hermes 模型解析接口已改变,未修改核心文件')
- body = body.replace(old, f' {marker}\n m = _load_cfg().get("model", "")\n')
- body = body.replace(empty_model, ' if isinstance(m, dict):\n m = str(m.get("default", "") or "").strip()\n')
- return body.replace(anchor, ' if env := _env_model_seed():\n return env\n' + anchor)
- def _provider_from_profile(body):
- marker = '# Yaoyao: launch-model/provider seeds cannot replace a configured Profile.'
- if marker in body:
- return body
- anchor = ' model = _resolve_model()\n'
- if body.count(anchor) != 1:
- raise ValueError('Hermes Provider 解析接口已改变,未修改核心文件')
- return body.replace(anchor, f' {marker}\n'
- ' configured_model, configured_provider = _config_model_target()\n'
- ' if configured_model:\n'
- ' return configured_model, configured_provider or None\n' + anchor)
- def _scope_session_metadata(source):
- for method in ('session.create', 'session.resume'):
- needle = f'@method("{method}")\n'
- if source.count(needle) != 1:
- raise ValueError(f'Hermes {method} 接口已改变,未修改核心文件')
- if needle + '@_profile_scoped\n' not in source:
- source = source.replace(needle, needle + '@_profile_scoped\n')
- return source
- def prepare(source_root: Path):
- """Return (path, original, replacement) tuples, including no-op entries."""
- files = [(source_root / 'tui_gateway' / 'server.py', lambda text:
- _replace_function(_replace_function(text, '_resolve_model', _config_first),
- '_resolve_startup_runtime', _provider_from_profile)),
- (source_root / 'tui_gateway' / 'methods_session.py', _scope_session_metadata)]
- changes = []
- for path, transform in files:
- if not path.is_file() or path.is_symlink():
- raise ValueError('未找到可修复的 Hermes 核心文件')
- original = path.read_bytes()
- repaired = transform(original.decode('utf-8'))
- compile(repaired, str(path), 'exec')
- changes.append((path, original, repaired.encode('utf-8')))
- return changes
|