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
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ jobs:
echo "$LOG" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Lowercase image path for the install snippet
id: meta
run: echo "repo=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
if: steps.check.outputs.exists == 'false'
uses: softprops/action-gh-release@v2
Expand All @@ -214,7 +218,7 @@ jobs:

**Docker:**
```bash
docker run -it -v ~/.teleton:/data ghcr.io/${{ github.repository }}:latest setup
docker run -it -v ~/.teleton:/data ghcr.io/${{ steps.meta.outputs.repo }}:latest setup
```

## Changes
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **Replaced the `cocoon` provider with `gocoon`**: a native OpenAI-compatible, pure-Go COCOON client ([gocoon](https://github.com/TONresistor/gocoon)). Tool-calling now uses the model's native `tools`/`tool_calls` instead of the old XML-injection shim, which has been removed (`src/cocoon/` deleted). **Breaking:** set `agent.provider: gocoon` and rename the `cocoon:` config block to `gocoon:` (`gocoon.port`, default `10000`). Turnkey lifecycle: auto-install the gocoon runner, supervise it, and set up/top-up/withdraw the TON channel from the CLI (`teleton gocoon`) or the WebUI Gocoon page.

## [0.8.1] - 2026-03-05

### Added
Expand Down
4 changes: 2 additions & 2 deletions GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Configuration is in `~/.teleton/config.yaml`. The setup wizard generates everyth

```yaml
agent:
provider: "anthropic" # anthropic | openai | google | xai | groq | openrouter | moonshot | mistral | cerebras | zai | minimax | huggingface | cocoon | local
provider: "anthropic" # anthropic | openai | google | xai | groq | openrouter | moonshot | mistral | cerebras | zai | minimax | huggingface | gocoon | local
model: "claude-opus-4-5-20251101"
max_tokens: 4096
temperature: 0.7
Expand Down Expand Up @@ -142,7 +142,7 @@ agent:
model: "gpt-4o"
```

Supported: `anthropic`, `openai`, `google`, `xai`, `groq`, `openrouter`, `moonshot`, `mistral`, `cerebras`, `zai`, `minimax`, `huggingface`, `cocoon`, `local`
Supported: `anthropic`, `openai`, `google`, `xai`, `groq`, `openrouter`, `moonshot`, `mistral`, `cerebras`, `zai`, `minimax`, `huggingface`, `gocoon`, `local`

---

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
## Prerequisites

- **Node.js 20.0.0+** - [Download](https://nodejs.org/)
- **LLM API Key** - One of: [Anthropic](https://console.anthropic.com/) (recommended), [OpenAI](https://platform.openai.com/), [Google](https://aistudio.google.com/), [xAI](https://console.x.ai/), [Groq](https://console.groq.com/), [OpenRouter](https://openrouter.ai/), [Moonshot](https://platform.moonshot.ai/), [Mistral](https://console.mistral.ai/), [Cerebras](https://cloud.cerebras.ai/), [ZAI](https://open.bigmodel.cn/), [MiniMax](https://platform.minimaxi.com/), [Hugging Face](https://huggingface.co/settings/tokens) — or keyless: Codex (auto-detect), Cocoon (TON), Local (Ollama/vLLM)
- **LLM API Key** - One of: [Anthropic](https://console.anthropic.com/) (recommended), [OpenAI](https://platform.openai.com/), [Google](https://aistudio.google.com/), [xAI](https://console.x.ai/), [Groq](https://console.groq.com/), [OpenRouter](https://openrouter.ai/), [Moonshot](https://platform.moonshot.ai/), [Mistral](https://console.mistral.ai/), [Cerebras](https://cloud.cerebras.ai/), [ZAI](https://open.bigmodel.cn/), [MiniMax](https://platform.minimaxi.com/), [Hugging Face](https://huggingface.co/settings/tokens) — or keyless: Codex (auto-detect), Gocoon (TON), Local (Ollama/vLLM)
- **Telegram Account** - Dedicated account recommended for security
- **Telegram API Credentials** - From [my.telegram.org/apps](https://my.telegram.org/apps)
- **Your Telegram User ID** - Message [@userinfobot](https://t.me/userinfobot)
Expand Down Expand Up @@ -172,7 +172,7 @@ The `teleton setup` wizard generates a fully configured `~/.teleton/config.yaml`

```yaml
agent:
provider: "anthropic" # anthropic | openai | google | xai | groq | openrouter | moonshot | mistral | cerebras | zai | minimax | huggingface | cocoon | local
provider: "anthropic" # anthropic | openai | google | xai | groq | openrouter | moonshot | mistral | cerebras | zai | minimax | huggingface | gocoon | local
api_key: "sk-ant-api03-..."
model: "claude-haiku-4-5-20251001"
utility_model: "claude-haiku-4-5-20251001" # for summarization, compaction, vision
Expand Down Expand Up @@ -235,7 +235,7 @@ ton_proxy: # Optional: .ton domain proxy
<td align="center"><br><b>ZAI</b><br>GLM-5<br><br></td>
<td align="center"><br><b>MiniMax</b><br>M2.5<br><br></td>
<td align="center"><br><b>Hugging Face</b><br>DeepSeek V3.2<br><br></td>
<td align="center"><br><b>Cocoon</b><br>Decentralized (TON)<br><br></td>
<td align="center"><br><b>Gocoon</b><br>Decentralized (TON)<br><br></td>
<td align="center"><br><b>Local</b><br>Ollama, vLLM, LM Studio<br><br></td>
</tr>
</table>
Expand Down Expand Up @@ -475,7 +475,7 @@ The SDK provides namespaced access to core services:

| Layer | Technology |
|-------|------------|
| LLM | Multi-provider via [pi-ai](https://github.com/mariozechner/pi-ai) (15 providers: Anthropic, Codex, OpenAI, Google, xAI, Groq, OpenRouter, Moonshot, Mistral, Cerebras, ZAI, MiniMax, Hugging Face, Cocoon, Local) |
| LLM | Multi-provider via [pi-ai](https://github.com/mariozechner/pi-ai) (15 providers: Anthropic, Codex, OpenAI, Google, xAI, Groq, OpenRouter, Moonshot, Mistral, Cerebras, ZAI, MiniMax, Hugging Face, Gocoon, Local) |
| Telegram Userbot | [GramJS](https://gram.js.org/) Layer 223 fork (MTProto) |
| Inline Bot | [Grammy](https://grammy.dev/) (Bot API, for deals) |
| Blockchain | [TON SDK](https://github.com/ton-org/ton) (W5R1 wallet) |
Expand Down Expand Up @@ -593,7 +593,7 @@ packages/sdk/ # Published @teleton-agent/sdk
| **Payment security** | `INSERT OR IGNORE` on tx hashes prevents double-spend, atomic status transitions prevent race conditions |
| **Exec audit** | All YOLO mode commands logged to `exec_audit` table with user, command, output, and timestamps |
| **Pino redaction** | Structured logging with automatic redaction of apiKey, password, secret, token, mnemonic fields |
| **Tool scoping** | Financial tools DM-only, moderation group-only, admin-only policies, per-chat permissions configurable at runtime |
| **Tool access control** | Per-tool access level (all, allow-list, admin, off), DM vs group gated by global policies, per-group module permissions, all runtime-configurable |

### Reporting Vulnerabilities

Expand Down
16 changes: 8 additions & 8 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Run `teleton setup` to generate a config file interactively, or copy `config.exa
- [plugins](#plugins)
- [ton_proxy](#ton_proxy)
- [api](#api)
- [cocoon](#cocoon)
- [gocoon](#gocoon)
- [tonapi_key](#tonapi_key)
- [toncenter_api_key](#toncenter_api_key)
- [tavily_api_key](#tavily_api_key)
Expand All @@ -38,7 +38,7 @@ LLM provider and agentic loop configuration.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `agent.provider` | `enum` | `"anthropic"` | LLM provider. One of: `anthropic`, `codex`, `openai`, `google`, `xai`, `groq`, `openrouter`, `moonshot`, `mistral`, `cerebras`, `zai`, `minimax`, `huggingface`, `cocoon`, `local`. |
| `agent.provider` | `enum` | `"anthropic"` | LLM provider. One of: `anthropic`, `codex`, `openai`, `google`, `xai`, `groq`, `openrouter`, `moonshot`, `mistral`, `cerebras`, `zai`, `minimax`, `huggingface`, `gocoon`, `local`. |
| `agent.api_key` | `string` | `""` | API key for the chosen provider. Can be overridden with `TELETON_API_KEY` env var. |
| `agent.model` | `string` | `"claude-haiku-4-5-20251001"` | Primary model ID. Auto-detected from provider if not set (only for non-Anthropic providers). |
| `agent.utility_model` | `string` | *auto-detected* | Cheap/fast model used for summarization and compaction. If omitted, the platform selects one based on the provider (e.g., `claude-haiku-4-5-20251001` for Anthropic, `gpt-4o-mini` for OpenAI). |
Expand Down Expand Up @@ -96,7 +96,7 @@ When you change the `provider` and omit `model`, the platform auto-selects:
| `zai` | `glm-5.1` | `glm-4.5-air` |
| `minimax` | `MiniMax-M2.7` | `MiniMax-M2.7` |
| `huggingface` | `deepseek-ai/DeepSeek-V3.2` | `Qwen/Qwen3-Next-80B-A3B-Instruct` |
| `cocoon` | `Qwen/Qwen3-32B` | `Qwen/Qwen3-32B` |
| `gocoon` | `Qwen/Qwen3-32B` | `Qwen/Qwen3-32B` |
| `local` | `auto` | `auto` |

---
Expand Down Expand Up @@ -485,20 +485,20 @@ api:

---

## cocoon
## gocoon

Cocoon Network configuration. The Cocoon provider is a decentralized LLM proxy that pays in TON. It requires an external `cocoon-cli` process running on the specified port.
Gocoon configuration. Gocoon is a pure-Go COCOON client: a decentralized LLM that pays in TON. It exposes a native OpenAI-compatible API (with function calling) and requires the `gocoon-runner` process running on the specified port.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `cocoon.port` | `number` | `10000` | HTTP port of the `cocoon-cli` proxy (1-65535). |
| `gocoon.port` | `number` | `10000` | HTTP port of the `gocoon-runner` OpenAI-compatible API (1-65535). |

The `cocoon` section is optional. Only needed when `agent.provider` is set to `"cocoon"`.
The `gocoon` section is optional. Only needed when `agent.provider` is set to `"gocoon"`.

### Example

```yaml
cocoon:
gocoon:
port: 10000
```

Expand Down
2 changes: 1 addition & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Before deploying, make sure you have:
1. **Node.js 20+** (required by the `engines` field in `package.json`)
2. **npm** (ships with Node.js)
3. **Telegram API credentials** -- obtain `api_id` and `api_hash` from [my.telegram.org/apps](https://my.telegram.org/apps)
4. **LLM API key** -- from your chosen provider (Anthropic, OpenAI, Google, xAI, Groq, OpenRouter, Moonshot, Mistral, Cerebras, ZAI, MiniMax, Hugging Face, or Cocoon)
4. **LLM API key** -- from your chosen provider (Anthropic, OpenAI, Google, xAI, Groq, OpenRouter, Moonshot, Mistral, Cerebras, ZAI, MiniMax, Hugging Face, or Gocoon)
5. **Build tools** (only for source/Docker builds) -- `python3`, `make`, `g++` for native modules (`better-sqlite3`)

---
Expand Down
1 change: 0 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"src/webui/**/*.ts",
"src/agent/index.ts",
"src/telegram/index.ts",
"src/telegram/callbacks/index.ts",
"src/sdk/hooks/index.ts",
"src/constants/index.ts",
"src/api/**/*.ts"
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "teleton",
"version": "0.8.6",
"version": "0.9.0",
"workspaces": [
"packages/*"
],
Expand Down Expand Up @@ -32,7 +32,6 @@
"files": [
"dist/",
"bin/",
"scripts/",
"src/templates/"
],
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions src/agent/__tests__/runtime-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ describe("Runtime Hook Integration", () => {
expect(mockLogger.error).not.toHaveBeenCalled();
});

it("5.9 Cocoon provider path: hooks fire same as standard path", async () => {
// Hooks fire before the Cocoon/standard fork — same event for both paths
it("5.9 gocoon provider path: hooks fire same as standard path", async () => {
// Hooks fire before the gocoon/standard fork — same event for both paths
const beforeCalls: string[] = [];
const afterCalls: string[] = [];

Expand All @@ -310,7 +310,7 @@ describe("Runtime Hook Integration", () => {

const runner = createHookRunner(registry, { logger: mockLogger });

// Simulate tool call (same event regardless of Cocoon or standard provider)
// Simulate tool call (same event regardless of gocoon or standard provider)
const beforeEvent: BeforeToolCallEvent = {
toolName: "ton_get_balance",
params: {},
Expand Down
52 changes: 6 additions & 46 deletions src/agent/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { getProviderModel } from "../providers/model-resolver.js";
// layer so non-agent consumers (e.g. memory) can resolve models without importing
// from agent/. Re-exported here for backward compatibility with existing importers.
export {
registerCocoonModels,
registerGocoonModels,
registerLocalModels,
getProviderModel,
getUtilityModel,
Expand All @@ -38,15 +38,10 @@ const RETRY_401_PROVIDERS: { provider: string; refresh: () => Promise<string | n
{ provider: "codex", refresh: refreshCodexApiKey },
];

export function isOAuthToken(apiKey: string, provider?: string): boolean {
if (provider && provider !== "anthropic") return false;
return apiKey.startsWith("sk-ant-oat01-");
}

/** Resolve the effective API key for a provider (local/cocoon need no real key) */
/** Resolve the effective API key for a provider (local/gocoon need no real key) */
export function getEffectiveApiKey(provider: string, rawKey: string): string {
if (provider === "local") return "local";
if (provider === "cocoon") return "";
if (provider === "gocoon") return "gocoon";
if (provider === "codex") return getCodexApiKey(rawKey);
return rawKey;
}
Expand All @@ -71,7 +66,7 @@ const THINK_RE = /<think>[\s\S]*?<\/think>/g;

/**
* Shared post-processing for both complete() and stream() responses: strip
* <think> blocks (Cocoon, Mistral, etc.), persist the transcript, extract the
* <think> blocks (Mistral, local models, etc.), persist the transcript, extract the
* text content, and append the response to the context.
*/
function finalizeResponse(
Expand Down Expand Up @@ -106,23 +101,10 @@ export async function chatWithContext(
): Promise<ChatResponse> {
const provider = (config.provider || "anthropic") as SupportedProvider;
const model = getProviderModel(provider, config.model);
const isCocoon = provider === "cocoon";

let tools =
const tools =
provider === "google" && options.tools ? sanitizeToolsForGemini(options.tools) : options.tools;

// Cocoon: disable thinking mode + inject tools into system prompt
let systemPrompt = options.systemPrompt || options.context.systemPrompt || "";
let cocoonAllowedTools: Set<string> | undefined;
if (isCocoon) {
systemPrompt = "/no_think\n" + systemPrompt;
if (tools && tools.length > 0) {
cocoonAllowedTools = new Set(tools.map((t) => t.name));
const { injectToolsIntoSystemPrompt } = await import("../cocoon/tool-adapter.js");
systemPrompt = injectToolsIntoSystemPrompt(systemPrompt, tools);
tools = undefined; // Don't send via API
}
}
const systemPrompt = options.systemPrompt || options.context.systemPrompt || "";

const context: Context = {
...options.context,
Expand All @@ -139,10 +121,6 @@ export async function chatWithContext(
sessionId: options.sessionId,
cacheRetention: "long",
};
if (isCocoon) {
const { stripCocoonPayload } = await import("../cocoon/tool-adapter.js");
completeOptions.onPayload = stripCocoonPayload;
}

let response = await complete(model, context, completeOptions as ProviderStreamOptions);

Expand All @@ -157,24 +135,6 @@ export async function chatWithContext(
}
}

// Cocoon: parse <tool_call> from text response
if (isCocoon) {
const textBlock = response.content.find((b) => b.type === "text");
if (textBlock?.type === "text" && textBlock.text.includes("<tool_call>")) {
const { parseToolCallsFromText, extractPlainText } =
await import("../cocoon/tool-adapter.js");
const syntheticCalls = parseToolCallsFromText(textBlock.text, cocoonAllowedTools);
if (syntheticCalls.length > 0) {
const plainText = extractPlainText(textBlock.text);
response.content = [
...(plainText ? [{ type: "text" as const, text: plainText }] : []),
...syntheticCalls,
];
(response as { stopReason: AssistantMessage["stopReason"] }).stopReason = "toolUse";
}
}
}

return finalizeResponse(response, context, options);
}

Expand Down
33 changes: 9 additions & 24 deletions src/agent/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import type {
ToolCall,
AssistantMessage,
Message,
ToolResultMessage,
} from "@mariozechner/pi-ai";
import { CompactionManager, DEFAULT_COMPACTION_CONFIG } from "../memory/compaction.js";
import { maskOldToolResults } from "../memory/observation-masking.js";
Expand Down Expand Up @@ -1029,13 +1030,14 @@ export class AgentRuntime {
log.warn(`Tool result too large, truncated to ${resultText.length} chars`);
}

const { buildToolResultMessage } = await import("../cocoon/tool-adapter.js");
const resultMsg = buildToolResultMessage(
sink.provider,
block,
resultText,
!exec.result.success
);
const resultMsg: ToolResultMessage = {
role: "toolResult",
toolCallId: block.id,
toolName: block.name,
content: [{ type: "text", text: resultText }],
isError: !exec.result.success,
timestamp: Date.now(),
};
resultMessages.push(resultMsg);
appendToTranscript(sink.sessionId, resultMsg);
}
Expand Down Expand Up @@ -1411,23 +1413,6 @@ export class AgentRuntime {
return rows.map((r) => r.chat_id);
}

setSoul(soul: string): void {
this.soul = soul;
}

configureCompaction(config: {
enabled?: boolean;
maxMessages?: number;
maxTokens?: number;
}): void {
this.compactionManager.updateConfig(config);
log.info({ config: this.compactionManager.getConfig() }, `Compaction config updated`);
}

getCompactionConfig() {
return this.compactionManager.getConfig();
}

private _memoryStatsCache: {
data: { totalMessages: number; totalChats: number; knowledgeChunks: number };
expiry: number;
Expand Down
5 changes: 0 additions & 5 deletions src/agent/token-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,3 @@ export function accumulateTokenUsage(usage: {
globalTokenUsage.totalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
globalTokenUsage.totalCost += usage.totalCost;
}

export function resetTokenUsage() {
globalTokenUsage.totalTokens = 0;
globalTokenUsage.totalCost = 0;
}
5 changes: 0 additions & 5 deletions src/agent/tools/exec/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
export { default as execModule } from "./module.js";
export { execRunTool, createExecRunExecutor } from "./run.js";
export { execInstallTool, createExecInstallExecutor } from "./install.js";
export { execServiceTool, createExecServiceExecutor } from "./service.js";
export { execStatusTool, createExecStatusExecutor } from "./status.js";
export type { ExecResult, ExecAuditEntry, RunOptions } from "./types.js";
Loading
Loading