OUTPUT #691
PARÇA 5 / 5
TOPLAM: 167636 karakter | 2901 satır
BU PARÇA: 7636 karakter
ENIED
82-# (what a held *target* handle actually reports — measured: a plain reader yields 5, NOT 32),
83-# 32 ERROR_SHARING_VIOLATION (the *source* temp file is held) or 33 ERROR_LOCK_VIOLATION (byte-range
84-# lock on the target). Ambiguous (a real ACL denial is also 5), so recovery is bounded and a
85-# still-failing write is re-raised unchanged rather than classified up front.
86-_WINDOWS_CONTENDED_REPLACE_ERRORS = frozenset({5, 32, 33})
87:# Retry budget for the atomic rename. A rename that wins here keeps the write fully atomic, so the
88-# budget covers a realistic hold (desktop auth-init holds auth.json >100 ms): ~200 ms recovered
89-# atomically, ~310 ms worst case. The cap matters as much as the count — gateway_state.json is
90-# rewritten every turn, so a permanently-held target pays the full budget per write. Jittered so
91:# concurrent writers don't retry in lockstep.
92:_REPLACE_RETRY_ATTEMPTS = 4
93:_REPLACE_RETRY_BASE_DELAY_S = 0.02
94:_REPLACE_RETRY_MAX_DELAY_S = 0.1
95-_CROSS_DEVICE_ERRNOS = (errno.EXDEV, errno.EBUSY)
96-
97-
98-def _is_contended_windows_replace_error(exc: OSError) -> bool:
99- """Candidate-only: winerror 5 also covers a genuine ACL denial."""
100- return _IS_WINDOWS and getattr(exc, "winerror", None) in _WINDOWS_CONTENDED_REPLACE_ERRORS
--
119- os.fsync(fd)
120- finally:
121- os.close(fd)
122- os.unlink(tmp_str)
123-
124-
125:def _copy_fallback(tmp_str: str, real_path: str) -> None:
126: """Copy/fsync/unlink fallback for cross-device and bind-mount renames."""
127- shutil.copyfile(tmp_str, real_path)
128- with suppress(OSError):
129- shutil.copystat(tmp_str, real_path)
130- with suppress(OSError), open(real_path, "rb") as f:
131- os.fsync(f.fileno())
132- os.unlink(tmp_str)
--
135-def atomic_replace(tmp_path: Union[str, Path], target: Union[str, Path]) -> str:
136- """Atomically move *tmp_path* onto *target*, preserving symlinks.
137-
138- Resolves a symlink first so ``os.replace`` writes the real file in place and the symlink
139- survives. Otherwise identical to ``os.replace`` unless the rename fails with EXDEV/EBUSY
140- (cross-device, bind-mount, busy file: copy/fsync/unlink immediately — these never clear on
141: retry) or a Windows rename contended by another open handle (winerror 5/32/33: bounded retry,
142- then in-place rewrite).
143- """
144- target_str = str(target)
145- real_path = os.path.realpath(target_str) if os.path.islink(target_str) else target_str
146- tmp_str = str(tmp_path)
147- try:
--
150- except OSError as exc:
151- contended = _is_contended_windows_replace_error(exc)
152- if exc.errno not in _CROSS_DEVICE_ERRNOS and not contended:
153- raise
154- if contended:
155- # Lazy: keeps ``utils`` free of a package-level dependency on ``agent``.
156: from agent.retry_utils import jittered_backoff
157: for attempt in range(1, _REPLACE_RETRY_ATTEMPTS + 1):
158: time.sleep(jittered_backoff(attempt, base_delay=_REPLACE_RETRY_BASE_DELAY_S, max_delay=_REPLACE_RETRY_MAX_DELAY_S))
159- try:
160- os.replace(tmp_str, real_path)
161- return real_path
162: except OSError as retry_exc:
163: exc = retry_exc
164: if retry_exc.errno in _CROSS_DEVICE_ERRNOS:
165: contended = False # not contention after all — stop burning the budget
166- break
167: if not _is_contended_windows_replace_error(retry_exc):
168- raise
169- logger.debug("atomic_replace: %s -> %s failed with %s; falling back to %s", tmp_str, real_path,
170- getattr(exc, "winerror", None) or errno.errorcode.get(exc.errno or 0, exc.errno),
171- "in-place rewrite" if contended else "copy")
172- # The rewrite re-raises its own error, so an ACL denial is reported as such, not as contention.
173: (_rewrite_in_place if contended else _copy_fallback)(tmp_str, real_path)
174- return real_path
175-
176-
177-def _atomic_write(path: Path, write, *, prefix: str, encoding: str = "utf-8", mode: "int | None" = None, preserve_owner: bool = True) -> None:
178- """Temp file + fsync + :func:`atomic_replace`, then re-apply owner/mode.
179-
--
298- """Update one dotted YAML key while preserving comments, ordering, quoting and Unicode.
299-
300- Narrower than :func:`atomic_yaml_write` on purpose: for user-edited config files where a
301- single setting mutation must not disturb the rest. Still writes via temp file + atomic replace.
302- """
303- from ruamel.yaml.comments import CommentedMap
304: # Honor escaped dots and prefer existing literal dotted keys (model IDs like ``glm-5.3``) over
305- # blind splitting — same navigation as ``hermes config set``'s ``_set_nested``; otherwise
306: # /model + TUI persistence wrote ``glm-5: {'3': ...}`` phantom siblings.
307- # See #91607.
308- from hermes_cli.config import _greedy_literal_match, _split_key_path
309-
310- path = Path(path)
311- path.parent.mkdir(parents=True, exist_ok=True)
312- yaml_rt, config = _roundtrip_load(path)
--
328- _roundtrip_dump(path, yaml_rt, config)
329-
330-
331-# ruamel's round-trip dumper resolves plain scalars under YAML 1.2, where only true/false/null are
332-# reserved — so a str like "off" or "yes" is emitted unquoted. Every other config reader here
333-# (PyYAML, yaml.safe_load sites) parses under YAML 1.1, where on/off/yes/no are booleans: an
334:# unquoted ``approvals.mode: off`` would silently round-trip back as ``False``.
335-_YAML11_AMBIGUOUS_WORDS = frozenset({"y", "n", "yes", "no", "true", "false", "on", "off", "null", "~"})
336-
337-
338-def atomic_roundtrip_yaml_save(path: Union[str, Path], new_state: dict) -> None:
339- """Persist a full config-state dict while preserving comments and ordering.
340-
--
397- return cast(raw) if raw else default
398- except (ValueError, TypeError):
399- return default
400-
401-
402-def env_int(key: str, default: int = 0) -> int:
403: """Read an environment variable as an integer, with fallback."""
404- return _env_number(key, default, int)
405-
406-
407-def env_float(key: str, default: float = 0.0) -> float:
408: """Read an environment variable as a float, with fallback."""
409- return _env_number(key, default, float)
410-
411-
412-def env_bool(key: str, default: bool = False) -> bool:
413- """Read an environment variable as a boolean."""
414- return is_truthy_value(os.getenv(key, ""), default=default)
--
451- ``https://api.openai.com.example/v1`` or ``https://proxy.test/api.openai.com/v1`` would
452- otherwise pass as native endpoints and mis-route api_mode and auth.
453- """
454- return _hostname_of(_parse_base_url(base_url))
455-
456-
457:def model_forces_max_completion_tokens(model: str) -> bool:
458- """True for OpenAI families that reject ``max_tokens`` (HTTP 400 ``unsupported_parameter``)."""
459: m = (model or "").strip().lower().rsplit("/", 1)[-1]
460- return m.startswith(("gpt-4o", "gpt-4.1", "gpt-5", "o1", "o3", "o4"))
461-
462-
463-def base_url_origin(base_url: str) -> tuple[str, str, int]:
464- """``(scheme, hostname, effective_port)`` for a base URL; ``("", "", 0)`` on no host/bad port.
465-
root@213-238-170-219:~#