refactor: adopt upstream versions for docs, config, and low-diff Go files
Replace 30+ files with upstream's exact versions to eliminate merge conflicts. Fork-only additions (interfaces, struct fields, methods) are re-added at non-conflicting positions or in separate _ext.go files. Files replaced: READMEs, CI configs, docs, Makefile, and ~20 Go source files with <30 line diffs from upstream. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8eaf46e87e
commit
9caa237639
47 changed files with 760 additions and 346 deletions
23
.github/workflows/pr.yml
vendored
23
.github/workflows/pr.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
name: PR
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
pull_request: { }
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
|
@ -16,9 +16,6 @@ jobs:
|
|||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run go generate
|
||||
run: go generate ./...
|
||||
|
||||
|
|
@ -58,26 +55,8 @@ jobs:
|
|||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Run go generate
|
||||
run: go generate ./...
|
||||
|
||||
- name: Run frontend tests
|
||||
run: |
|
||||
pnpm --dir pkg/miniapp/frontend install --frozen-lockfile
|
||||
pnpm --dir pkg/miniapp/frontend test
|
||||
|
||||
- name: Run go test
|
||||
run: go test ./...
|
||||
|
|
|
|||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
|
|
@ -65,6 +65,14 @@ jobs:
|
|||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
run: corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
|
|
@ -96,6 +104,11 @@ jobs:
|
|||
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }}
|
||||
GOVERSION: ${{ steps.setup-go.outputs.go-version }}
|
||||
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
|
||||
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
||||
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
|
||||
- name: Apply release flags
|
||||
shell: bash
|
||||
|
|
|
|||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -47,14 +47,12 @@ docs/plans/
|
|||
|
||||
# Added by goreleaser init:
|
||||
dist/
|
||||
!pkg/miniapp/static/dist/
|
||||
!pkg/miniapp/static/dist/**
|
||||
*.vite/
|
||||
|
||||
# Windows Application Icon/Resource
|
||||
*.syso
|
||||
|
||||
|
||||
# Frontend dependencies
|
||||
node_modules/
|
||||
|
||||
|
||||
# Keep embedded backend dist directory placeholder in VCS
|
||||
!web/backend/dist/
|
||||
web/backend/dist/*
|
||||
!web/backend/dist/.gitkeep
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ before:
|
|||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
- sh -c 'cd web/frontend && pnpm install && pnpm build:backend'
|
||||
- go install github.com/tc-hib/go-winres@latest
|
||||
- go-winres make --in cmd/picoclaw-launcher/winres/winres.json --out cmd/picoclaw-launcher/rsrc --product-version={{ .Version }} --file-version={{ .Version }}
|
||||
- go-winres make --in web/backend/winres/winres.json --out web/backend/rsrc --product-version={{ .Version }} --file-version={{ .Version }}
|
||||
|
||||
builds:
|
||||
- id: picoclaw
|
||||
|
|
@ -17,10 +18,10 @@ builds:
|
|||
- stdjson
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.version={{ .Version }}
|
||||
- -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.gitCommit={{ .ShortCommit }}
|
||||
- -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.buildTime={{ .Date }}
|
||||
- -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.goVersion={{ .Env.GOVERSION }}
|
||||
- -X github.com/sipeed/picoclaw/pkg/config.Version={{ .Version }}
|
||||
- -X github.com/sipeed/picoclaw/pkg/config.GitCommit={{ .ShortCommit }}
|
||||
- -X github.com/sipeed/picoclaw/pkg/config.BuildTime={{ .Date }}
|
||||
- -X github.com/sipeed/picoclaw/pkg/config.GoVersion={{ .Env.GOVERSION }}
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
|
|
@ -32,9 +33,13 @@ builds:
|
|||
- riscv64
|
||||
- loong64
|
||||
- arm
|
||||
- s390x
|
||||
- mipsle
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
gomips:
|
||||
- softfloat
|
||||
main: ./cmd/picoclaw
|
||||
ignore:
|
||||
- goos: windows
|
||||
|
|
@ -59,10 +64,14 @@ builds:
|
|||
- riscv64
|
||||
- loong64
|
||||
- arm
|
||||
- s390x
|
||||
- mipsle
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
main: ./cmd/picoclaw-launcher
|
||||
gomips:
|
||||
- softfloat
|
||||
main: ./web/backend
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
|
|
@ -86,9 +95,13 @@ builds:
|
|||
- riscv64
|
||||
- loong64
|
||||
- arm
|
||||
- s390x
|
||||
- mipsle
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
gomips:
|
||||
- softfloat
|
||||
main: ./cmd/picoclaw-launcher-tui
|
||||
ignore:
|
||||
- goos: windows
|
||||
|
|
@ -103,15 +116,49 @@ dockers_v2:
|
|||
- picoclaw
|
||||
images:
|
||||
- "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw"
|
||||
- "docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}"
|
||||
- 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}'
|
||||
tags:
|
||||
- "{{ .Tag }}"
|
||||
- "latest"
|
||||
- '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}{{ .Tag }}{{ end }}'
|
||||
- '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}latest{{ end }}'
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/riscv64
|
||||
|
||||
- id: picoclaw-launcher
|
||||
dockerfile: docker/Dockerfile.goreleaser.launcher
|
||||
ids:
|
||||
- picoclaw
|
||||
- picoclaw-launcher
|
||||
- picoclaw-launcher-tui
|
||||
images:
|
||||
- "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw"
|
||||
- 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}'
|
||||
tags:
|
||||
- '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}{{ .Tag }}-launcher{{ end }}'
|
||||
- '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}launcher{{ end }}'
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/riscv64
|
||||
|
||||
notarize:
|
||||
macos:
|
||||
- enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
|
||||
ids:
|
||||
- picoclaw
|
||||
- picoclaw-launcher
|
||||
- picoclaw-launcher-tui
|
||||
sign:
|
||||
certificate: "{{.Env.MACOS_SIGN_P12}}"
|
||||
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
|
||||
notarize:
|
||||
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
|
||||
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
|
||||
key: "{{.Env.MACOS_NOTARY_KEY}}"
|
||||
wait: true
|
||||
timeout: 20m
|
||||
|
||||
archives:
|
||||
- formats: [tar.gz]
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
|
|
@ -129,7 +176,7 @@ archives:
|
|||
|
||||
nfpms:
|
||||
- id: picoclaw
|
||||
builds:
|
||||
ids:
|
||||
- picoclaw
|
||||
- picoclaw-launcher
|
||||
- picoclaw-launcher-tui
|
||||
|
|
@ -149,6 +196,11 @@ nfpms:
|
|||
- rpm
|
||||
- deb
|
||||
bindir: /usr/bin
|
||||
contents:
|
||||
- src: web/picoclaw-launcher.desktop
|
||||
dst: /usr/share/applications/picoclaw-launcher.desktop
|
||||
- src: web/picoclaw-launcher.png
|
||||
dst: /usr/share/icons/hicolor/512x512/apps/picoclaw-launcher.png
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
|
|
@ -163,6 +215,7 @@ changelog:
|
|||
# lzma: true
|
||||
|
||||
release:
|
||||
disable: '{{ isEnvSet "NIGHTLY_BUILD" }}'
|
||||
footer: >-
|
||||
|
||||
---
|
||||
|
|
|
|||
20
Makefile
20
Makefile
|
|
@ -11,8 +11,8 @@ VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
|||
GIT_COMMIT=$(shell git rev-parse --short=8 HEAD 2>/dev/null || echo "dev")
|
||||
BUILD_TIME=$(shell date +%FT%T%z)
|
||||
GO_VERSION=$(shell $(GO) version | awk '{print $$3}')
|
||||
INTERNAL=github.com/sipeed/picoclaw/cmd/picoclaw/internal
|
||||
LDFLAGS=-ldflags "-X $(INTERNAL).version=$(VERSION) -X $(INTERNAL).gitCommit=$(GIT_COMMIT) -X $(INTERNAL).buildTime=$(BUILD_TIME) -X $(INTERNAL).goVersion=$(GO_VERSION) -s -w"
|
||||
CONFIG_PKG=github.com/sipeed/picoclaw/pkg/config
|
||||
LDFLAGS=-ldflags "-X $(CONFIG_PKG).Version=$(VERSION) -X $(CONFIG_PKG).GitCommit=$(GIT_COMMIT) -X $(CONFIG_PKG).BuildTime=$(BUILD_TIME) -X $(CONFIG_PKG).GoVersion=$(GO_VERSION) -s -w"
|
||||
|
||||
# Go variables
|
||||
GO?=CGO_ENABLED=0 go
|
||||
|
|
@ -110,12 +110,18 @@ build: generate
|
|||
@$(GO) build $(GOFLAGS) $(LDFLAGS) -o $(BINARY_PATH) ./$(CMD_DIR)
|
||||
@echo "Build complete: $(BINARY_PATH)"
|
||||
@ln -sf $(BINARY_NAME)-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/$(BINARY_NAME)
|
||||
@if [ "$(PLATFORM)" = "linux" ] || [ "$(PLATFORM)" = "darwin" ]; then \
|
||||
echo "Install to /usr/local/bin:"; \
|
||||
echo " sudo install -m 755 $(BINARY_PATH) /usr/local/bin/$(BINARY_NAME)"; \
|
||||
else \
|
||||
echo "Install hint skipped: /usr/local/bin command is for Linux/macOS (current: $(PLATFORM))."; \
|
||||
|
||||
## build-launcher: Build the picoclaw-launcher (web console) binary
|
||||
build-launcher:
|
||||
@echo "Building picoclaw-launcher for $(PLATFORM)/$(ARCH)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@if [ ! -f web/backend/dist/index.html ]; then \
|
||||
echo "Building frontend..."; \
|
||||
cd web/frontend && pnpm install && pnpm build:backend; \
|
||||
fi
|
||||
@$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH) ./web/backend
|
||||
@ln -sf picoclaw-launcher-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher
|
||||
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher"
|
||||
|
||||
## build-whatsapp-native: Build with WhatsApp native (whatsmeow) support; larger binary
|
||||
build-whatsapp-native: generate
|
||||
|
|
|
|||
63
README.fr.md
63
README.fr.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw : Assistant IA Ultra-Efficace en Go</h1>
|
||||
|
||||
|
|
@ -206,9 +206,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 Démarrage Rapide
|
||||
|
||||
> [!TIP]
|
||||
> Configurez votre clé API dans `~/.picoclaw/config.json`.
|
||||
> Obtenir des clés API : [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> La recherche web est **optionnelle** — obtenez gratuitement l'[API Brave Search](https://brave.com/search/api) (2000 requêtes gratuites/mois) ou utilisez le repli automatique intégré.
|
||||
> Configurez votre clé API dans `~/.picoclaw/config.json`. Obtenez des clés API : [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). La recherche web est optionnelle — obtenez gratuitement l'[API Tavily](https://tavily.com) (1000 requêtes gratuites/mois) ou l'[API Brave Search](https://brave.com/search/api) (2000 requêtes gratuites/mois).
|
||||
|
||||
**1. Initialiser**
|
||||
|
||||
|
|
@ -222,8 +220,13 @@ picoclaw onboard
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"request_timeout": 300,
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
|
|
@ -231,7 +234,7 @@ picoclaw onboard
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt4"
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"channels": {
|
||||
|
|
@ -649,7 +652,6 @@ PicoClaw stocke les données dans votre workspace configuré (par défaut : `~/.
|
|||
├── HEARTBEAT.md # Invites de tâches périodiques (vérifiées toutes les 30 min)
|
||||
├── IDENTITY.md # Identité de l'Agent
|
||||
├── SOUL.md # Âme de l'Agent
|
||||
├── TOOLS.md # Description des outils
|
||||
└── USER.md # Préférences utilisateur
|
||||
```
|
||||
|
||||
|
|
@ -978,8 +980,10 @@ Cette conception permet également le **support multi-agent** avec une sélectio
|
|||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Obtenir Clé](https://openrouter.ai/keys) |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Obtenir Clé](https://cerebras.ai) |
|
||||
| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) |
|
||||
| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) |
|
||||
| **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Obtenir une clé](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | Custom | OAuth uniquement |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -989,8 +993,13 @@ Cette conception permet également le **support multi-agent** avec une sélectio
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -1006,7 +1015,7 @@ Cette conception permet également le **support multi-agent** avec une sélectio
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1017,8 +1026,17 @@ Cette conception permet également le **support multi-agent** avec une sélectio
|
|||
**OpenAI**
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**VolcEngine (Doubao)**
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -1061,14 +1079,14 @@ Configurez plusieurs points de terminaison pour le même nom de modèle—PicoCl
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -1200,6 +1218,13 @@ Cela se produit lorsqu'une autre instance du bot est en cours d'exécution. Assu
|
|||
| Service | Offre Gratuite | Cas d'Utilisation |
|
||||
| ---------------- | -------------------- | ------------------------------------- |
|
||||
| **OpenRouter** | 200K tokens/mois | Multiples modèles (Claude, GPT-4, etc.) |
|
||||
| **Zhipu** | 200K tokens/mois | Idéal pour les utilisateurs chinois |
|
||||
| **Volcengine CodingPlan** | 9,9¥/premier mois | Idéal pour les utilisateurs chinois, multiples modèles SOTA (Doubao, DeepSeek, etc.) |
|
||||
| **Zhipu** | 200K tokens/mois | Convient aux utilisateurs chinois |
|
||||
| **Brave Search** | 2000 requêtes/mois | Fonctionnalité de recherche web |
|
||||
| **Groq** | Offre gratuite dispo | Inférence ultra-rapide (Llama, Mixtral) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
63
README.ja.md
63
README.ja.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Go で書かれた超効率 AI アシスタント</h1>
|
||||
|
||||
|
|
@ -168,9 +168,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 クイックスタート(ネイティブ)
|
||||
|
||||
> [!TIP]
|
||||
> `~/.picoclaw/config.json` に API キーを設定してください。
|
||||
> API キーの取得先: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> Web 検索は **任意** です - 無料の [Tavily API](https://tavily.com) (月 1000 クエリ無料) または [Brave Search API](https://brave.com/search/api) (月 2000 クエリ無料)
|
||||
> `~/.picoclaw/config.json` に API キーを設定してください。API キーの取得先: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)。Web 検索は **任意** です — 無料の [Tavily API](https://tavily.com) (月 1000 クエリ無料) または [Brave Search API](https://brave.com/search/api) (月 2000 クエリ無料)。
|
||||
|
||||
**1. 初期化**
|
||||
|
||||
|
|
@ -184,8 +182,13 @@ picoclaw onboard
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"request_timeout": 300,
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
|
|
@ -193,7 +196,7 @@ picoclaw onboard
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt4"
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"channels": {
|
||||
|
|
@ -610,7 +613,6 @@ PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw
|
|||
├── HEARTBEAT.md # 定期タスクプロンプト(30分ごとに確認)
|
||||
├── IDENTITY.md # エージェントのアイデンティティ
|
||||
├── SOUL.md # エージェントのソウル
|
||||
├── TOOLS.md # ツールの説明
|
||||
└── USER.md # ユーザー設定
|
||||
```
|
||||
|
||||
|
|
@ -919,8 +921,10 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
|||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [キーを取得](https://openrouter.ai/keys) |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | ローカル |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [キーを取得](https://cerebras.ai) |
|
||||
| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) |
|
||||
| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) |
|
||||
| **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [キーを取得](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | カスタム | OAuthのみ |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -930,8 +934,13 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -947,7 +956,7 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -958,8 +967,17 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
|||
**OpenAI**
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**VolcEngine (Doubao)**
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -1002,14 +1020,14 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -1120,9 +1138,16 @@ Web 検索を有効にするには:
|
|||
| サービス | 無料枠 | ユースケース |
|
||||
|---------|--------|------------|
|
||||
| **OpenRouter** | 月 200K トークン | 複数モデル(Claude, GPT-4 など) |
|
||||
| **Zhipu** | 月 200K トークン | 中国ユーザー向け最適 |
|
||||
| **Volcengine CodingPlan** | 9.9元/初月 | 中国ユーザーに最適、複数のSOTAモデル(Doubao、DeepSeek等) |
|
||||
| **Zhipu** | 月 200K トークン | 中国ユーザーに適している |
|
||||
| **Qwen** | 無料枠あり | 通義千問 (Qwen) |
|
||||
| **Brave Search** | 月 2000 クエリ | Web 検索機能 |
|
||||
| **Tavily** | 月 1000 クエリ | AI エージェント検索最適化 |
|
||||
| **Groq** | 無料枠あり | 高速推論(Llama, Mixtral) |
|
||||
| **Cerebras** | 無料枠あり | 高速推論(Llama, Qwen など) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
78
README.md
78
README.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Ultra-Efficient AI Assistant in Go</h1>
|
||||
|
||||
|
|
@ -194,6 +194,19 @@ docker compose -f docker/docker-compose.yml logs -f picoclaw-gateway
|
|||
docker compose -f docker/docker-compose.yml --profile gateway down
|
||||
```
|
||||
|
||||
### Launcher Mode (Web Console)
|
||||
|
||||
The `launcher` image includes all three binaries (`picoclaw`, `picoclaw-launcher`, `picoclaw-launcher-tui`) and starts the web console by default, which provides a browser-based UI for configuration and chat.
|
||||
|
||||
```bash
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
Open http://localhost:18800 in your browser. The launcher manages the gateway process automatically.
|
||||
|
||||
> [!WARNING]
|
||||
> The web console does not yet support authentication. Avoid exposing it to the public internet.
|
||||
|
||||
### Agent Mode (One-shot)
|
||||
|
||||
```bash
|
||||
|
|
@ -214,9 +227,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 Quick Start
|
||||
|
||||
> [!TIP]
|
||||
> Set your API key in `~/.picoclaw/config.json`.
|
||||
> Get API keys: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> Web Search is **optional** - get free [Tavily API](https://tavily.com) (1000 free queries/month), [SearXNG](https://github.com/searxng/searxng) (free, self-hosted) or [Brave Search API](https://brave.com/search/api) (2000 free queries/month) or use built-in auto fallback.
|
||||
> Set your API Key in `~/.picoclaw/config.json`. Get API Keys: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Web search is optional — get a free [Tavily API](https://tavily.com) (1000 free queries/month) or [Brave Search API](https://brave.com/search/api) (2000 free queries/month).
|
||||
|
||||
**1. Initialize**
|
||||
|
||||
|
|
@ -231,7 +242,7 @@ picoclaw onboard
|
|||
"agents": {
|
||||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"model_name": "gpt4",
|
||||
"model_name": "gpt-5.4",
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"max_tool_iterations": 20
|
||||
|
|
@ -239,8 +250,13 @@ picoclaw onboard
|
|||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"request_timeout": 300
|
||||
},
|
||||
|
|
@ -774,7 +790,6 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa
|
|||
├── HEARTBEAT.md # Periodic task prompts (checked every 30 min)
|
||||
├── IDENTITY.md # Agent identity
|
||||
├── SOUL.md # Agent soul
|
||||
├── TOOLS.md # Tool descriptions
|
||||
└── USER.md # User preferences
|
||||
```
|
||||
|
||||
|
|
@ -1018,9 +1033,11 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
| **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | Your LiteLLM proxy key |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Get Key](https://cerebras.ai) |
|
||||
| **火山引擎** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) |
|
||||
| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) |
|
||||
| **神算云** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) |
|
||||
| **Vivgrid** | `vivgrid/` | `https://api.vivgrid.com/v1` | OpenAI | [Get Key](https://vivgrid.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Get Key](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | Custom | OAuth only |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -1030,8 +1047,13 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -1047,7 +1069,7 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1059,8 +1081,18 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**VolcEngine (Doubao)**
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -1139,14 +1171,14 @@ Configure multiple endpoints for the same model name—PicoClaw will automatical
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -1486,8 +1518,16 @@ This happens when another instance of the bot is running. Make sure only one `pi
|
|||
| Service | Free Tier | Use Case |
|
||||
| ---------------- | ------------------------ | ------------------------------------- |
|
||||
| **OpenRouter** | 200K tokens/month | Multiple models (Claude, GPT-4, etc.) |
|
||||
| **Zhipu** | 200K tokens/month | Best for Chinese users |
|
||||
| **Volcengine CodingPlan** | ¥9.9/first month | Best for Chinese users, multiple SOTA models (Doubao, DeepSeek, etc.) |
|
||||
| **Zhipu** | 200K tokens/month | Suitable for Chinese users |
|
||||
| **Brave Search** | Paid ($5/1000 queries) | Web search functionality |
|
||||
| **SearXNG** | Unlimited (self-hosted) | Privacy-focused metasearch (70+ engines) |
|
||||
| **Groq** | Free tier available | Fast inference (Llama, Mixtral) |
|
||||
| **Cerebras** | Free tier available | Fast inference (Llama, Qwen, etc.) |
|
||||
| **LongCat** | Up to 5M tokens/day | Fast inference (free tier) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Assistente de IA Ultra-Eficiente em Go</h1>
|
||||
|
||||
|
|
@ -207,9 +207,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 Início Rápido
|
||||
|
||||
> [!TIP]
|
||||
> Configure sua API key em `~/.picoclaw/config.json`.
|
||||
> Obtenha API keys: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> Busca web e **opcional** — obtenha a [Brave Search API](https://brave.com/search/api) gratuita (2000 consultas grátis/mês) ou use o fallback automático integrado.
|
||||
> Configure sua API key em `~/.picoclaw/config.json`. Obtenha API keys: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Busca web é **opcional** — obtenha a [API Tavily](https://tavily.com) gratuita (1000 consultas grátis/mês) ou a [Brave Search API](https://brave.com/search/api) (2000 consultas grátis/mês).
|
||||
|
||||
**1. Inicializar**
|
||||
|
||||
|
|
@ -223,8 +221,13 @@ picoclaw onboard
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"request_timeout": 300,
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
|
|
@ -232,7 +235,7 @@ picoclaw onboard
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt4"
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
|
|
@ -645,7 +648,6 @@ O PicoClaw armazena dados no workspace configurado (padrão: `~/.picoclaw/worksp
|
|||
├── HEARTBEAT.md # Prompts de tarefas periodicas (verificado a cada 30 min)
|
||||
├── IDENTITY.md # Identidade do Agente
|
||||
├── SOUL.md # Alma do Agente
|
||||
├── TOOLS.md # Descrição das ferramentas
|
||||
└── USER.md # Preferencias do usuario
|
||||
```
|
||||
|
||||
|
|
@ -974,8 +976,10 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve
|
|||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Obter Chave](https://openrouter.ai/keys) |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Obter Chave](https://cerebras.ai) |
|
||||
| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) |
|
||||
| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) |
|
||||
| **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Obter Chave](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | Custom | Apenas OAuth |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -985,8 +989,13 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -1002,7 +1011,7 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1013,8 +1022,17 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve
|
|||
**OpenAI**
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**VolcEngine (Doubao)**
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -1057,14 +1075,14 @@ Configure vários endpoints para o mesmo nome de modelo—PicoClaw fará round-r
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -1196,7 +1214,14 @@ Isso acontece quando outra instância do bot está em execução. Certifique-se
|
|||
| Serviço | Plano Gratuito | Caso de Uso |
|
||||
| --- | --- | --- |
|
||||
| **OpenRouter** | 200K tokens/mês | Múltiplos modelos (Claude, GPT-4, etc.) |
|
||||
| **Zhipu** | 200K tokens/mês | Melhor para usuários chineses |
|
||||
| **Volcengine CodingPlan** | ¥9,9/primeiro mês | Ideal para usuários chineses, múltiplos modelos SOTA (Doubao, DeepSeek, etc.) |
|
||||
| **Zhipu** | 200K tokens/mês | Adequado para usuários chineses |
|
||||
| **Brave Search** | 2000 consultas/mês | Funcionalidade de busca web |
|
||||
| **Groq** | Plano gratuito disponível | Inferência ultra-rápida (Llama, Mixtral) |
|
||||
| **Cerebras** | Plano gratuito disponível | Inferência ultra-rápida (Llama 3.3 70B) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
61
README.vi.md
61
README.vi.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Trợ lý AI Siêu Nhẹ viết bằng Go</h1>
|
||||
|
||||
|
|
@ -187,9 +187,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 Bắt đầu nhanh
|
||||
|
||||
> [!TIP]
|
||||
> Thiết lập API key trong `~/.picoclaw/config.json`.
|
||||
> Lấy API key: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> Tìm kiếm web là **tùy chọn** — lấy [Brave Search API](https://brave.com/search/api) miễn phí (2000 truy vấn/tháng) hoặc dùng tính năng auto fallback tích hợp sẵn.
|
||||
> Thiết lập API key trong `~/.picoclaw/config.json`. Lấy API key: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Tìm kiếm web là **tùy chọn** — lấy [Tavily API](https://tavily.com) miễn phí (1000 truy vấn/tháng) hoặc [Brave Search API](https://brave.com/search/api) (2000 truy vấn/tháng).
|
||||
|
||||
**1. Khởi tạo**
|
||||
|
||||
|
|
@ -203,8 +201,13 @@ picoclaw onboard
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"request_timeout": 300,
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
|
|
@ -617,7 +620,6 @@ PicoClaw lưu trữ dữ liệu trong workspace đã cấu hình (mặc định:
|
|||
├── HEARTBEAT.md # Prompt tác vụ định kỳ (kiểm tra mỗi 30 phút)
|
||||
├── IDENTITY.md # Danh tính Agent
|
||||
├── SOUL.md # Tâm hồn/Tính cách Agent
|
||||
├── TOOLS.md # Mô tả công cụ
|
||||
└── USER.md # Tùy chọn người dùng
|
||||
```
|
||||
|
||||
|
|
@ -943,8 +945,10 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
|||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Lấy Khóa](https://openrouter.ai/keys) |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Lấy Khóa](https://cerebras.ai) |
|
||||
| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) |
|
||||
| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) |
|
||||
| **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Lấy Key](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | Tùy chỉnh | Chỉ OAuth |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -954,8 +958,13 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -971,7 +980,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -982,8 +991,17 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
|||
**OpenAI**
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**VolcEngine (Doubao)**
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -1026,14 +1044,14 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -1165,6 +1183,13 @@ Một số nhà cung cấp (như Zhipu) có bộ lọc nội dung nghiêm ngặt
|
|||
| Dịch vụ | Gói miễn phí | Trường hợp sử dụng |
|
||||
| --- | --- | --- |
|
||||
| **OpenRouter** | 200K tokens/tháng | Đa model (Claude, GPT-4, v.v.) |
|
||||
| **Zhipu** | 200K tokens/tháng | Tốt nhất cho người dùng Trung Quốc |
|
||||
| **Volcengine CodingPlan** | ¥9.9/tháng đầu | Tốt nhất cho người dùng Trung Quốc, nhiều mô hình SOTA (Doubao, DeepSeek, v.v.) |
|
||||
| **Zhipu** | 200K tokens/tháng | Phù hợp cho người dùng Trung Quốc |
|
||||
| **Brave Search** | 2000 truy vấn/tháng | Chức năng tìm kiếm web |
|
||||
| **Groq** | Có gói miễn phí | Suy luận siêu nhanh (Llama, Mixtral) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
65
README.zh.md
65
README.zh.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: 基于Go语言的超高效 AI 助手</h1>
|
||||
|
||||
|
|
@ -208,9 +208,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d
|
|||
### 🚀 快速开始
|
||||
|
||||
> [!TIP]
|
||||
> 在 `~/.picoclaw/config.json` 中设置您的 API Key。
|
||||
> 获取 API Key: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu (智谱)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)
|
||||
> 网络搜索是 **可选的** - 获取免费的 [Tavily API](https://tavily.com) (每月 1000 次免费查询) 或 [Brave Search API](https://brave.com/search/api) (每月 2000 次免费查询)
|
||||
> 在 `~/.picoclaw/config.json` 中设置您的 API Key。获取 API Key: [火山引擎 (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu (智谱)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)。网络搜索是 **可选的** — 获取免费的 [Tavily API](https://tavily.com) (每月 1000 次免费查询) 或 [Brave Search API](https://brave.com/search/api) (每月 2000 次免费查询)。
|
||||
|
||||
**1. 初始化 (Initialize)**
|
||||
|
||||
|
|
@ -226,7 +224,7 @@ picoclaw onboard
|
|||
"agents": {
|
||||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"model_name": "gpt4",
|
||||
"model_name": "gpt-5.4",
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"max_tool_iterations": 20
|
||||
|
|
@ -234,8 +232,13 @@ picoclaw onboard
|
|||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"request_timeout": 300
|
||||
},
|
||||
|
|
@ -365,7 +368,6 @@ PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/work
|
|||
├── HEARTBEAT.md # 周期性任务提示词 (每 30 分钟检查一次)
|
||||
├── IDENTITY.md # Agent 身份设定
|
||||
├── SOUL.md # Agent 灵魂/性格
|
||||
├── TOOLS.md # 工具描述
|
||||
└── USER.md # 用户偏好
|
||||
|
||||
```
|
||||
|
|
@ -515,8 +517,10 @@ Agent 读取 HEARTBEAT.md
|
|||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [获取密钥](https://openrouter.ai/keys) |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | 本地 |
|
||||
| **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [获取密钥](https://cerebras.ai) |
|
||||
| **火山引擎** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) |
|
||||
| **火山引擎(Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) |
|
||||
| **神算云** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - |
|
||||
| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) |
|
||||
| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [获取密钥](https://longcat.chat/platform) |
|
||||
| **Antigravity** | `antigravity/` | Google Cloud | 自定义 | 仅 OAuth |
|
||||
| **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - |
|
||||
|
||||
|
|
@ -526,8 +530,13 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
|
|
@ -543,7 +552,7 @@ Agent 读取 HEARTBEAT.md
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -555,8 +564,18 @@ Agent 读取 HEARTBEAT.md
|
|||
|
||||
```json
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
||||
**火山引擎(Doubao)**
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
}
|
||||
```
|
||||
|
|
@ -622,14 +641,14 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
}
|
||||
|
|
@ -875,7 +894,15 @@ Discord: [https://discord.gg/V4sAZ9XWpN](https://discord.gg/V4sAZ9XWpN)
|
|||
| 服务 | 免费层级 | 适用场景 |
|
||||
| --- | --- | --- |
|
||||
| **OpenRouter** | 200K tokens/月 | 多模型聚合 (Claude, GPT-4 等) |
|
||||
| **智谱 (Zhipu)** | 200K tokens/月 | 最适合中国用户 |
|
||||
| **火山引擎 CodingPlan** | 9.9 元/首月 | 最适合国内用户,多种 SOTA 模型(豆包、DeepSeek 等) |
|
||||
| **智谱 (Zhipu)** | 200K tokens/月 | 适合中国用户 |
|
||||
| **Brave Search** | 2000 次查询/月 | 网络搜索功能 |
|
||||
| **Tavily** | 1000 次查询/月 | AI Agent 搜索优化 |
|
||||
| **Groq** | 提供免费层级 | 极速推理 (Llama, Mixtral) |
|
||||
| **LongCat** | 最多 5M tokens/天 | 推理速度快 (免费额度) |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" alt="PicoClaw Meme" width="512">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
func (s *appState) buildChannelMenuItems() []MenuItem {
|
||||
return []MenuItem{
|
||||
{Label: "Back", Description: "Return to main menu", Action: func() { s.pop() }},
|
||||
channelItem(
|
||||
"Telegram",
|
||||
"Telegram bot settings",
|
||||
|
|
@ -101,10 +100,6 @@ func (s *appState) channelMenu() tview.Primitive {
|
|||
s.pop()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'q' {
|
||||
s.pop()
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return menu
|
||||
|
|
|
|||
|
|
@ -41,3 +41,15 @@ func bannerView() *tview.TextView {
|
|||
text.SetBorder(false)
|
||||
return text
|
||||
}
|
||||
|
||||
const footerText = "Esc: Back/Exit | Enter: Enter | ←↓↑→ : Move | Space: Select | Tab/Shift+Tab: Switch"
|
||||
|
||||
func footerView() *tview.TextView {
|
||||
text := tview.NewTextView()
|
||||
text.SetTextAlign(tview.AlignCenter)
|
||||
text.SetText(footerText)
|
||||
text.SetBackgroundColor(tview.Styles.MoreContrastBackgroundColor)
|
||||
text.SetTextColor(tview.Styles.PrimaryTextColor)
|
||||
text.SetBorder(false)
|
||||
return text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ func NewAgentCommand() *cobra.Command {
|
|||
sessionKey string
|
||||
model string
|
||||
debug bool
|
||||
orchestrationEnabled bool
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -18,7 +17,7 @@ func NewAgentCommand() *cobra.Command {
|
|||
Short: "Interact with the agent directly",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return agentCmd(message, sessionKey, model, debug, orchestrationEnabled)
|
||||
return agentCmd(message, sessionKey, model, debug)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +25,6 @@ func NewAgentCommand() *cobra.Command {
|
|||
cmd.Flags().StringVarP(&message, "message", "m", "", "Send a single message (non-interactive mode)")
|
||||
cmd.Flags().StringVarP(&sessionKey, "session", "s", "cli:default", "Session key")
|
||||
cmd.Flags().StringVarP(&model, "model", "", "", "Model to use")
|
||||
cmd.Flags().BoolVar(&orchestrationEnabled, "orchestration", false, "Enable orchestration mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
)
|
||||
|
||||
func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled bool) error {
|
||||
func agentCmd(message, sessionKey, model string, debug bool) error {
|
||||
if sessionKey == "" {
|
||||
sessionKey = "cli:default"
|
||||
}
|
||||
|
|
@ -37,10 +37,6 @@ func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled boo
|
|||
cfg.Agents.Defaults.ModelName = model
|
||||
}
|
||||
|
||||
if orchestrationEnabled {
|
||||
cfg.Agents.Defaults.Orchestration = true
|
||||
}
|
||||
|
||||
provider, modelID, err := providers.CreateProvider(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating provider: %w", err)
|
||||
|
|
@ -54,6 +50,7 @@ func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled boo
|
|||
msgBus := bus.NewMessageBus()
|
||||
defer msgBus.Close()
|
||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||
defer agentLoop.Close()
|
||||
|
||||
// Print agent startup info (only for interactive mode)
|
||||
startupInfo := agentLoop.GetStartupInfo()
|
||||
|
|
|
|||
|
|
@ -72,14 +72,14 @@ func authLoginOpenAI(useDeviceCode bool) error {
|
|||
// If no openai in ModelList, add it
|
||||
if !foundOpenAI {
|
||||
appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
|
||||
ModelName: "gpt-5.2",
|
||||
Model: "openai/gpt-5.2",
|
||||
ModelName: "gpt-5.4",
|
||||
Model: "openai/gpt-5.4",
|
||||
AuthMethod: "oauth",
|
||||
})
|
||||
}
|
||||
|
||||
// Update default model to use OpenAI
|
||||
appCfg.Agents.Defaults.ModelName = "gpt-5.2"
|
||||
appCfg.Agents.Defaults.ModelName = "gpt-5.4"
|
||||
|
||||
if err = config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil {
|
||||
return fmt.Errorf("could not update config: %w", err)
|
||||
|
|
@ -90,7 +90,7 @@ func authLoginOpenAI(useDeviceCode bool) error {
|
|||
if cred.AccountID != "" {
|
||||
fmt.Printf("Account: %s\n", cred.AccountID)
|
||||
}
|
||||
fmt.Println("Default model set to: gpt-5.2")
|
||||
fmt.Println("Default model set to: gpt-5.4")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -318,13 +318,13 @@ func authLoginPasteToken(provider string) error {
|
|||
}
|
||||
if !found {
|
||||
appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
|
||||
ModelName: "gpt-5.2",
|
||||
Model: "openai/gpt-5.2",
|
||||
ModelName: "gpt-5.4",
|
||||
Model: "openai/gpt-5.4",
|
||||
AuthMethod: "token",
|
||||
})
|
||||
}
|
||||
// Update default model
|
||||
appCfg.Agents.Defaults.ModelName = "gpt-5.2"
|
||||
appCfg.Agents.Defaults.ModelName = "gpt-5.4"
|
||||
}
|
||||
if err := config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil {
|
||||
return fmt.Errorf("could not update config: %w", err)
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
package gateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
)
|
||||
|
||||
func NewGatewayCommand() *cobra.Command {
|
||||
var (
|
||||
debug bool
|
||||
orchestration bool
|
||||
enableStats bool
|
||||
)
|
||||
var debug bool
|
||||
var noTruncate bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "gateway",
|
||||
Aliases: []string{"g"},
|
||||
Short: "Start picoclaw gateway",
|
||||
Args: cobra.NoArgs,
|
||||
PreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
if noTruncate && !debug {
|
||||
return fmt.Errorf("the --no-truncate option can only be used in conjunction with --debug (-d)")
|
||||
}
|
||||
|
||||
if noTruncate {
|
||||
utils.SetDisableTruncation(true)
|
||||
logger.Info("String truncation is globally disabled via 'no-truncate' flag")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return gatewayCmd(debug, orchestration, enableStats)
|
||||
return gatewayCmd(debug)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
|
||||
cmd.Flags().BoolVar(&orchestration, "orchestration", false, "Enable subagent orchestration")
|
||||
cmd.Flags().BoolVar(&enableStats, "stats", false, "Enable stats tracking")
|
||||
cmd.Flags().BoolVarP(&noTruncate, "no-truncate", "T", false, "Disable string truncation in debug logs")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,14 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
const Logo = "🦞"
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
gitCommit string
|
||||
buildTime string
|
||||
goVersion string
|
||||
)
|
||||
|
||||
// GetPicoclawHome returns the picoclaw home directory.
|
||||
// Priority: $PICOCLAW_HOME > ~/.picoclaw
|
||||
func GetPicoclawHome() string {
|
||||
|
|
@ -40,25 +31,19 @@ func LoadConfig() (*config.Config, error) {
|
|||
}
|
||||
|
||||
// FormatVersion returns the version string with optional git commit
|
||||
// Deprecated: Use pkg/config.FormatVersion instead
|
||||
func FormatVersion() string {
|
||||
v := version
|
||||
if gitCommit != "" {
|
||||
v += fmt.Sprintf(" (git: %s)", gitCommit)
|
||||
}
|
||||
return v
|
||||
return config.FormatVersion()
|
||||
}
|
||||
|
||||
// FormatBuildInfo returns build time and go version info
|
||||
// Deprecated: Use pkg/config.FormatBuildInfo instead
|
||||
func FormatBuildInfo() (string, string) {
|
||||
build := buildTime
|
||||
goVer := goVersion
|
||||
if goVer == "" {
|
||||
goVer = runtime.Version()
|
||||
}
|
||||
return build, goVer
|
||||
return config.FormatBuildInfo()
|
||||
}
|
||||
|
||||
// GetVersion returns the version string
|
||||
// Deprecated: Use pkg/config.GetVersion instead
|
||||
func GetVersion() string {
|
||||
return version
|
||||
return config.GetVersion()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
package onboard
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
//go:generate cp -r ../../../../workspace .
|
||||
//go:embed workspace
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
func NewOnboardCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/auth"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func statusCmd() {
|
||||
|
|
@ -18,8 +19,8 @@ func statusCmd() {
|
|||
configPath := internal.GetConfigPath()
|
||||
|
||||
fmt.Printf("%s picoclaw Status\n", internal.Logo)
|
||||
fmt.Printf("Version: %s\n", internal.FormatVersion())
|
||||
build, _ := internal.FormatBuildInfo()
|
||||
fmt.Printf("Version: %s\n", config.FormatVersion())
|
||||
build, _ := config.FormatBuildInfo()
|
||||
if build != "" {
|
||||
fmt.Printf("Build: %s\n", build)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func NewVersionCommand() *cobra.Command {
|
||||
|
|
@ -22,8 +23,8 @@ func NewVersionCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
func printVersion() {
|
||||
fmt.Printf("%s picoclaw %s\n", internal.Logo, internal.FormatVersion())
|
||||
build, goVer := internal.FormatBuildInfo()
|
||||
fmt.Printf("%s picoclaw %s\n", internal.Logo, config.FormatVersion())
|
||||
build, goVer := config.FormatBuildInfo()
|
||||
if build != "" {
|
||||
fmt.Printf(" Build: %s\n", build)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,15 +22,16 @@ import (
|
|||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/skills"
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/status"
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/version"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func NewPicoclawCommand() *cobra.Command {
|
||||
short := fmt.Sprintf("%s picoclaw - Personal AI Assistant v%s\n\n", internal.Logo, internal.GetVersion())
|
||||
short := fmt.Sprintf("%s picoclaw - Personal AI Assistant v%s\n\n", internal.Logo, config.GetVersion())
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "picoclaw",
|
||||
Short: short,
|
||||
Example: "picoclaw list",
|
||||
Example: "picoclaw version",
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"restrict_to_workspace": true,
|
||||
"model_name": "gpt4",
|
||||
"model_name": "gpt-5.4",
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"max_tool_iterations": 20,
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
},
|
||||
|
|
@ -36,14 +36,19 @@
|
|||
"api_key": "sk-your-deepseek-key"
|
||||
},
|
||||
{
|
||||
"model_name": "loadbalanced-gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "longcat",
|
||||
"model": "longcat/LongCat-Flash-Thinking",
|
||||
"api_key": "your-longcat-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "loadbalanced-gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key1",
|
||||
"api_base": "https://api1.example.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "loadbalanced-gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "loadbalanced-gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key2",
|
||||
"api_base": "https://api2.example.com/v1"
|
||||
}
|
||||
|
|
@ -194,8 +199,13 @@
|
|||
"nickserv_password": "",
|
||||
"sasl_user": "",
|
||||
"sasl_password": "",
|
||||
"channels": ["#mychannel"],
|
||||
"request_caps": ["server-time", "message-tags"],
|
||||
"channels": [
|
||||
"#mychannel"
|
||||
],
|
||||
"request_caps": [
|
||||
"server-time",
|
||||
"message-tags"
|
||||
],
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
|
|
@ -269,6 +279,10 @@
|
|||
"avian": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api.avian.io/v1"
|
||||
},
|
||||
"longcat": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api.longcat.chat/openai"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
|
|
@ -279,6 +293,9 @@
|
|||
"brave": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_BRAVE_API_KEY",
|
||||
"api_keys": [
|
||||
"YOUR_BRAVE_API_KEY"
|
||||
],
|
||||
"max_results": 5
|
||||
},
|
||||
"tavily": {
|
||||
|
|
@ -293,7 +310,10 @@
|
|||
},
|
||||
"perplexity": {
|
||||
"enabled": false,
|
||||
"api_key": "",
|
||||
"api_key": "pplx-xxx",
|
||||
"api_keys": [
|
||||
"pplx-xxx"
|
||||
],
|
||||
"max_results": 5
|
||||
},
|
||||
"searxng": {
|
||||
|
|
@ -316,6 +336,13 @@
|
|||
},
|
||||
"mcp": {
|
||||
"enabled": false,
|
||||
"discovery": {
|
||||
"enabled": false,
|
||||
"ttl": 5,
|
||||
"max_search_results": 5,
|
||||
"use_bm25": true,
|
||||
"use_regex": false
|
||||
},
|
||||
"servers": {
|
||||
"context7": {
|
||||
"enabled": false,
|
||||
|
|
@ -459,6 +486,9 @@
|
|||
"enabled": false,
|
||||
"monitor_usb": true
|
||||
},
|
||||
"voice": {
|
||||
"echo_transcription": false
|
||||
},
|
||||
"gateway": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18790
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ services:
|
|||
|
||||
# ─────────────────────────────────────────────
|
||||
# PicoClaw Gateway (Long-running Bot)
|
||||
# docker compose -f docker/docker-compose.yml up picoclaw-gateway
|
||||
# docker compose -f docker/docker-compose.yml --profile gateway up
|
||||
# ─────────────────────────────────────────────
|
||||
picoclaw-gateway:
|
||||
image: docker.io/sipeed/picoclaw:latest
|
||||
|
|
@ -32,3 +32,21 @@ services:
|
|||
# - "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./data:/root/.picoclaw
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# PicoClaw Launcher (Web Console + Gateway)
|
||||
# docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# ─────────────────────────────────────────────
|
||||
picoclaw-launcher:
|
||||
image: docker.io/sipeed/picoclaw:launcher
|
||||
container_name: picoclaw-launcher
|
||||
restart: on-failure
|
||||
profiles:
|
||||
- launcher
|
||||
environment:
|
||||
- PICOCLAW_GATEWAY_HOST=0.0.0.0
|
||||
ports:
|
||||
- "127.0.0.1:18800:18800"
|
||||
- "127.0.0.1:18790:18790"
|
||||
volumes:
|
||||
- ./data:/root/.picoclaw
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ Add this to `config.json`:
|
|||
"enabled": true,
|
||||
"text": "Thinking..."
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
"reasoning_channel_id": "",
|
||||
"message_format": "richtext"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,10 +43,12 @@ Add this to `config.json`:
|
|||
| group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) |
|
||||
| placeholder | object | No | Placeholder message config |
|
||||
| reasoning_channel_id | string | No | Target channel for reasoning output |
|
||||
| message_format | string | No | Output format: `"richtext"` (default) renders markdown as HTML; `"plain"` sends plain text only |
|
||||
|
||||
## 3. Currently Supported
|
||||
|
||||
- Text message send/receive
|
||||
- Text message send/receive with markdown rendering (bold, italic, headers, code blocks, etc.)
|
||||
- Configurable message format (`richtext` / `plain`)
|
||||
- Incoming image/audio/video/file download (MediaStore first, local path fallback)
|
||||
- Incoming audio normalization into existing transcription flow (`[audio: ...]`)
|
||||
- Outgoing image/audio/video/file upload and send
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ Problem: Agent needs to know both `provider` and `model`, adding complexity.
|
|||
Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design:
|
||||
|
||||
1. **Model-centric**: Users care about models, not providers
|
||||
2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.2`, `anthropic/claude-sonnet-4.6`
|
||||
2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.4`, `anthropic/claude-sonnet-4.6`
|
||||
3. **Configuration-driven**: Adding new Providers only requires config changes, no code changes
|
||||
|
||||
### 2.2 New Configuration Structure
|
||||
|
|
@ -81,8 +81,8 @@ Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design:
|
|||
"api_key": "sk-xxx"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.2",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-xxx"
|
||||
},
|
||||
{
|
||||
|
|
@ -128,7 +128,7 @@ type Config struct {
|
|||
type ModelConfig struct {
|
||||
// Required
|
||||
ModelName string `json:"model_name"` // user-facing name (alias)
|
||||
Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.2
|
||||
Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.4
|
||||
|
||||
// Common config
|
||||
APIBase string `json:"api_base,omitempty"`
|
||||
|
|
@ -180,7 +180,7 @@ Identify protocol via prefix in `model` field:
|
|||
"model": "deepseek-chat"
|
||||
},
|
||||
"coder": {
|
||||
"model": "gpt-5.2",
|
||||
"model": "gpt-5.4",
|
||||
"system_prompt": "You are a coding assistant..."
|
||||
},
|
||||
"translator": {
|
||||
|
|
@ -200,7 +200,7 @@ Each Agent only needs to specify `model` (corresponds to `model_name` in `model_
|
|||
model_list:
|
||||
- model_name: gpt-4o
|
||||
litellm_params:
|
||||
model: openai/gpt-5.2
|
||||
model: openai/gpt-5.4
|
||||
api_key: xxx
|
||||
- model_name: my-custom
|
||||
litellm_params:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ The new `model_list` configuration offers several advantages:
|
|||
"agents": {
|
||||
"defaults": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.2"
|
||||
"model": "gpt-5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ The new `model_list` configuration offers several advantages:
|
|||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
},
|
||||
|
|
@ -82,7 +82,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
|||
|
||||
| Prefix | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `openai/` | OpenAI API (default) | `openai/gpt-5.2` |
|
||||
| `openai/` | OpenAI API (default) | `openai/gpt-5.4` |
|
||||
| `anthropic/` | Anthropic API | `anthropic/claude-opus-4` |
|
||||
| `antigravity/` | Google via Antigravity OAuth | `antigravity/gemini-2.0-flash` |
|
||||
| `gemini/` | Google Gemini API | `gemini/gemini-2.0-flash-exp` |
|
||||
|
|
@ -109,7 +109,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
|||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `model_name` | Yes | User-facing alias for the model |
|
||||
| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.2`) |
|
||||
| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) |
|
||||
| `api_base` | No | API endpoint URL |
|
||||
| `api_key` | No* | API authentication key |
|
||||
| `proxy` | No | HTTP proxy URL |
|
||||
|
|
@ -130,19 +130,19 @@ Configure multiple endpoints for the same model to distribute load:
|
|||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key1",
|
||||
"api_base": "https://api1.example.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key2",
|
||||
"api_base": "https://api2.example.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key3",
|
||||
"api_base": "https://api3.example.com/v1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,21 @@ PicoClaw's tools configuration is located in the `tools` field of `config.json`.
|
|||
```json
|
||||
{
|
||||
"tools": {
|
||||
"web": { ... },
|
||||
"mcp": { ... },
|
||||
"exec": { ... },
|
||||
"cron": { ... },
|
||||
"skills": { ... }
|
||||
"web": {
|
||||
...
|
||||
},
|
||||
"mcp": {
|
||||
...
|
||||
},
|
||||
"exec": {
|
||||
...
|
||||
},
|
||||
"cron": {
|
||||
...
|
||||
},
|
||||
"skills": {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -23,7 +33,7 @@ Web tools are used for web search and fetching.
|
|||
### Brave
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ------------- | ------ | ------- | ------------------------- |
|
||||
|---------------|--------|---------|---------------------------|
|
||||
| `enabled` | bool | false | Enable Brave search |
|
||||
| `api_key` | string | - | Brave Search API key |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
|
@ -31,14 +41,14 @@ Web tools are used for web search and fetching.
|
|||
### DuckDuckGo
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ------------- | ---- | ------- | ------------------------- |
|
||||
|---------------|------|---------|---------------------------|
|
||||
| `enabled` | bool | true | Enable DuckDuckGo search |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### Perplexity
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ------------- | ------ | ------- | ------------------------- |
|
||||
|---------------|--------|---------|---------------------------|
|
||||
| `enabled` | bool | false | Enable Perplexity search |
|
||||
| `api_key` | string | - | Perplexity API key |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
|
@ -48,7 +58,7 @@ Web tools are used for web search and fetching.
|
|||
The exec tool is used to execute shell commands.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ---------------------- | ----- | ------- | ------------------------------------------ |
|
||||
|------------------------|-------|---------|--------------------------------------------|
|
||||
| `enable_deny_patterns` | bool | true | Enable default dangerous command blocking |
|
||||
| `custom_deny_patterns` | array | [] | Custom deny patterns (regular expressions) |
|
||||
|
||||
|
|
@ -81,7 +91,10 @@ By default, PicoClaw blocks the following dangerous commands:
|
|||
"tools": {
|
||||
"exec": {
|
||||
"enable_deny_patterns": true,
|
||||
"custom_deny_patterns": ["\\brm\\s+-r\\b", "\\bkillall\\s+python"]
|
||||
"custom_deny_patterns": [
|
||||
"\\brm\\s+-r\\b",
|
||||
"\\bkillall\\s+python"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,24 +105,47 @@ By default, PicoClaw blocks the following dangerous commands:
|
|||
The cron tool is used for scheduling periodic tasks.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ---------------------- | ---- | ------- | ---------------------------------------------- |
|
||||
|------------------------|------|---------|------------------------------------------------|
|
||||
| `exec_timeout_minutes` | int | 5 | Execution timeout in minutes, 0 means no limit |
|
||||
|
||||
## MCP Tool
|
||||
|
||||
The MCP tool enables integration with external Model Context Protocol servers.
|
||||
|
||||
### Tool Discovery (Lazy Loading)
|
||||
|
||||
When connecting to multiple MCP servers, exposing hundreds of tools simultaneously can exhaust the LLM's context window
|
||||
and increase API costs. The **Discovery** feature solves this by keeping MCP tools *hidden* by default.
|
||||
|
||||
Instead of loading all tools, the LLM is provided with a lightweight search tool (using BM25 keyword matching or Regex).
|
||||
When the LLM needs a specific capability, it searches the hidden library. Matching tools are then temporarily "unlocked"
|
||||
and injected into the context for a configured number of turns (`ttl`).
|
||||
|
||||
### Global Config
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --------- | ------ | ------- | ----------------------------------- |
|
||||
|-------------|--------|---------|----------------------------------------------|
|
||||
| `enabled` | bool | false | Enable MCP integration globally |
|
||||
| `discovery` | object | `{}` | Configuration for Tool Discovery (see below) |
|
||||
| `servers` | object | `{}` | Map of server name to server config |
|
||||
|
||||
### Discovery Config (`discovery`)
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|----------------------|------|---------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `enabled` | bool | false | If true, MCP tools are hidden and loaded on-demand via search. If false, all tools are loaded |
|
||||
| `ttl` | int | 5 | Number of conversational turns a discovered tool remains unlocked |
|
||||
| `max_search_results` | int | 5 | Maximum number of tools returned per search query |
|
||||
| `use_bm25` | bool | true | Enable the natural language/keyword search tool (`tool_search_tool_bm25`). **Warning**: consumes more resources than regex search |
|
||||
| `use_regex` | bool | false | Enable the regex pattern search tool (`tool_search_tool_regex`) |
|
||||
|
||||
> **Note:** If `discovery.enabled` is `true`, you MUST enable at least one search engine (`use_bm25` or `use_regex`),
|
||||
> otherwise the application will fail to start.
|
||||
|
||||
### Per-Server Config
|
||||
|
||||
| Config | Type | Required | Description |
|
||||
| ---------- | ------ | -------- | ------------------------------------------ |
|
||||
|------------|--------|----------|--------------------------------------------|
|
||||
| `enabled` | bool | yes | Enable this MCP server |
|
||||
| `type` | string | no | Transport type: `stdio`, `sse`, `http` |
|
||||
| `command` | string | stdio | Executable command for stdio transport |
|
||||
|
|
@ -140,7 +176,11 @@ The MCP tool enables integration with external Model Context Protocol servers.
|
|||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/tmp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +210,62 @@ The MCP tool enables integration with external Model Context Protocol servers.
|
|||
}
|
||||
```
|
||||
|
||||
#### 3) Massive MCP setup with Tool Discovery enabled
|
||||
|
||||
*In this example, the LLM will only see the `tool_search_tool_bm25`. It will search and unlock Github or Postgres tools
|
||||
dynamically only when requested by the user.*
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"discovery": {
|
||||
"enabled": true,
|
||||
"ttl": 5,
|
||||
"max_search_results": 5,
|
||||
"use_bm25": true,
|
||||
"use_regex": false
|
||||
},
|
||||
"servers": {
|
||||
"github": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-github"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN"
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-postgres",
|
||||
"postgresql://user:password@localhost/dbname"
|
||||
]
|
||||
},
|
||||
"slack": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-slack"
|
||||
],
|
||||
"env": {
|
||||
"SLACK_BOT_TOKEN": "YOUR_SLACK_BOT_TOKEN",
|
||||
"SLACK_TEAM_ID": "YOUR_SLACK_TEAM_ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Skills Tool
|
||||
|
||||
The skills tool configures skill discovery and installation via registries like ClawHub.
|
||||
|
|
@ -177,7 +273,7 @@ The skills tool configures skill discovery and installation via registries like
|
|||
### Registries
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ---------------------------------- | ------ | -------------------- | ----------------------- |
|
||||
|------------------------------------|--------|----------------------|----------------------------------------------|
|
||||
| `registries.clawhub.enabled` | bool | true | Enable ClawHub registry |
|
||||
| `registries.clawhub.base_url` | string | `https://clawhub.ai` | ClawHub base URL |
|
||||
| `registries.clawhub.auth_token` | string | `""` | Optional Bearer token for higher rate limits |
|
||||
|
|
@ -217,4 +313,5 @@ For example:
|
|||
- `PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES=10`
|
||||
- `PICOCLAW_TOOLS_MCP_ENABLED=true`
|
||||
|
||||
Note: Nested map-style config (for example `tools.mcp.servers.<name>.*`) is configured in `config.json` rather than environment variables.
|
||||
Note: Nested map-style config (for example `tools.mcp.servers.<name>.*`) is configured in `config.json` rather than
|
||||
environment variables.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
|
@ -32,6 +33,9 @@ func init() {
|
|||
uniqueIDPrefix = hex.EncodeToString(b[:])
|
||||
}
|
||||
|
||||
// audioAnnotationRe matches audio/voice annotations injected by channels (e.g. [voice], [audio: file.ogg]).
|
||||
var audioAnnotationRe = regexp.MustCompile(`\[(voice|audio)(?::[^\]]*)?\]`)
|
||||
|
||||
// uniqueID generates a process-unique ID using a random prefix and an atomic counter.
|
||||
// This ID is intended for internal correlation (e.g. media scope keys) and is NOT
|
||||
// cryptographically secure — it must not be used in contexts where unpredictability matters.
|
||||
|
|
@ -285,10 +289,10 @@ func (c *BaseChannel) HandleMessage(
|
|||
}
|
||||
}
|
||||
// Placeholder — independent pipeline.
|
||||
// Skip for DraftSender channels: the streaming draft bubble serves
|
||||
// as the placeholder, and sendMessage on final response replaces it.
|
||||
// Sending both a placeholder AND drafts causes duplicate chat bubbles.
|
||||
if _, isDrafter := c.owner.(DraftSender); !isDrafter {
|
||||
// Skip when the message contains audio: the agent will send the
|
||||
// placeholder after transcription completes, so the user sees
|
||||
// "Thinking…" only once the voice has been processed.
|
||||
if !audioAnnotationRe.MatchString(content) {
|
||||
if pc, ok := c.owner.(PlaceholderCapable); ok {
|
||||
if phID, err := pc.SendPlaceholder(ctx, chatID); err == nil && phID != "" {
|
||||
c.placeholderRecorder.RecordPlaceholder(c.name, chatID, phID)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot"
|
||||
"github.com/open-dingtalk/dingtalk-stream-sdk-go/client"
|
||||
dinglog "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
|
|
@ -39,6 +40,9 @@ func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) (
|
|||
return nil, fmt.Errorf("dingtalk client_id and client_secret are required")
|
||||
}
|
||||
|
||||
// Set the logger for the Stream SDK
|
||||
dinglog.SetLogger(logger.NewLogger("dingtalk"))
|
||||
|
||||
base := channels.NewBaseChannel("dingtalk", cfg, messageBus, cfg.AllowFrom,
|
||||
channels.WithMaxMessageLength(20000),
|
||||
channels.WithGroupTrigger(cfg.GroupTrigger),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ const (
|
|||
lineBotInfoEndpoint = lineAPIBase + "/info"
|
||||
lineLoadingEndpoint = lineAPIBase + "/chat/loading/start"
|
||||
lineReplyTokenMaxAge = 25 * time.Second
|
||||
|
||||
// Limit request body to prevent memory exhaustion (DoS).
|
||||
// LINE webhook payloads are typically a few KB; 1 MiB is generous.
|
||||
maxWebhookBodySize = 1 << 20 // 1 MiB
|
||||
)
|
||||
|
||||
type replyTokenEntry struct {
|
||||
|
|
@ -166,7 +170,7 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, maxWebhookBodySize+1))
|
||||
if err != nil {
|
||||
logger.ErrorCF("line", "Failed to read request body", map[string]any{
|
||||
"error": err.Error(),
|
||||
|
|
@ -174,6 +178,11 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if int64(len(body)) > maxWebhookBodySize {
|
||||
logger.WarnC("line", "Webhook request body too large, rejected")
|
||||
http.Error(w, "Request entity too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
signature := r.Header.Get("X-Line-Signature")
|
||||
if !c.verifySignature(body, signature) {
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@ func BuiltinDefinitions() []Definition {
|
|||
listCommand(),
|
||||
switchCommand(),
|
||||
checkCommand(),
|
||||
clearCommand(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ type Runtime struct {
|
|||
GetEnabledChannels func() []string
|
||||
SwitchModel func(value string) (oldModel string, err error)
|
||||
SwitchChannel func(value string) error
|
||||
ClearHistory func() error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,18 +227,18 @@ type AgentDefaults struct {
|
|||
ModelName string `json:"model_name,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
|
||||
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
|
||||
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
||||
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
||||
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
||||
PlanModel string `json:"plan_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_PLAN_MODEL"`
|
||||
PlanModelFallbacks []string `json:"plan_model_fallbacks,omitempty"`
|
||||
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
|
||||
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
||||
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||
Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||
TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"`
|
||||
Orchestration bool `json:"orchestration,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION"`
|
||||
SummarizeMessageThreshold int `json:"summarize_message_threshold" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_MESSAGE_THRESHOLD"`
|
||||
SummarizeTokenPercent int `json:"summarize_token_percent" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_TOKEN_PERCENT"`
|
||||
MaxMediaSize int `json:"max_media_size,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_MEDIA_SIZE"`
|
||||
TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"`
|
||||
Orchestration bool `json:"orchestration,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION"`
|
||||
Routing *RoutingConfig `json:"routing,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -304,19 +304,20 @@ type WhatsAppConfig struct {
|
|||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_WHATSAPP_ALLOW_FROM"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_WHATSAPP_REASONING_CHANNEL_ID"`
|
||||
}
|
||||
|
||||
type TelegramConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_TELEGRAM_ENABLED"`
|
||||
Token string `json:"token" env:"PICOCLAW_CHANNELS_TELEGRAM_TOKEN"`
|
||||
BaseURL string `json:"base_url" env:"PICOCLAW_CHANNELS_TELEGRAM_BASE_URL"`
|
||||
Proxy string `json:"proxy" env:"PICOCLAW_CHANNELS_TELEGRAM_PROXY"`
|
||||
WebAppURL string `json:"web_app_url" env:"PICOCLAW_CHANNELS_TELEGRAM_WEB_APP_URL"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_TELEGRAM_ALLOW_FROM"`
|
||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
||||
Typing TypingConfig `json:"typing,omitempty"`
|
||||
Placeholder PlaceholderConfig `json:"placeholder,omitempty"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"`
|
||||
WebAppURL string `json:"web_app_url" env:"PICOCLAW_CHANNELS_TELEGRAM_WEB_APP_URL"`
|
||||
SubagentThreadID int `json:"subagent_thread_id,omitempty" env:"PICOCLAW_CHANNELS_TELEGRAM_SUBAGENT_THREAD_ID"`
|
||||
HeartbeatThreadID int `json:"heartbeat_thread_id,omitempty" env:"PICOCLAW_CHANNELS_TELEGRAM_HEARTBEAT_THREAD_ID"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"`
|
||||
}
|
||||
|
||||
type FeishuConfig struct {
|
||||
|
|
@ -609,12 +610,13 @@ type ModelConfig struct {
|
|||
AuthMethod string `json:"auth_method,omitempty"` // Authentication method: oauth, token
|
||||
ConnectMode string `json:"connect_mode,omitempty"` // Connection mode: stdio, grpc
|
||||
Workspace string `json:"workspace,omitempty"` // Workspace path for CLI-based providers
|
||||
|
||||
// Optional optimizations
|
||||
RPM int `json:"rpm,omitempty"` // Requests per minute limit
|
||||
MaxTokensField string `json:"max_tokens_field,omitempty"` // Field name for max tokens (e.g., "max_completion_tokens")
|
||||
Stream *bool `json:"stream,omitempty"` // Use SSE streaming (default: protocol-dependent)
|
||||
RequestTimeout int `json:"request_timeout,omitempty"`
|
||||
ThinkingLevel string `json:"thinking_level,omitempty"` // Extended thinking: off|low|medium|high|xhigh|adaptive
|
||||
Stream *bool `json:"stream,omitempty"` // Use SSE streaming (default: protocol-dependent)
|
||||
}
|
||||
|
||||
// Validate checks if the ModelConfig has all required fields.
|
||||
|
|
|
|||
|
|
@ -407,6 +407,23 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
|||
}, true
|
||||
},
|
||||
},
|
||||
{
|
||||
providerNames: []string{"longcat"},
|
||||
protocol: "longcat",
|
||||
buildConfig: func(p ProvidersConfig) (ModelConfig, bool) {
|
||||
if p.LongCat.APIKey == "" && p.LongCat.APIBase == "" {
|
||||
return ModelConfig{}, false
|
||||
}
|
||||
return ModelConfig{
|
||||
ModelName: "longcat",
|
||||
Model: "longcat/LongCat-Flash-Thinking",
|
||||
APIKey: p.LongCat.APIKey,
|
||||
APIBase: p.LongCat.APIBase,
|
||||
Proxy: p.LongCat.Proxy,
|
||||
RequestTimeout: p.LongCat.RequestTimeout,
|
||||
}, true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Process each provider migration
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package health
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
|
@ -13,7 +12,6 @@ import (
|
|||
|
||||
type Server struct {
|
||||
server *http.Server
|
||||
mux *http.ServeMux
|
||||
mu sync.RWMutex
|
||||
ready bool
|
||||
checks map[string]Check
|
||||
|
|
@ -36,7 +34,6 @@ type StatusResponse struct {
|
|||
func NewServer(host string, port int) *Server {
|
||||
mux := http.NewServeMux()
|
||||
s := &Server{
|
||||
mux: mux,
|
||||
ready: false,
|
||||
checks: make(map[string]Check),
|
||||
startTime: time.Now(),
|
||||
|
|
@ -49,34 +46,13 @@ func NewServer(host string, port int) *Server {
|
|||
s.server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Mux returns the underlying ServeMux so additional routes can be registered.
|
||||
func (s *Server) Mux() *http.ServeMux {
|
||||
return s.mux
|
||||
}
|
||||
|
||||
// StartTLS starts the server with TLS using the provided certificate and key files.
|
||||
func (s *Server) StartTLS(certFile, keyFile string) error {
|
||||
s.mu.Lock()
|
||||
s.ready = true
|
||||
s.mu.Unlock()
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load TLS cert: %w", err)
|
||||
}
|
||||
s.server.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
return s.server.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
s.mu.Lock()
|
||||
s.ready = true
|
||||
|
|
|
|||
28
pkg/health/server_ext.go
Normal file
28
pkg/health/server_ext.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package health
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux returns the underlying ServeMux so additional routes can be registered.
|
||||
func (s *Server) Mux() *http.ServeMux {
|
||||
return s.server.Handler.(*http.ServeMux)
|
||||
}
|
||||
|
||||
// StartTLS starts the server with TLS using the provided certificate and key files.
|
||||
func (s *Server) StartTLS(certFile, keyFile string) error {
|
||||
s.mu.Lock()
|
||||
s.ready = true
|
||||
s.mu.Unlock()
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load TLS cert: %w", err)
|
||||
}
|
||||
s.server.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
return s.server.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
|
@ -86,14 +86,14 @@ func (s *JSONLStore) metaPath(key string) string {
|
|||
|
||||
// sanitizeKey converts a session key to a safe filename component.
|
||||
// Mirrors pkg/session.sanitizeFilename so that migration paths match.
|
||||
//
|
||||
// Note: this is a lossy mapping — "telegram:123" and "telegram_123"
|
||||
// both produce the same filename. This is an intentional tradeoff:
|
||||
// keys with colons (e.g. from channels) are by far the common case,
|
||||
// and a bidirectional encoding (like URL-encoding) would complicate
|
||||
// file listings and debugging.
|
||||
// Replaces ':' with '_' (session key separator) and '/' and '\' with '_'
|
||||
// so composite IDs (e.g. Telegram forum "chatID/threadID", Slack "channel/thread_ts")
|
||||
// do not create subdirectories or break on Windows.
|
||||
func sanitizeKey(key string) string {
|
||||
return strings.ReplaceAll(key, ":", "_")
|
||||
s := strings.ReplaceAll(key, ":", "_")
|
||||
s = strings.ReplaceAll(s, "/", "_")
|
||||
s = strings.ReplaceAll(s, "\\", "_")
|
||||
return s
|
||||
}
|
||||
|
||||
// readMeta loads the metadata file for a session.
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ func MigrateFromJSON(
|
|||
if !strings.HasSuffix(name, ".json") {
|
||||
continue
|
||||
}
|
||||
// Skip JSONL metadata files. They are part of the new storage format,
|
||||
// not legacy session snapshots, and re-importing them would overwrite
|
||||
// the paired .jsonl history with an empty message list.
|
||||
if strings.HasSuffix(name, ".meta.json") {
|
||||
continue
|
||||
}
|
||||
// Skip already-migrated files.
|
||||
if strings.HasSuffix(name, ".migrated") {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ var migrateableFiles = []string{
|
|||
"AGENTS.md",
|
||||
"SOUL.md",
|
||||
"USER.md",
|
||||
"TOOLS.md",
|
||||
"HEARTBEAT.md",
|
||||
"TOOLS.md",
|
||||
}
|
||||
|
||||
var migrateableDirs = []string{
|
||||
|
|
|
|||
|
|
@ -181,8 +181,10 @@ func buildParams(
|
|||
}
|
||||
for _, tc := range msg.ToolCalls {
|
||||
args := tc.Arguments
|
||||
if args == nil && tc.Function != nil && len(tc.Function.Arguments) > 0 {
|
||||
args = tc.Function.Arguments
|
||||
if args == nil && tc.Function != nil && tc.Function.Arguments != "" {
|
||||
if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil {
|
||||
args = map[string]any{}
|
||||
}
|
||||
}
|
||||
if args == nil {
|
||||
args = map[string]any{}
|
||||
|
|
@ -306,19 +308,16 @@ func levelToBudget(level string) int {
|
|||
func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam {
|
||||
result := make([]anthropic.ToolUnionParam, 0, len(tools))
|
||||
for _, t := range tools {
|
||||
params := t.Function.ParametersMap()
|
||||
tool := anthropic.ToolParam{
|
||||
Name: t.Function.Name,
|
||||
InputSchema: anthropic.ToolInputSchemaParam{
|
||||
Properties: params["properties"],
|
||||
Properties: t.Function.Parameters["properties"],
|
||||
},
|
||||
}
|
||||
if desc := t.Function.Description; desc != "" {
|
||||
tool.Description = anthropic.String(desc)
|
||||
}
|
||||
|
||||
switch req := params["required"].(type) {
|
||||
case []any:
|
||||
if req, ok := t.Function.Parameters["required"].([]any); ok {
|
||||
required := make([]string, 0, len(req))
|
||||
for _, r := range req {
|
||||
if s, ok := r.(string); ok {
|
||||
|
|
@ -326,8 +325,6 @@ func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam {
|
|||
}
|
||||
}
|
||||
tool.InputSchema.Required = required
|
||||
case []string:
|
||||
tool.InputSchema.Required = append([]string(nil), req...)
|
||||
}
|
||||
result = append(result, anthropic.ToolUnionParam{OfTool: &tool})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,6 @@ func BuildAgentMainSessionKey(agentID string) string {
|
|||
return fmt.Sprintf("agent:%s:%s", NormalizeAgentID(agentID), DefaultMainKey)
|
||||
}
|
||||
|
||||
// BuildSubagentSessionKey returns "subagent:<taskID>" for subagent sessions.
|
||||
func BuildSubagentSessionKey(taskID string) string {
|
||||
return fmt.Sprintf("subagent:%s", taskID)
|
||||
}
|
||||
|
||||
// BuildAgentPeerSessionKey constructs a session key based on agent, channel, peer, and DM scope.
|
||||
func BuildAgentPeerSessionKey(params SessionKeyParams) string {
|
||||
agentID := NormalizeAgentID(params.AgentID)
|
||||
|
|
@ -195,3 +190,8 @@ func resolveLinkedPeerID(identityLinks map[string][]string, channel, peerID stri
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BuildSubagentSessionKey returns "subagent:<taskID>" for subagent sessions.
|
||||
func BuildSubagentSessionKey(taskID string) string {
|
||||
return fmt.Sprintf("subagent:%s", taskID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
|
|
@ -112,7 +111,7 @@ func (c *ClawHubRegistry) Search(ctx context.Context, query string, limit int) (
|
|||
q := u.Query()
|
||||
q.Set("q", query)
|
||||
if limit > 0 {
|
||||
q.Set("limit", strconv.Itoa(limit))
|
||||
q.Set("limit", fmt.Sprintf("%d", limit))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func (rm *RegistryManager) SearchAll(ctx context.Context, query string, limit in
|
|||
close(resultsCh)
|
||||
}()
|
||||
|
||||
merged := make([]SearchResult, 0, len(regs)*limit)
|
||||
var merged []SearchResult
|
||||
var lastErr error
|
||||
|
||||
var anyRegistrySucceeded bool
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ func NewSearchCache(maxEntries int, ttl time.Duration) *SearchCache {
|
|||
ttl = 5 * time.Minute
|
||||
}
|
||||
return &SearchCache{
|
||||
entries: make(map[string]*cacheEntry, maxEntries),
|
||||
order: make([]string, 0, maxEntries),
|
||||
entries: make(map[string]*cacheEntry),
|
||||
order: make([]string, 0),
|
||||
maxEntries: maxEntries,
|
||||
ttl: ttl,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,11 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
audioExtensions = []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"}
|
||||
audioTypes = []string{"audio/", "application/ogg", "application/x-ogg"}
|
||||
)
|
||||
|
||||
// IsAudioFile checks if a file is an audio file based on its filename extension and content type.
|
||||
func IsAudioFile(filename, contentType string) bool {
|
||||
audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"}
|
||||
audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"}
|
||||
|
||||
for _, ext := range audioExtensions {
|
||||
if strings.HasSuffix(strings.ToLower(filename), ext) {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ PicoClaw 🦞
|
|||
## Description
|
||||
Ultra-lightweight personal AI assistant written in Go, inspired by nanobot.
|
||||
|
||||
## Version
|
||||
0.1.0
|
||||
|
||||
## Purpose
|
||||
- Provide intelligent AI assistance with minimal resource usage
|
||||
- Support multiple LLM providers (OpenAI, Anthropic, Zhipu, etc.)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue