Stapler is a universal Paperclip external adapter for OpenAI-compatible LLM APIs (GET /v1/models, POST /v1/chat/completions).
Configure up to 16 providers with numbered env slots — no code changes, no hardcoded vendor list. Slot ids (openai, groq, openrouter, …) are derived from each URL hostname at runtime.
Version: 1.3.1 · Spec: docs/stapler-spec.md
- Up to 16 provider slots via
STAPLER_WEB_URL_n+STAPLER_API_KEY_n - Union model discovery — Models / Detect / Refresh list every configured slot
- Prefixed model ids when multiple slots exist (
openai::gpt-4o,groq::llama-3.3-70b) - Sandboxed workspace tools:
list_dir,read_file,write_file,str_replace,delete_file,move_file,grep_search,glob_files,http_fetch,web_search,exec_command(full host shell; cwd stays in the workspace) - Default 1000 tool rounds with configurable loop protection (
off,advisory,balanced,strict, or custom thresholds) - Default run timeout 7200s (raise further for very long jobs)
- Paperclip Tool Gateway + MCP when host auth is available
- Issue tools:
paperclip_checkout_issue,paperclip_update_issue,paperclip_add_comment,paperclip_create_issue,paperclip_get_issue,paperclip_list_my_issues,paperclip_list_agents - Bundled stapler operating skill injected on every run (handoffs + structured mentions)
Restart Paperclip after setting these on the server process:
# Slot 1 — example: OpenAI
STAPLER_WEB_URL_1=https://api.openai.com/v1
STAPLER_API_KEY_1=sk-...
# Slot 2 — example: Groq
STAPLER_WEB_URL_2=https://api.groq.com/openai/v1
STAPLER_API_KEY_2=gsk_...
# Optional: prefer a slot for global Detect
STAPLER_PROVIDER=groq
STAPLER_MODEL=llama-3.3-70b-versatileBind keys through Paperclip Secret access using the same variable names.
git clone https://github.com/MYehia565/stapler.git
cd stapler
npm ci
npm run build
# Install into Paperclip via Adapter Manager or POST /api/adapters/install- Adapter type:
stapler - LLM provider select: pick a slot id from the dropdown (derived from hostname)
- Loop protection mode:
balancedby default; chooseoff,advisory,balanced,strict, orcustom - Run Environment test, then a heartbeat run
Every Stapler heartbeat always injects the bundled operating skill (tools, handoffs, structured @mentions). The body is embedded in the adapter so it still loads when Paperclip installs only dist/ and skills/ is missing on disk. skills/stapler/SKILL.md remains the source for optional company-library import.
You do not need to attach it manually for agents to see it (manual attach still works and is fine as a backup).
Optional — import into the company skill library so it also appears in the Paperclip Skills UI:
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/skills/import" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "source": "https://github.com/MYehia565/stapler/tree/main/skills/stapler" }'Then attach stapler (plus your other skills) on each agent via Skills → sync if you want it listed in the UI. Runtime injection still happens even without that attach.
- Max tool rounds defaults to
1000. A round is one model response that can request one or more tool calls, followed by the next model turn. - Run timeout defaults to
7200seconds and still caps the total wall-clock runtime even if tool rounds remain. - Loop protection mode defaults to
balanced:off: no loop interventionadvisory: warnings onlybalanced: warn early, optionally block redundant tools later, hard-stop mainly repeated failuresstrict: intervene earlier and can hard-stop repeated successful no-op loopscustom: keep the mode UI but override thresholds below
- Advanced loop overrides available in adapter config:
loopSoftAfterloopFirmAfterloopHardAfterloopSameCallSoftAfterloopSameCallFirmAfterloopSameCallHardAfterloopBlockRepeatedToolsloopAbortRepeatedSuccesses
Stapler now treats repeated same calls with the same results as a stronger loop signal than repeated calls alone, which reduces false positives during normal work.
Stapler works with any host that exposes OpenAI-compatible /models and /chat/completions. The slot id column is what appears in the UI and in prefixed model ids — it is extracted from the URL hostname (see How slot ids work).
| Provider | STAPLER_WEB_URL_n |
Slot id | Notes |
|---|---|---|---|
| OpenAI | https://api.openai.com/v1 |
openai |
Official API |
| Groq | https://api.groq.com/openai/v1 |
groq |
Fast inference |
| DeepSeek | https://api.deepseek.com/v1 |
deepseek |
DeepSeek models |
| Mistral | https://api.mistral.ai/v1 |
mistral |
Mistral API |
| Cerebras | https://api.cerebras.ai/v1 |
cerebras |
Low-latency inference |
| xAI (Grok) | https://api.x.ai/v1 |
xai |
Grok API |
| Perplexity | https://api.perplexity.ai |
perplexity |
Sonar models |
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai/ |
googleapis |
OpenAI-compat layer |
| Azure OpenAI | https://{resource}.openai.azure.com/openai/deployments/{deployment} |
azure |
Use deployment-specific path |
| Alibaba DashScope | https://dashscope.aliyuncs.com/compatible-mode/v1 |
aliyuncs |
Qwen and compatible models |
| Moonshot (Kimi) | https://api.moonshot.cn/v1 |
moonshot |
Kimi API |
| Provider | STAPLER_WEB_URL_n |
Slot id | Notes |
|---|---|---|---|
| OpenRouter | https://openrouter.ai/api/v1 |
openrouter |
Multi-vendor router; use STAPLER_EXTRA_n for routing hints |
| Together AI | https://api.together.xyz/v1 |
together |
Open models |
| Fireworks AI | https://api.fireworks.ai/inference/v1 |
fireworks |
Serverless models |
| Hugging Face Inference | https://router.huggingface.co/v1 |
huggingface |
Router endpoint |
| Anyscale Endpoints | https://api.endpoints.anyscale.com/v1 |
anyscale |
Hosted open models |
| Novita AI | https://api.novita.ai/v3/openai |
novita |
Open-model API |
| SiliconFlow | https://api.siliconflow.cn/v1 |
siliconflow |
CN-hosted open models |
| Lambda Labs | https://api.lambdalabs.com/v1 |
lambdalabs |
Cloud GPU inference |
| Provider | STAPLER_WEB_URL_n |
Slot id | Notes |
|---|---|---|---|
| Ollama | http://127.0.0.1:11434/v1 |
slot1 |
IP addresses → slot{n} |
| LM Studio | http://127.0.0.1:1234/v1 |
slot1 |
Local OpenAI-compat server |
| LiteLLM proxy | http://localhost:4000/v1 |
localhost |
Hostname localhost stays localhost |
| vLLM | http://127.0.0.1:8000/v1 |
slot1 |
Self-hosted serving |
Anthropic Claude does not expose a native OpenAI-compatible API. Route through OpenRouter or a LiteLLM proxy, then set STAPLER_EXTRA_n if the router needs provider hints.
Given STAPLER_WEB_URL_n, Stapler parses the hostname, strips common gateway prefixes (api., www., gateway., …), and uses the brand label immediately before the public suffix (e.g. api.openai.com → openai, openrouter.ai → openrouter, api.x.ai → xai, foo.example.co.uk → example). Bare IP addresses become slot1, slot2, … by slot index. The hostname localhost becomes localhost. When two URLs would collide, ids are deduplicated (openai, openai-2).
STAPLER_WEB_URL_1=https://api.openai.com/v1
STAPLER_API_KEY_1=sk-openai-...
STAPLER_WEB_URL_2=https://openrouter.ai/api/v1
STAPLER_API_KEY_2=sk-or-...
STAPLER_WEB_URL_3=https://api.groq.com/openai/v1
STAPLER_API_KEY_3=gsk-...Models UI shows a union catalogue. Pick openai::gpt-4o or set the agent model field to pin a slot even if Provider is stale.
Merge provider-specific body fields into every /chat/completions call for that slot:
STAPLER_EXTRA_2='{"provider":{"order":["Anthropic"]}}' # OpenRouter exampleAgent extraBody merges on top (agent wins on key conflicts). Invalid STAPLER_EXTRA_n JSON fails Environment test. Extra cannot set model, messages, or tools.
| Variable | Required | Purpose |
|---|---|---|
STAPLER_WEB_URL_1 … _16 |
Yes (per slot) | OpenAI-compatible base URL |
STAPLER_API_KEY_1 … _16 |
Yes (per slot) | Bearer token for that URL |
STAPLER_WEB_URL + STAPLER_API_KEY |
Alt slot 1 | Used when _1 pair is absent |
STAPLER_EXTRA_n |
No | JSON object merged into requests |
STAPLER_PROVIDER |
No | Global Detect preference (slot id) |
STAPLER_MODEL |
No | Global model override |
Aliases accepted: STAPLER_API_KEY1 (no underscore before number).
npm ci
npm run typecheck
npm test # 100+ unit tests
npm run build
npm run verify:e2eSee CONTRIBUTING.md, VERIFICATION.md, and docs/e2e-checklist.md.
After creating github.com/MYehia565/stapler (or your org):
- Update
repositoryin package.json if the org/name differs git add . && git commit -m "Initial public release: Stapler 1.0.0"git remote add origin git@github.com:MYehia565/stapler.gitgit push -u origin main- Optional: tag
v1.0.0for a GitHub release
Install in Paperclip from the published path or git URL.