ren worker folder adding orig, mv new one into root
This commit is contained in:
@@ -82,8 +82,23 @@ multi_agent_v2 = false
|
||||
memories = false
|
||||
external_agent_memory_import = false
|
||||
"""
|
||||
# A provider of our own, not the built-in `openai`: codex reserves built-in provider ids,
|
||||
# and env_http_headers — the only place codex can be told to send the call-origin header —
|
||||
# is a per-provider setting. base_url has to live in the table with it (a provider without
|
||||
# one silently falls back to api.openai.com), so harness_refresh_config_keys refreshes
|
||||
# [model_providers.*] keys as well as root ones.
|
||||
container_config = """
|
||||
openai_base_url = "${OPENAI_BASE_URL}"
|
||||
model_provider = "llm-proxy"
|
||||
|
||||
[model_providers.llm-proxy]
|
||||
name = "LLM proxy"
|
||||
base_url = "${OPENAI_BASE_URL}"
|
||||
# A custom provider reads its key from this env var and never from auth.json, so it
|
||||
# names the one key .env actually carries. That makes the key live per launch rather
|
||||
# than baked at container create — better than the auth-file path it replaces.
|
||||
env_key = "ANTHROPIC_API_KEY"
|
||||
wire_api = "responses"
|
||||
env_http_headers = { "X-Surge-Client-Metadata" = "LLM_CALL_METADATA" }
|
||||
"""
|
||||
explore_config = """
|
||||
[hooks]
|
||||
|
||||
@@ -190,16 +190,39 @@ if [m for m in re.finditer(r"\$\{(\w+)\}", text) if not os.environ.get(m.group(1
|
||||
raise SystemExit(1)
|
||||
text = os.path.expandvars(text)
|
||||
|
||||
# Root keys, plus keys inside a [model_providers.*] table: codex reserves its built-in
|
||||
# provider ids, so the proxy URL it must follow lives in a provider table, not at the
|
||||
# root. Every other table, [hooks] on the explore surface included, is left alone.
|
||||
REFRESHABLE_TABLE = re.compile(r"\[model_providers\.[^]]+\]$")
|
||||
wanted = []
|
||||
section = None
|
||||
for line in text.splitlines():
|
||||
if line.lstrip().startswith("["):
|
||||
break
|
||||
m = re.match(r"\s*([A-Za-z0-9_-]+)\s*=", line)
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("["):
|
||||
section = stripped if REFRESHABLE_TABLE.match(stripped) else False
|
||||
continue
|
||||
if section is False:
|
||||
continue
|
||||
m = re.match(r"\s*\"?([A-Za-z0-9_.-]+)\"?\s*=", line)
|
||||
if m:
|
||||
wanted.append((m.group(1), line.rstrip()))
|
||||
wanted.append((section, m.group(1), line.rstrip()))
|
||||
if not wanted:
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def section_path(header):
|
||||
"""[model_providers.llm-proxy] -> ("model_providers", "llm-proxy")."""
|
||||
return tuple(header.strip("[]").split("."))
|
||||
|
||||
|
||||
def lookup(doc, header, key):
|
||||
"""The value a parsed config holds for a wanted key, or KeyError."""
|
||||
node = doc
|
||||
if header:
|
||||
for part in section_path(header):
|
||||
node = node[part]
|
||||
return node[key]
|
||||
|
||||
mode = None
|
||||
if os.path.exists(target):
|
||||
try:
|
||||
@@ -208,23 +231,52 @@ if os.path.exists(target):
|
||||
mode = os.stat(target).st_mode & 0o777
|
||||
except OSError:
|
||||
raise SystemExit(1)
|
||||
# Everything from the first table header on belongs to a table. A key appended after
|
||||
# one is reparented into it, so both the search and the insert stay above the line.
|
||||
root_end = next((i for i, l in enumerate(lines) if l.lstrip().startswith("[")), len(lines))
|
||||
changed = False
|
||||
for key, line in wanted:
|
||||
# The quoted spelling is the same key: replacing it beats adding a duplicate.
|
||||
pat = re.compile(r"\s*\"?" + re.escape(key) + r"\"?\s*=")
|
||||
at = next((i for i in range(root_end) if pat.match(lines[i])), None)
|
||||
def span(header):
|
||||
"""The line range a section owns, or None when the file has no such section.
|
||||
|
||||
Root is everything above the first table header: a key appended below one
|
||||
would be reparented into it, so searches and inserts stay inside the span.
|
||||
"""
|
||||
heads = [i for i, l in enumerate(lines) if l.lstrip().startswith("[")]
|
||||
if header is None:
|
||||
return 0, (heads[0] if heads else len(lines))
|
||||
at = next((i for i in heads if lines[i].strip() == header), None)
|
||||
if at is None:
|
||||
if root_end < len(lines) and lines[root_end].strip():
|
||||
lines.insert(root_end, "")
|
||||
lines.insert(root_end, line)
|
||||
root_end += 1
|
||||
changed = True
|
||||
elif lines[at] != line:
|
||||
lines[at] = line
|
||||
return None
|
||||
after = next((i for i in heads if i > at), len(lines))
|
||||
return at + 1, after
|
||||
|
||||
# Grouped, root first, so a section this file lacks can be written whole.
|
||||
grouped = {}
|
||||
for header, key, line in wanted:
|
||||
grouped.setdefault(header, []).append((key, line))
|
||||
ordered = sorted(grouped, key=lambda h: (h is not None, h or ""))
|
||||
|
||||
changed = False
|
||||
for header in ordered:
|
||||
if span(header) is None:
|
||||
# A config written before this section existed. Write the whole table
|
||||
# rather than leave a root key naming a provider that is not there.
|
||||
if lines and lines[-1].strip():
|
||||
lines.append("")
|
||||
lines.append(header)
|
||||
lines.extend(line for _, line in grouped[header])
|
||||
changed = True
|
||||
continue
|
||||
for key, line in grouped[header]:
|
||||
# Re-read the span: an insert for an earlier key moved it.
|
||||
start, end = span(header)
|
||||
# The quoted spelling is the same key: replace rather than duplicate.
|
||||
pat = re.compile(r"\s*\"?" + re.escape(key) + r"\"?\s*=")
|
||||
at = next((i for i in range(start, end) if pat.match(lines[i])), None)
|
||||
if at is None:
|
||||
if end < len(lines) and lines[end].strip():
|
||||
lines.insert(end, "")
|
||||
lines.insert(end, line)
|
||||
changed = True
|
||||
elif lines[at] != line:
|
||||
lines[at] = line
|
||||
changed = True
|
||||
if not changed:
|
||||
raise SystemExit(0)
|
||||
out = "\n".join(lines).rstrip("\n") + "\n"
|
||||
@@ -238,9 +290,22 @@ try:
|
||||
except tomllib.TOMLDecodeError:
|
||||
raise SystemExit(1)
|
||||
# Parsing is not enough: a line edit can land inside a multi-line value, which still
|
||||
# parses while leaving the key unset. Require every key to have reached the root.
|
||||
if doc != {**doc, **tomllib.loads("\n".join(line for _, line in wanted))}:
|
||||
raise SystemExit(1)
|
||||
# parses while leaving the key unset. Require every key to have landed on the value the
|
||||
# blob asks for, in its own section — skipping sections this file does not carry.
|
||||
blob_doc = tomllib.loads(text)
|
||||
for header, key, _ in wanted:
|
||||
try:
|
||||
expected = lookup(blob_doc, header, key)
|
||||
except (KeyError, TypeError):
|
||||
raise SystemExit(1)
|
||||
try:
|
||||
got = lookup(doc, header, key)
|
||||
except (KeyError, TypeError):
|
||||
if header is None:
|
||||
raise SystemExit(1)
|
||||
continue
|
||||
if got != expected:
|
||||
raise SystemExit(1)
|
||||
|
||||
# Pid-suffixed: two launches at once must not write the same scratch path.
|
||||
tmp = target + ".raccoon-tmp." + str(os.getpid())
|
||||
|
||||
@@ -34,4 +34,21 @@ _scripts_dir="${HARNESS_SCRIPTS_DIR:-/workspace/scripts}"
|
||||
|
||||
# No args is a valid call: refresh only, for a lifecycle hook.
|
||||
[ "$#" -gt 0 ] || exit 0
|
||||
|
||||
# Outside the subshell, because these have to reach the exec'd command: codex now reads
|
||||
# its key from $ANTHROPIC_API_KEY per request, and a non-login shell sourced neither
|
||||
# .bashrc (the key, the call origin) nor the profile that puts the CLI on PATH.
|
||||
# Failures stay swallowed — an unreadable .env must not stop the agent starting.
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
if [ -f "${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "${RACCOON_ENV_FILE:-/workspace/.env}" 2>/dev/null || true
|
||||
set +a
|
||||
fi
|
||||
if [ -f "$HOME/.raccoon-call-origin" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. "$HOME/.raccoon-call-origin" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -151,6 +151,19 @@ harness_install_launchers() {
|
||||
#!/bin/bash
|
||||
# GENERATED by scripts/setup-harnesses.sh from harness-registry.toml — do not edit.
|
||||
set -euo pipefail
|
||||
# A harness that reads its key from \$ENV per request needs .env in its environment,
|
||||
# and only an interactive shell sources .bashrc — which is also where PATH picks up
|
||||
# ~/.local/bin, where the CLI itself lives. Both are set here so a launch works the
|
||||
# same either way, with the key .env holds right now.
|
||||
export PATH="\$HOME/.local/bin:\$PATH"
|
||||
if [ -f "\${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
|
||||
set -a
|
||||
. "\${RACCOON_ENV_FILE:-/workspace/.env}"
|
||||
set +a
|
||||
fi
|
||||
if [ -f "\$HOME/.raccoon-call-origin" ]; then
|
||||
. "\$HOME/.raccoon-call-origin"
|
||||
fi
|
||||
if [ -f "$note_src" ]; then
|
||||
RACCOON_TOOLSET_NOTE="\$(sed "s#/opt/agent-cli#$agent_cli_dir#g" "$note_src")"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user