Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/samba-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
27 changes: 6 additions & 21 deletions plugins/samba-plugin/agent_shims/src/agent_shims/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("/")
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}": {
Expand Down
84 changes: 84 additions & 0 deletions plugins/samba-plugin/copybara/README.md
Original file line number Diff line number Diff line change
@@ -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=<token-with-access-to-both-repos>
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.
70 changes: 70 additions & 0 deletions plugins/samba-plugin/copybara/copy.bara.sky
Original file line number Diff line number Diff line change
@@ -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 <oss-bot@sambanovasystems.com>"

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",
)
30 changes: 5 additions & 25 deletions plugins/samba-plugin/hooks/ensure_venv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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)"

Expand Down
4 changes: 2 additions & 2 deletions plugins/samba-plugin/hooks/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion plugins/samba-plugin/mcp_server/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
35 changes: 4 additions & 31 deletions plugins/samba-plugin/mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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."

Expand Down
3 changes: 0 additions & 3 deletions plugins/samba-plugin/skills/model-info/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Loading