From 4237ef0031a9f9eb3db759ebae99d732044755e9 Mon Sep 17 00:00:00 2001 From: SambaNova Sync Bot Date: Mon, 29 Jun 2026 16:27:51 -0500 Subject: [PATCH] Project import generated by Copybara. GitOrigin-RevId: 8711a756aef8d6badc8c2f2dfe9a60d35246ad87 --- .../samba-plugin/.claude-plugin/plugin.json | 2 +- .../src/agent_shims/environment.py | 27 ++---- .../agent_shims/opencode/opencode.json.jinja | 5 +- plugins/samba-plugin/copybara/README.md | 84 +++++++++++++++++++ plugins/samba-plugin/copybara/copy.bara.sky | 70 ++++++++++++++++ plugins/samba-plugin/hooks/ensure_venv.sh | 30 ++----- plugins/samba-plugin/hooks/setup.sh | 4 +- plugins/samba-plugin/mcp_server/bootstrap.sh | 2 +- plugins/samba-plugin/mcp_server/server.py | 35 +------- .../samba-plugin/skills/model-info/SKILL.md | 3 - 10 files changed, 174 insertions(+), 88 deletions(-) create mode 100644 plugins/samba-plugin/copybara/README.md create mode 100644 plugins/samba-plugin/copybara/copy.bara.sky diff --git a/plugins/samba-plugin/.claude-plugin/plugin.json b/plugins/samba-plugin/.claude-plugin/plugin.json index 23d397a..91e587c 100644 --- a/plugins/samba-plugin/.claude-plugin/plugin.json +++ b/plugins/samba-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "sambanova-plugin-cc", - "version": "1.1.2", + "version": "1.1.0", "description": "Delegate various tasks to Sambanova Cloud models.", "author": { "name": "Nathan Zhang", diff --git a/plugins/samba-plugin/agent_shims/src/agent_shims/environment.py b/plugins/samba-plugin/agent_shims/src/agent_shims/environment.py index 7f32302..19a4164 100644 --- a/plugins/samba-plugin/agent_shims/src/agent_shims/environment.py +++ b/plugins/samba-plugin/agent_shims/src/agent_shims/environment.py @@ -15,25 +15,10 @@ def get_sambanova_key(): def get_sambanova_base_url(): """Base URL for the SambaNova-compatible API. - Lets SambaManaged / Stack customers point the plugin at their own - (OpenAI-compatible) deployment. Resolution order: - - 1. SAMBANOVA_BASE_URL (preferred, customer-facing name) - 2. SAMBANOVA_API_OVERRIDE (legacy name, still honored for back-compat) - 3. DEFAULT_SAMBANOVA_BASE_URL (public SambaNova Cloud) - - The value is normalized so callers can safely append paths like "/models": - any trailing slash is stripped, and a trailing "/chat/completions" is - removed too — so a customer who pastes a full endpoint URL - (e.g. "https://api.example.ai/v1/chat/completions") still gets the correct - base ("https://api.example.ai/v1"). + Honors SAMBANOVA_API_OVERRIDE to target a custom (OpenAI-compatible) + endpoint; defaults to the public SambaNova Cloud endpoint. Any trailing + slash is stripped so callers can append paths like "/models". """ - raw = ( - os.environ.get("SAMBANOVA_BASE_URL") - or os.environ.get("SAMBANOVA_API_OVERRIDE") # back-compat - or DEFAULT_SAMBANOVA_BASE_URL - ) - url = raw.rstrip("/") - if url.endswith("/chat/completions"): - url = url[: -len("/chat/completions")].rstrip("/") - return url + return os.environ.get( + "SAMBANOVA_API_OVERRIDE", DEFAULT_SAMBANOVA_BASE_URL + ).rstrip("/") diff --git a/plugins/samba-plugin/agent_shims/src/agent_shims/opencode/opencode.json.jinja b/plugins/samba-plugin/agent_shims/src/agent_shims/opencode/opencode.json.jinja index c5cd170..4c5d2c9 100644 --- a/plugins/samba-plugin/agent_shims/src/agent_shims/opencode/opencode.json.jinja +++ b/plugins/samba-plugin/agent_shims/src/agent_shims/opencode/opencode.json.jinja @@ -21,10 +21,7 @@ "name": "SambaNova", "options": { "baseURL": "{{ base_url }}", - "apiKey": "{env:SAMBANOVA_API_KEY}", - "headers": { - "User-Agent": "sambanova-plugin-cc" - } + "apiKey": "{env:SAMBANOVA_API_KEY}" }, "models": { "{{ model.id }}": { diff --git a/plugins/samba-plugin/copybara/README.md b/plugins/samba-plugin/copybara/README.md new file mode 100644 index 0000000..fb701fa --- /dev/null +++ b/plugins/samba-plugin/copybara/README.md @@ -0,0 +1,84 @@ +# Public mirror sync (Copybara) + +This directory configures the one-way mirror of this plugin to its **public** repo: + +``` +internal: sambanova/sambanova-marketplace : plugins/sambanova-plugin-cc/** (source of truth) +public: sambanova/sambanova-plugin-cc : plugins/samba-plugin/** (mirror, via PR) +``` + +- [`copy.bara.sky`](./copy.bara.sky) — *what* to sync (the Copybara recipe). +- [`../../../.github/workflows/sync-public.yml`](../../../.github/workflows/sync-public.yml) — *when* to sync: + on push to `main` touching this plugin, a daily cron safety net, and a manual button. + +The sync only ever **opens/updates a review PR** (`copybara/sync-main`) on the public repo. +**Publishing requires a human to merge that PR** — automation never publishes directly. + +> Source of truth is internal. Do **not** hand-edit `plugins/samba-plugin/**` in the public +> repo; the next sync would revert it. Make changes here and let them flow out. + +--- + +## One-time setup (before the first automated run) + +1. **Token.** Create a fine-grained PAT (or GitHub App token) with: + - read access to `sambanova/sambanova-marketplace` (origin), and + - `contents: write` + `pull_requests: write` on `sambanova/sambanova-plugin-cc` (destination). + + Store it as an Actions secret named **`OSS_SYNC_TOKEN`** on this repo (or the org). + +2. **Collaborator.** The public repo's `.github/workflows/close-prs.yml` auto-closes PRs from + non-members. Add the token's identity (the sync bot/maintainer) as a **collaborator/member** of + `sambanova/sambanova-plugin-cc`, or its sync PR will be auto-closed. + +3. **Bootstrap the baseline (manual, once).** The public repo already has content and there is no + `GitOrigin-RevId` baseline yet, so the very first sync needs `--force`. Run it by hand: + + ```bash + export GH_TOKEN= + printf 'https://x-access-token:%s@github.com\n' "$GH_TOKEN" > "$HOME/.git-credentials" + + # Dry run first — inspect the (large) first-time reconciliation diff: + docker run --rm -it -e HOME=/root \ + -v "$HOME/.git-credentials":/root/.git-credentials:ro \ + -v "$PWD":/usr/src/app \ + google/copybara copybara \ + plugins/sambanova-plugin-cc/copybara/copy.bara.sky push_to_public --force --dry-run + + # Then for real (opens the first PR): + docker run --rm -it -e HOME=/root \ + -v "$HOME/.git-credentials":/root/.git-credentials:ro \ + -v "$PWD":/usr/src/app \ + google/copybara copybara \ + plugins/sambanova-plugin-cc/copybara/copy.bara.sky push_to_public --force \ + --git-committer-name="SambaNova Sync Bot" \ + --git-committer-email="oss-bot@sambanovasystems.com" + ``` + + Review and **merge** that first PR. After it merges, the `GitOrigin-RevId` baseline lives on the + public `main`, and all subsequent syncs are incremental and fully automated by the workflow + (no `--force`). + +> Expect the **first** PR to be large: the public mirror currently lags behind (script-based, +> v1.0.10) while internal is MCP-server based (v1.1.0). The first sync adds `mcp_server/`, +> `.mcp.json`, rewrites the skills, and removes the stale `cn/` / `scripts/` / `tools/` dirs. +> Also update the public-only `README.md` / `CONTRIBUTING.md` (which describe the old flow) when +> merging that first PR — Copybara does not touch files outside `plugins/samba-plugin/`. + +--- + +## Day-to-day + +Nothing to do. Merging a change into internal `main` under `plugins/sambanova-plugin-cc/**` triggers +the workflow, which refreshes the public sync PR. A maintainer reviews and merges it to publish. + +Validate the config after editing it: + +```bash +docker run --rm -v "$PWD":/usr/src/app google/copybara \ + copybara validate plugins/sambanova-plugin-cc/copybara/copy.bara.sky +``` + +When the internal and public copies legitimately differ in text (e.g. an internal URL that must +become public), encode each difference as a `core.replace` in `copy.bara.sky` so the sync re-applies +it every run instead of reverting it. diff --git a/plugins/samba-plugin/copybara/copy.bara.sky b/plugins/samba-plugin/copybara/copy.bara.sky new file mode 100644 index 0000000..78056ec --- /dev/null +++ b/plugins/samba-plugin/copybara/copy.bara.sky @@ -0,0 +1,70 @@ +# Copybara config — one-way mirror of the SambaNova Claude Code plugin. +# +# Direction: internal monorepo -> public standalone repo (via a reviewable PR) +# origin: github.com/sambanova/sambanova-marketplace @ plugins/sambanova-plugin-cc/** +# destination: github.com/sambanova/sambanova-plugin-cc @ plugins/samba-plugin/** (PR) +# +# This file is the "what to sync" recipe. The "when" is handled by the GitHub Actions +# workflow at .github/workflows/sync-public.yml (on push to main + daily cron + manual). +# +# Internal is the source of truth. Do not hand-edit plugins/samba-plugin/** in the +# public repo — those edits would be reverted on the next sync. + +INTERNAL_URL = "https://github.com/sambanova/sambanova-marketplace.git" +PUBLIC_URL = "https://github.com/sambanova/sambanova-plugin-cc.git" + +# Bot/maintainer identity stamped on the public commit (scrubs internal authorship). +SYNC_AUTHOR = "SambaNova Sync Bot " + +core.workflow( + name = "push_to_public", + mode = "SQUASH", # one net commit/PR per sync; no internal commit-by-commit history leak + + origin = git.github_origin( + url = INTERNAL_URL, + ref = "main", + ), + # Only read the plugin subtree from the monorepo. + origin_files = glob(["plugins/sambanova-plugin-cc/**"]), + + destination = git.github_pr_destination( + url = PUBLIC_URL, + destination_ref = "main", + pr_branch = "copybara/sync-main", # stable branch => one rolling sync PR + title = "Sync from internal: ${COPYBARA_CURRENT_MESSAGE_TITLE}", + body = "Automated mirror of internal `plugins/sambanova-plugin-cc`.\n\n" + + "Review the diff, then merge to publish.\n\n${COPYBARA_CURRENT_MESSAGE}", + update_description = True, + ), + # CRITICAL scope rail: Copybara may only add/modify/delete files under this path. + # Everything else in the public repo (README, LICENSE, CONTRIBUTING, .github/, + # NOTICE, root .claude-plugin/marketplace.json) is public-only and left untouched. + destination_files = glob(["plugins/samba-plugin/**"]), + + # Overwrite all authorship with the bot identity (don't expose internal authors publicly). + authoring = authoring.overwrite(SYNC_AUTHOR), + + transformations = [ + # Rename the folder: internal name -> public name. + core.move("plugins/sambanova-plugin-cc", "plugins/samba-plugin"), + + # --- Public/internal text rewrites ------------------------------------------- + # Leave commented until the first `--dry-run` reveals real internal<->public + # text differences (internal URLs, registry hosts, plugin.json fields the public + # copy intentionally changes, etc.). Encode each as one core.replace so the sync + # re-applies it every time instead of reverting it. Example: + # core.replace( + # before = "internal.sambanova.example", + # after = "api.sambanova.ai", + # paths = glob(["**"]), + # ), + + # Strip internal-only commit-message labels/footers from the public PR description. + metadata.scrubber( + regex = "(?m)^(BUG|INTERNAL|JIRA|Reviewed-by|Change-Id)=?:?.*$", + replacement = "", + ), + ], + + description = "Mirror internal plugins/sambanova-plugin-cc to public sambanova-plugin-cc", +) diff --git a/plugins/samba-plugin/hooks/ensure_venv.sh b/plugins/samba-plugin/hooks/ensure_venv.sh index f909599..089d122 100755 --- a/plugins/samba-plugin/hooks/ensure_venv.sh +++ b/plugins/samba-plugin/hooks/ensure_venv.sh @@ -2,7 +2,7 @@ # Idempotent, concurrency-safe builder for the plugin's Python venv. # # Safe to call from both the SessionStart hook (warmup) and the MCP bootstrap -# wrapper: a lock serializes the two so they can never clobber each other's +# wrapper: an flock serializes the two so they can never clobber each other's # venv creation / pip install. Whoever gets the lock builds; the other blocks, # then sees the work is already done and no-ops. # @@ -20,30 +20,10 @@ VENV="${PLUGIN_DIR}/.env" PY="${VENV}/bin/python3" SENTINEL="${VENV}/.installed_version" -# Serialize the two callers. Lock artifacts live OUTSIDE .env so `rm -rf "$VENV"` -# below can't delete them mid-build and break mutual exclusion. -# -# Prefer flock (Linux). macOS has no flock, so fall back to an atomic mkdir -# spinlock: mkdir either creates the dir (we win) or fails because it exists -# (someone else is building, so we wait). A timeout is the safety valve against -# a stale lock left by a killed process -- after it we proceed unlocked rather -# than hang the MCP startup. -if command -v flock >/dev/null 2>&1; then - exec 9>"${PLUGIN_DIR}/.env.lock" - flock 9 -else - LOCKDIR="${PLUGIN_DIR}/.env.lock.d" - tries=0 - until mkdir "$LOCKDIR" 2>/dev/null; do - tries=$((tries + 1)) - if (( tries > 300 )); then # ~60s at 0.2s/try - echo "ensure_venv: lock busy too long; proceeding without it" >&2 - break - fi - sleep 0.2 - done - trap 'rmdir "$LOCKDIR" 2>/dev/null || true' EXIT -fi +# Lockfile lives OUTSIDE .env so `rm -rf "$VENV"` below can't delete it +# mid-build and break mutual exclusion. +exec 9>"${PLUGIN_DIR}/.env.lock" +flock 9 VERSION="$(python3 -c "import json; print(json.load(open('${PLUGIN_DIR}/.claude-plugin/plugin.json'))['version'])" 2>/dev/null || true)" diff --git a/plugins/samba-plugin/hooks/setup.sh b/plugins/samba-plugin/hooks/setup.sh index eb64ede..aa0ce88 100644 --- a/plugins/samba-plugin/hooks/setup.sh +++ b/plugins/samba-plugin/hooks/setup.sh @@ -5,8 +5,8 @@ if [[ -z "$CLAUDE_PLUGIN_DIR" ]]; then fi # Warm the venv eagerly at session start so the first MCP connect doesn't pay -# the create/install cost (and risk the MCP startup timeout). This shares a -# lock with the MCP bootstrap wrapper, so the two never clobber each other. +# the create/install cost (and risk the MCP startup timeout). This shares an +# flock with the MCP bootstrap wrapper, so the two never clobber each other. "${CLAUDE_PLUGIN_DIR}/hooks/ensure_venv.sh" "$CLAUDE_PLUGIN_DIR" echo "export SAMBANOVA_PLUGIN_CC_PYTHON=\"${CLAUDE_PLUGIN_DIR}/.env/bin/python3\"" >> "$CLAUDE_ENV_FILE" diff --git a/plugins/samba-plugin/mcp_server/bootstrap.sh b/plugins/samba-plugin/mcp_server/bootstrap.sh index 4502e3c..8f8b8db 100755 --- a/plugins/samba-plugin/mcp_server/bootstrap.sh +++ b/plugins/samba-plugin/mcp_server/bootstrap.sh @@ -3,7 +3,7 @@ # # Runs when Claude Code spawns the server -- i.e. after the plugin has loaded -- # so venv creation never races the SessionStart hook. ensure_venv.sh is -# lock-guarded and shared with that hook, so the two are mutually exclusive. +# flock-guarded and shared with that hook, so the two are mutually exclusive. set -euo pipefail PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:?CLAUDE_PLUGIN_ROOT not set}" diff --git a/plugins/samba-plugin/mcp_server/server.py b/plugins/samba-plugin/mcp_server/server.py index 9389358..c7a6269 100644 --- a/plugins/samba-plugin/mcp_server/server.py +++ b/plugins/samba-plugin/mcp_server/server.py @@ -38,17 +38,6 @@ mcp = FastMCP("sambanova-plugin-cc") -# Explicit User-Agent for our direct HTTP calls. Some deployments sit behind a -# proxy/WAF that rejects the default "Python-urllib/x.y" UA with a 403, so we -# always send a stable, identifiable one instead. Harmless on endpoints that -# don't gate on User-Agent. -USER_AGENT = "sambanova-plugin-cc" - -# Different SambaNova-compatible endpoints name the same concept differently. -# Map known aliases onto the field our Model dataclass expects so /model-info -# works across deployments (e.g. some endpoints return max_output_length). -_MODEL_FIELD_ALIASES = {"max_output_length": "max_completion_tokens"} - @mcp.tool() async def code( @@ -147,7 +136,7 @@ def _run_and_format(m: Model, prompt: str, cwd: str, args: list[str], f"opencode exited {result.returncode} " f"(baseURL={get_sambanova_base_url()}, model={m.id})\n{err}\n" "If this is a model-not-found / auth error, the model may not be " - "served on this endpoint — set SAMBANOVA_BASE_URL to the " + "served on this endpoint — set SAMBANOVA_API_OVERRIDE to the " "endpoint that serves it." ) return _format_opencode_run(result.stdout or "", cwd) @@ -286,31 +275,15 @@ def _model_info_impl() -> str: req = urllib.request.Request( f"{get_sambanova_base_url()}/models", - headers={ - "Authorization": f"Bearer {api_key}", - "User-Agent": USER_AGENT, - }, + headers={"Authorization": f"Bearer {api_key}"}, ) with urllib.request.urlopen(req) as resp: result = json.loads(resp.read()) known_fields = set(Model.__dataclass_fields__) lines = [] - for raw in result.get("data", []): - data = dict(raw) - # Bridge endpoint-specific field names (e.g. max_output_length) onto - # the dataclass's expected fields without overwriting a real value. - for src, dst in _MODEL_FIELD_ALIASES.items(): - if data.get(dst) is None and data.get(src) is not None: - data[dst] = data[src] - filtered = {k: v for k, v in data.items() - if k in known_fields and v is not None} - if "id" not in filtered: - continue # nothing useful to show without an id - # Default the required numerics so one sparse entry (e.g. an embedding - # model with no max-output field) can't crash the whole listing. - filtered.setdefault("context_length", 0) - filtered.setdefault("max_completion_tokens", 0) + for model in result.get("data", []): + filtered = {k: v for k, v in model.items() if k in known_fields} lines.append(str(Model(**filtered))) return "\n".join(lines) if lines else "No models returned by the platform." diff --git a/plugins/samba-plugin/skills/model-info/SKILL.md b/plugins/samba-plugin/skills/model-info/SKILL.md index f8c6504..dc6bfbe 100644 --- a/plugins/samba-plugin/skills/model-info/SKILL.md +++ b/plugins/samba-plugin/skills/model-info/SKILL.md @@ -18,6 +18,3 @@ and display its output to the user. Requires the `sambanova-plugin-cc` MCP server to be running, with `SAMBA_CLAUDE_API_KEY` or `SAMBANOVA_API_KEY` set in the environment the server inherits. - -The endpoint defaults to public SambaNova Cloud (`https://api.sambanova.ai/v1`) and -can be pointed at a SambaManaged / Stack deployment by setting `SAMBANOVA_BASE_URL`.