Skip to content
Merged
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ lynkr start

### Install

**One-line install (recommended):**
```bash
curl -fsSL https://raw.githubusercontent.com/Fast-Editor/Lynkr/main/install.sh | bash
```

**Or via npm:**
```bash
npm install -g pino-pretty && npm install -g lynkr
```
Expand Down Expand Up @@ -258,7 +264,12 @@ CODE_MODE_ENABLED=true # ~96% reduction in tool-catalog tokens

## Deployment Options

**NPM (recommended)**
**One-line install (recommended)**
```bash
curl -fsSL https://raw.githubusercontent.com/Fast-Editor/Lynkr/main/install.sh | bash
```

**NPM**
```bash
npm install -g lynkr && lynkr start
```
Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Lynkr Installation Script
# Usage: curl -fsSL https://raw.githubusercontent.com/vishalveerareddy123/Lynkr/main/install.sh | bash
# Usage: curl -fsSL https://raw.githubusercontent.com/Fast-Editor/Lynkr/main/install.sh | bash
#
# This script installs Lynkr, a self-hosted Claude Code proxy with multi-provider support.
#
Expand Down Expand Up @@ -125,7 +125,7 @@ create_env_file() {
# Fallback: create minimal .env if .env.example doesn't exist
cat > "$INSTALL_DIR/.env" << 'EOF'
# Lynkr Configuration
# For full options, see: https://github.com/vishalveerareddy123/Lynkr/blob/main/.env.example
# For full options, see: https://github.com/Fast-Editor/Lynkr/blob/main/.env.example

# Model Provider (databricks, openai, azure-openai, azure-anthropic, openrouter, ollama, llamacpp)
MODEL_PROVIDER=ollama
Expand Down Expand Up @@ -247,7 +247,7 @@ print_next_steps() {
echo "💡 ${YELLOW}Tip:${NC} Memory system is enabled by default"
echo " Lynkr remembers preferences and project context across sessions"
echo ""
echo "📚 Documentation: ${BLUE}https://github.com/vishalveerareddy123/Lynkr${NC}"
echo "📚 Documentation: ${BLUE}https://github.com/Fast-Editor/Lynkr${NC}"
echo "💬 Discord: ${BLUE}https://discord.gg/qF7DDxrX${NC}"
echo ""
}
Expand Down
53 changes: 52 additions & 1 deletion src/api/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,46 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
const { createTimer } = require("../utils/perf-timer");
const timer = createTimer("POST /v1/messages");
metrics.recordRequest();
// Support both query parameter (?stream=true) and body parameter ({"stream": true})

// Convert Anthropic server tools (web_search_20260209, etc.) to regular
// function tools so non-Anthropic providers can execute them via Lynkr.
// The orchestrator's SERVER_SIDE_TOOLS handling will execute them server-side.
if (Array.isArray(req.body?.tools)) {
const incomingToolTypes = req.body.tools.map(t => t?.type || t?.name).filter(Boolean);
logger.info({ incomingToolTypes }, "Incoming /v1/messages tool types");
req.body.tools = req.body.tools.map((tool) => {
if (tool?.type?.startsWith?.("web_search_20")) {
logger.info({ originalType: tool.type, name: tool.name }, "Converting web_search server tool to function tool");
return {
name: tool.name || "web_search",
description: "Search the web for up-to-date information. Returns relevant search results from the web.",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
},
required: ["query"],
},
};
}
if (tool?.type?.startsWith?.("web_fetch_")) {
return {
name: tool.name || "web_fetch",
description: "Fetch the contents of a URL.",
input_schema: {
type: "object",
properties: {
url: { type: "string", description: "URL to fetch" },
},
required: ["url"],
},
};
}
return tool;
});
}

// Support both query parameter (?stream=true) and body parameter ({"stream": true})
const wantsStream = Boolean(req.query?.stream === 'true' || req.body?.stream);
const hasTools = Array.isArray(req.body?.tools) && req.body.tools.length > 0;
timer.mark("parseRequest");
Expand Down Expand Up @@ -770,6 +809,18 @@ router.get("/metrics/compression", async (req, res) => {
}
});

router.get("/metrics/tool-compression", (req, res) => {
const { getMetrics } = require("../context/tool-result-compressor");
res.json(getMetrics());
});

router.get("/tee/:id", (req, res) => {
const { teeGet } = require("../context/tool-result-compressor");
const content = teeGet(req.params.id);
if (!content) return res.status(404).json({ error: "Tee entry not found or expired" });
res.type("text/plain").send(content);
});

router.get("/health/headroom", async (req, res) => {
try {
const { getHeadroomManager } = require("../headroom");
Expand Down
3 changes: 3 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,9 @@ var config = {
fallbackProvider,
},
toolExecutionMode,
toolResultCompression: {
enabled: true,
},
server: {
jsonLimit: process.env.REQUEST_JSON_LIMIT ?? "1gb",
},
Expand Down
Loading
Loading